From 5dcc2f864256d8d68fade37e24b6fe2c205e8295 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Mon, 12 Jan 2026 01:35:04 -0800 Subject: [PATCH] feat(sts): implement strict trust policy validation for AssumeRole --- weed/iam/integration/iam_manager_trust.go | 43 +++++++++++++++++++++++ weed/s3api/auth_credentials_trust.go | 15 ++++++++ weed/s3api/s3_iam_middleware.go | 9 +++++ weed/s3api/s3api_sts.go | 27 +++++++++++--- 4 files changed, 89 insertions(+), 5 deletions(-) create mode 100644 weed/iam/integration/iam_manager_trust.go create mode 100644 weed/s3api/auth_credentials_trust.go diff --git a/weed/iam/integration/iam_manager_trust.go b/weed/iam/integration/iam_manager_trust.go new file mode 100644 index 000000000..7cffa5749 --- /dev/null +++ b/weed/iam/integration/iam_manager_trust.go @@ -0,0 +1,43 @@ +package integration + +import ( + "context" + "fmt" + + "github.com/seaweedfs/seaweedfs/weed/iam/policy" + "github.com/seaweedfs/seaweedfs/weed/iam/utils" +) + +// ValidateTrustPolicyForPrincipal validates if a principal is allowed to assume a role +func (m *IAMManager) ValidateTrustPolicyForPrincipal(ctx context.Context, roleArn, principalArn string) error { + if !m.initialized { + return fmt.Errorf("IAM manager not initialized") + } + + // Extract role name from ARN + roleName := utils.ExtractRoleNameFromArn(roleArn) + + // Get role definition + roleDef, err := m.roleStore.GetRole(ctx, m.getFilerAddress(), roleName) + if err != nil { + return fmt.Errorf("role not found: %s", roleName) + } + + if roleDef.TrustPolicy == nil { + return fmt.Errorf("role has no trust policy") + } + + // Create evaluation context + evalCtx := &policy.EvaluationContext{ + Principal: principalArn, + Action: "sts:AssumeRole", + Resource: roleArn, + } + + // Evaluate the trust policy + if !m.evaluateTrustPolicy(roleDef.TrustPolicy, evalCtx) { + return fmt.Errorf("trust policy denies access to principal: %s", principalArn) + } + + return nil +} diff --git a/weed/s3api/auth_credentials_trust.go b/weed/s3api/auth_credentials_trust.go new file mode 100644 index 000000000..e37cda328 --- /dev/null +++ b/weed/s3api/auth_credentials_trust.go @@ -0,0 +1,15 @@ +package s3api + +import ( + "context" + "fmt" +) + +// ValidateTrustPolicyForPrincipal validates if a principal is allowed to assume a role +// Delegates to the IAM integration if available +func (iam *IdentityAccessManagement) ValidateTrustPolicyForPrincipal(ctx context.Context, roleArn, principalArn string) error { + if iam.iamIntegration != nil { + return iam.iamIntegration.ValidateTrustPolicyForPrincipal(ctx, roleArn, principalArn) + } + return fmt.Errorf("IAM integration not available") +} diff --git a/weed/s3api/s3_iam_middleware.go b/weed/s3api/s3_iam_middleware.go index 3548b58a7..5898617b0 100644 --- a/weed/s3api/s3_iam_middleware.go +++ b/weed/s3api/s3_iam_middleware.go @@ -23,6 +23,7 @@ type IAMIntegration interface { AuthenticateJWT(ctx context.Context, r *http.Request) (*IAMIdentity, s3err.ErrorCode) AuthorizeAction(ctx context.Context, identity *IAMIdentity, action Action, bucket string, objectKey string, r *http.Request) s3err.ErrorCode ValidateSessionToken(ctx context.Context, token string) (*sts.SessionInfo, error) + ValidateTrustPolicyForPrincipal(ctx context.Context, roleArn, principalArn string) error } // S3IAMIntegration provides IAM integration for S3 API @@ -224,6 +225,14 @@ func (s3iam *S3IAMIntegration) AuthorizeAction(ctx context.Context, identity *IA return s3err.ErrNone } +// ValidateTrustPolicyForPrincipal delegates to IAMManager to validate trust policy +func (s3iam *S3IAMIntegration) ValidateTrustPolicyForPrincipal(ctx context.Context, roleArn, principalArn string) error { + if s3iam.iamManager == nil { + return fmt.Errorf("IAM manager not available") + } + return s3iam.iamManager.ValidateTrustPolicyForPrincipal(ctx, roleArn, principalArn) +} + // IAMIdentity represents an authenticated identity with session information type IAMIdentity struct { Name string diff --git a/weed/s3api/s3api_sts.go b/weed/s3api/s3api_sts.go index 4ae8a24c7..d19b220d1 100644 --- a/weed/s3api/s3api_sts.go +++ b/weed/s3api/s3api_sts.go @@ -274,6 +274,14 @@ func (h *STSHandlers) handleAssumeRole(w http.ResponseWriter, r *http.Request) { 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 + } + // Generate common STS components stsCreds, assumedUser, err := h.prepareSTSCredentials(roleArn, roleSessionName, identity.PrincipalArn, durationSeconds, nil) if err != nil { @@ -342,14 +350,21 @@ func (h *STSHandlers) handleAssumeRoleWithLDAPIdentity(w http.ResponseWriter, r // Find an LDAP provider from the registered providers var ldapProvider *ldap.LDAPProvider + ldapProvidersFound := 0 for _, provider := range h.stsService.GetProviders() { // Check if this is an LDAP provider by type assertion if p, ok := provider.(*ldap.LDAPProvider); ok { - ldapProvider = p - break + if ldapProvider == nil { + ldapProvider = p + } + ldapProvidersFound++ } } + if ldapProvidersFound > 1 { + glog.Warningf("Multiple LDAP providers found (%d). Using the first one found (non-deterministic).", ldapProvidersFound) + } + if ldapProvider == nil { glog.V(2).Infof("AssumeRoleWithLDAPIdentity: no LDAP provider configured") h.writeSTSErrorResponse(w, r, STSErrAccessDenied, @@ -390,9 +405,11 @@ func (h *STSHandlers) handleAssumeRoleWithLDAPIdentity(w http.ResponseWriter, r PrincipalArn: fmt.Sprintf("arn:aws:iam::%s:user/%s", accountId, identity.UserID), } - if authErr := h.iam.VerifyActionPermission(r, ldapUserIdentity, Action("sts:AssumeRole"), "", roleArn); authErr != s3err.ErrNone { - glog.V(2).Infof("AssumeRoleWithLDAPIdentity: authorization failed for %s to assume %s: %v", ldapUsername, roleArn, authErr) - h.writeSTSErrorResponse(w, r, STSErrAccessDenied, fmt.Errorf("access denied")) + // Verify that the identity is allowed to assume the role by checking the Trust Policy + // The LDAP user doesn't have identity policies, so we strictly check if the Role trusts this principal. + if err := h.iam.ValidateTrustPolicyForPrincipal(r.Context(), roleArn, ldapUserIdentity.PrincipalArn); err != nil { + glog.V(2).Infof("AssumeRoleWithLDAPIdentity: trust policy validation failed for %s to assume %s: %v", ldapUsername, roleArn, err) + h.writeSTSErrorResponse(w, r, STSErrAccessDenied, fmt.Errorf("trust policy denies access")) return }