STS: Fallback to Caller Identity when RoleArn is missing in AssumeRole (#8345)
* s3api: make RoleArn optional in AssumeRole * s3api: address PR feedback for optional RoleArn * iam: add configurable default role for AssumeRole * S3 STS: Use caller identity when RoleArn is missing - Fallback to PrincipalArn/Context in AssumeRole if RoleArn is empty - Handle User ARNs in prepareSTSCredentials - Fix PrincipalArn generation for env var credentials * Test: Add unit test for AssumeRole caller identity fallback * fix(s3api): propagate admin permissions to assumed role session when using caller identity fallback * STS: Fix is_admin propagation and optimize IAM policy evaluation for assumed roles - Restore is_admin propagation via JWT req_ctx - Optimize IsActionAllowed to skip role lookups for admin sessions - Ensure session policies are still applied for downscoping - Remove debug logging - Fix syntax errors in cleanup * fix(iam): resolve STS policy bypass for admin sessions - Fixed IsActionAllowed in iam_manager.go to correctly identify and validate internal STS tokens, ensuring session policies are enforced. - Refactored VerifyActionPermission in auth_credentials.go to properly handle session tokens and avoid legacy authorization short-circuits. - Added debug logging for better tracing of policy evaluation and session validation.
This commit is contained in:
@@ -186,6 +186,8 @@ func (h *STSHandlers) handleAssumeRoleWithWebIdentity(w http.ResponseWriter, r *
|
||||
Policy: sessionPolicyPtr,
|
||||
}
|
||||
|
||||
glog.V(0).Infof("DEBUG: AssumeRoleWithWebIdentity: RoleArn=%s SessionPolicyLen=%d", roleArn, len(sessionPolicyJSON))
|
||||
|
||||
// Call STS service
|
||||
response, err := h.stsService.AssumeRoleWithWebIdentity(ctx, request)
|
||||
if err != nil {
|
||||
@@ -237,11 +239,7 @@ func (h *STSHandlers) handleAssumeRole(w http.ResponseWriter, r *http.Request) {
|
||||
roleSessionName := r.FormValue("RoleSessionName")
|
||||
|
||||
// Validate required parameters
|
||||
if roleArn == "" {
|
||||
h.writeSTSErrorResponse(w, r, STSErrMissingParameter,
|
||||
fmt.Errorf("RoleArn is required"))
|
||||
return
|
||||
}
|
||||
// RoleArn is optional to support S3-compatible clients that omit it
|
||||
|
||||
if roleSessionName == "" {
|
||||
h.writeSTSErrorResponse(w, r, STSErrMissingParameter,
|
||||
@@ -290,22 +288,40 @@ func (h *STSHandlers) handleAssumeRole(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
// Check if the caller is authorized to assume the role (sts:AssumeRole permission)
|
||||
// This validates that the caller has a policy allowing sts:AssumeRole on the target role
|
||||
if authErr := h.iam.VerifyActionPermission(r, identity, Action("sts:AssumeRole"), "", roleArn); authErr != s3err.ErrNone {
|
||||
glog.V(2).Infof("AssumeRole: caller %s is not authorized to assume role %s", identity.Name, roleArn)
|
||||
h.writeSTSErrorResponse(w, r, STSErrAccessDenied,
|
||||
fmt.Errorf("user %s is not authorized to assume role %s", identity.Name, roleArn))
|
||||
return
|
||||
// Check authorizations
|
||||
if roleArn != "" {
|
||||
// Check if the caller is authorized to assume the role (sts:AssumeRole permission)
|
||||
if authErr := h.iam.VerifyActionPermission(r, identity, Action("sts:AssumeRole"), "", roleArn); authErr != s3err.ErrNone {
|
||||
glog.V(2).Infof("AssumeRole: caller %s is not authorized to assume role %s", identity.Name, roleArn)
|
||||
h.writeSTSErrorResponse(w, r, STSErrAccessDenied,
|
||||
fmt.Errorf("user %s is not authorized to assume role %s", identity.Name, roleArn))
|
||||
return
|
||||
}
|
||||
|
||||
// Validate that the target role trusts the caller (Trust Policy)
|
||||
if err := h.iam.ValidateTrustPolicyForPrincipal(r.Context(), roleArn, identity.PrincipalArn); err != nil {
|
||||
glog.V(2).Infof("AssumeRole: trust policy validation failed for %s to assume %s: %v", identity.Name, roleArn, err)
|
||||
h.writeSTSErrorResponse(w, r, STSErrAccessDenied, fmt.Errorf("trust policy denies access"))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// If RoleArn is missing, default to the caller's identity (User Context)
|
||||
// This allows the user to "assume" a session for themselves, inheriting their own permissions.
|
||||
roleArn = identity.PrincipalArn
|
||||
glog.V(2).Infof("AssumeRole: no RoleArn provided, defaulting to caller identity: %s", roleArn)
|
||||
|
||||
// We still enforce a global "sts:AssumeRole" check, similar to how we'd check if they can assume *any* role.
|
||||
// However, for self-assumption, this might be implicit.
|
||||
// For safety/consistency with previous logic, we keep the check but strictly it might not be required by AWS for GetSessionToken.
|
||||
// But since this IS AssumeRole, let's keep it.
|
||||
// Admin/Global check when no specific role is requested
|
||||
if authErr := h.iam.VerifyActionPermission(r, identity, Action("sts:AssumeRole"), "", ""); authErr != s3err.ErrNone {
|
||||
glog.Warningf("AssumeRole: caller %s attempted to assume role without RoleArn and lacks global sts:AssumeRole permission", identity.Name)
|
||||
h.writeSTSErrorResponse(w, r, STSErrAccessDenied, fmt.Errorf("access denied"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that the target role trusts the caller (Trust Policy)
|
||||
// This ensures the role's trust policy explicitly allows the principal to assume it
|
||||
if err := h.iam.ValidateTrustPolicyForPrincipal(r.Context(), roleArn, identity.PrincipalArn); err != nil {
|
||||
glog.V(2).Infof("AssumeRole: trust policy validation failed for %s to assume %s: %v", identity.Name, roleArn, err)
|
||||
h.writeSTSErrorResponse(w, r, STSErrAccessDenied, fmt.Errorf("trust policy denies access"))
|
||||
return
|
||||
}
|
||||
|
||||
// Parse optional inline session policy for downscoping
|
||||
sessionPolicyJSON, err := sts.NormalizeSessionPolicy(r.FormValue("Policy"))
|
||||
if err != nil {
|
||||
h.writeSTSErrorResponse(w, r, STSErrMalformedPolicyDocument,
|
||||
@@ -313,8 +329,19 @@ func (h *STSHandlers) handleAssumeRole(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare custom claims for the session
|
||||
var modifyClaims func(claims *sts.STSSessionClaims)
|
||||
if identity.isAdmin() {
|
||||
modifyClaims = func(claims *sts.STSSessionClaims) {
|
||||
if claims.RequestContext == nil {
|
||||
claims.RequestContext = make(map[string]interface{})
|
||||
}
|
||||
claims.RequestContext["is_admin"] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Generate common STS components
|
||||
stsCreds, assumedUser, err := h.prepareSTSCredentials(roleArn, roleSessionName, durationSeconds, sessionPolicyJSON, nil)
|
||||
stsCreds, assumedUser, err := h.prepareSTSCredentials(roleArn, roleSessionName, durationSeconds, sessionPolicyJSON, modifyClaims)
|
||||
if err != nil {
|
||||
h.writeSTSErrorResponse(w, r, STSErrInternalError, err)
|
||||
return
|
||||
@@ -492,7 +519,12 @@ func (h *STSHandlers) prepareSTSCredentials(roleArn, roleSessionName string,
|
||||
expiration := time.Now().Add(duration)
|
||||
|
||||
// Extract role name from ARN for proper response formatting
|
||||
roleName := utils.ExtractRoleNameFromArn(roleArn)
|
||||
roleName := utils.ExtractRoleNameFromPrincipal(roleArn)
|
||||
if roleName == "" {
|
||||
// Try to extract user name if it's a user ARN (for "User Context" assumption)
|
||||
roleName = utils.ExtractUserNameFromPrincipal(roleArn)
|
||||
}
|
||||
|
||||
if roleName == "" {
|
||||
roleName = roleArn // Fallback to full ARN if extraction fails
|
||||
}
|
||||
@@ -502,12 +534,19 @@ func (h *STSHandlers) prepareSTSCredentials(roleArn, roleSessionName string,
|
||||
// Construct AssumedRoleUser ARN - this will be used as the principal for the vended token
|
||||
assumedRoleArn := fmt.Sprintf("arn:aws:sts::%s:assumed-role/%s/%s", accountID, roleName, roleSessionName)
|
||||
|
||||
// Use assumedRoleArn as RoleArn in claims if original RoleArn is empty
|
||||
// This ensures STSSessionClaims.IsValid() passes (it requires non-empty RoleArn)
|
||||
effectiveRoleArn := roleArn
|
||||
if effectiveRoleArn == "" {
|
||||
effectiveRoleArn = assumedRoleArn
|
||||
}
|
||||
|
||||
// Create session claims with role information
|
||||
// SECURITY: Use the assumedRoleArn as the principal in the token.
|
||||
// This ensures that subsequent requests using this token are correctly identified as the assumed role.
|
||||
claims := sts.NewSTSSessionClaims(sessionId, h.stsService.Config.Issuer, expiration).
|
||||
WithSessionName(roleSessionName).
|
||||
WithRoleInfo(roleArn, fmt.Sprintf("%s:%s", roleName, roleSessionName), assumedRoleArn)
|
||||
WithRoleInfo(effectiveRoleArn, fmt.Sprintf("%s:%s", roleName, roleSessionName), assumedRoleArn)
|
||||
|
||||
if sessionPolicy != "" {
|
||||
claims.WithSessionPolicy(sessionPolicy)
|
||||
|
||||
Reference in New Issue
Block a user