diff --git a/weed/iam/integration/iam_manager.go b/weed/iam/integration/iam_manager.go index c428c0b83..fab819215 100644 --- a/weed/iam/integration/iam_manager.go +++ b/weed/iam/integration/iam_manager.go @@ -228,38 +228,27 @@ func (m *IAMManager) AssumeRoleWithCredentials(ctx context.Context, request *sts // IsActionAllowed checks if a principal is allowed to perform an action on a resource func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest) (bool, error) { - glog.V(0).Infof("IsActionAllowed: starting validation for principal=%s, action=%s", request.Principal, request.Action) - if !m.initialized { - glog.V(0).Info("IsActionAllowed: IAM manager not initialized") return false, fmt.Errorf("IAM manager not initialized") } // Validate session token first - glog.V(0).Infof("IsActionAllowed: validating session token (length=%d)", len(request.SessionToken)) _, err := m.stsService.ValidateSessionToken(ctx, request.SessionToken) if err != nil { - glog.V(0).Infof("IsActionAllowed: session token validation failed: %v", err) return false, fmt.Errorf("invalid session: %w", err) } - glog.V(0).Info("IsActionAllowed: session token validation successful") // Extract role name from principal ARN roleName := extractRoleNameFromPrincipal(request.Principal) - glog.V(0).Infof("IsActionAllowed: extracted role name=%s from principal=%s", roleName, request.Principal) if roleName == "" { - glog.V(0).Infof("IsActionAllowed: could not extract role from principal: %s", request.Principal) return false, fmt.Errorf("could not extract role from principal: %s", request.Principal) } // Get role definition - glog.V(0).Infof("IsActionAllowed: looking up role definition for role=%s", roleName) roleDef, err := m.roleStore.GetRole(ctx, roleName) if err != nil { - glog.V(0).Infof("IsActionAllowed: role lookup failed for role=%s: %v", roleName, err) return false, fmt.Errorf("role not found: %s", roleName) } - glog.V(0).Infof("IsActionAllowed: found role definition with %d attached policies", len(roleDef.AttachedPolicies)) // Create evaluation context evalCtx := &policy.EvaluationContext{ @@ -270,14 +259,11 @@ func (m *IAMManager) IsActionAllowed(ctx context.Context, request *ActionRequest } // Evaluate policies attached to the role - glog.V(0).Infof("IsActionAllowed: evaluating policies: %v", roleDef.AttachedPolicies) result, err := m.policyEngine.Evaluate(ctx, evalCtx, roleDef.AttachedPolicies) if err != nil { - glog.V(0).Infof("IsActionAllowed: policy evaluation failed: %v", err) return false, fmt.Errorf("policy evaluation failed: %w", err) } - glog.V(0).Infof("IsActionAllowed: policy evaluation result - effect=%s, allowed=%t", result.Effect, result.Effect == policy.EffectAllow) return result.Effect == policy.EffectAllow, nil } @@ -427,6 +413,11 @@ func (m *IAMManager) ExpireSessionForTesting(ctx context.Context, sessionToken s return m.stsService.ExpireSessionForTesting(ctx, sessionToken) } +// GetSTSService returns the STS service instance +func (m *IAMManager) GetSTSService() *sts.STSService { + return m.stsService +} + // parseJWTTokenForTrustPolicy parses a JWT token to extract claims for trust policy evaluation func parseJWTTokenForTrustPolicy(tokenString string) (map[string]interface{}, error) { // Simple JWT parsing without verification (for trust policy context only) diff --git a/weed/iam/sts/token_utils.go b/weed/iam/sts/token_utils.go index 13e4819a5..966160b22 100644 --- a/weed/iam/sts/token_utils.go +++ b/weed/iam/sts/token_utils.go @@ -89,10 +89,7 @@ func (t *TokenGenerator) ValidateSessionToken(tokenString string) (*SessionToken // ValidateJWTWithClaims validates and extracts comprehensive session claims from a JWT token func (t *TokenGenerator) ValidateJWTWithClaims(tokenString string) (*STSSessionClaims, error) { - glog.V(0).Infof("ValidateJWTWithClaims: validating token with length=%d", len(tokenString)) - token, err := jwt.ParseWithClaims(tokenString, &STSSessionClaims{}, func(token *jwt.Token) (interface{}, error) { - glog.V(0).Infof("ValidateJWTWithClaims: signing method=%v", token.Header["alg"]) if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } @@ -100,42 +97,33 @@ func (t *TokenGenerator) ValidateJWTWithClaims(tokenString string) (*STSSessionC }) if err != nil { - glog.V(0).Infof("ValidateJWTWithClaims: token parsing failed: %v", err) return nil, fmt.Errorf(ErrInvalidToken, err) } if !token.Valid { - glog.V(0).Info("ValidateJWTWithClaims: token is not valid") return nil, fmt.Errorf(ErrTokenNotValid) } claims, ok := token.Claims.(*STSSessionClaims) if !ok { - glog.V(0).Infof("ValidateJWTWithClaims: failed to cast claims to STSSessionClaims, got type: %T", token.Claims) return nil, fmt.Errorf(ErrInvalidTokenClaims) } - glog.V(0).Infof("ValidateJWTWithClaims: parsed claims - issuer=%s, sessionId=%s", claims.Issuer, claims.SessionId) - // Validate issuer if claims.Issuer != t.issuer { - glog.V(0).Infof("ValidateJWTWithClaims: issuer mismatch - expected=%s, got=%s", t.issuer, claims.Issuer) return nil, fmt.Errorf(ErrInvalidIssuer) } // Validate that required fields are present if claims.SessionId == "" { - glog.V(0).Info("ValidateJWTWithClaims: missing session ID") return nil, fmt.Errorf(ErrMissingSessionID) } // Additional validation using the claims' own validation method if !claims.IsValid() { - glog.V(0).Info("ValidateJWTWithClaims: claims validation failed") return nil, fmt.Errorf(ErrTokenNotValid) } - glog.V(0).Info("ValidateJWTWithClaims: validation successful") return claims, nil } diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go index 434df92d0..fff989c3e 100644 --- a/weed/s3api/auth_credentials.go +++ b/weed/s3api/auth_credentials.go @@ -442,13 +442,15 @@ func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action) glog.V(3).Infof("unsigned streaming upload") return identity, s3err.ErrNone case authTypeJWT: - glog.V(0).Infof("jwt auth type detected, iamIntegration != nil? %t", iam.iamIntegration != nil) + glog.V(3).Infof("jwt auth type detected, iamIntegration != nil? %t", iam.iamIntegration != nil) r.Header.Set(s3_constants.AmzAuthType, "Jwt") if iam.iamIntegration != nil { - return iam.authenticateJWTWithIAM(r) + identity, s3Err = iam.authenticateJWTWithIAM(r) + authType = "Jwt" + } else { + glog.V(0).Infof("IAM integration is nil, returning ErrNotImplemented") + return identity, s3err.ErrNotImplemented } - glog.V(0).Infof("IAM integration is nil, returning ErrNotImplemented") - return identity, s3err.ErrNotImplemented case authTypeAnonymous: authType = "Anonymous" if identity, found = iam.lookupAnonymous(); !found { @@ -487,15 +489,12 @@ func (iam *IdentityAccessManagement) authRequest(r *http.Request, action Action) } else { // Use enhanced authorization if IAM integration is available sessionToken := r.Header.Get("X-SeaweedFS-Session-Token") - glog.V(0).Infof("Authorization check: iamIntegration != nil? %t, sessionToken != \"\"? %t, sessionToken=%s", iam.iamIntegration != nil, sessionToken != "", sessionToken) if iam.iamIntegration != nil && sessionToken != "" { - glog.V(0).Infof("Using IAM authorization for action=%s, bucket=%s, object=%s", action, bucket, object) if errCode := iam.authorizeWithIAM(r, identity, action, bucket, object); errCode != s3err.ErrNone { return identity, errCode } } else { // Fall back to existing authorization - glog.V(0).Infof("Using fallback authorization for action=%s, bucket=%s, object=%s", action, bucket, object) if !identity.canDo(action, bucket, object) { return identity, s3err.ErrAccessDenied } @@ -612,10 +611,8 @@ func (iam *IdentityAccessManagement) SetIAMIntegration(integration *S3IAMIntegra func (iam *IdentityAccessManagement) authenticateJWTWithIAM(r *http.Request) (*Identity, s3err.ErrorCode) { ctx := r.Context() - glog.V(0).Infof("authenticateJWTWithIAM: starting JWT authentication") // Use IAM integration to authenticate JWT iamIdentity, errCode := iam.iamIntegration.AuthenticateJWT(ctx, r) - glog.V(0).Infof("authenticateJWTWithIAM: AuthenticateJWT returned errCode=%v", errCode) if errCode != s3err.ErrNone { return nil, errCode } @@ -631,7 +628,6 @@ func (iam *IdentityAccessManagement) authenticateJWTWithIAM(r *http.Request) (*I r.Header.Set("X-SeaweedFS-Session-Token", iamIdentity.SessionToken) r.Header.Set("X-SeaweedFS-Principal", iamIdentity.Principal) - glog.V(0).Infof("authenticateJWTWithIAM: successfully authenticated, sessionToken=%s, principal=%s", iamIdentity.SessionToken, iamIdentity.Principal) return identity, s3err.ErrNone } diff --git a/weed/s3api/s3_iam_middleware.go b/weed/s3api/s3_iam_middleware.go index 71a808fb6..59f184d45 100644 --- a/weed/s3api/s3_iam_middleware.go +++ b/weed/s3api/s3_iam_middleware.go @@ -10,6 +10,7 @@ import ( "github.com/golang-jwt/jwt/v5" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/iam/integration" + "github.com/seaweedfs/seaweedfs/weed/iam/sts" "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" ) @@ -17,14 +18,21 @@ import ( // S3IAMIntegration provides IAM integration for S3 API type S3IAMIntegration struct { iamManager *integration.IAMManager + stsService *sts.STSService filerAddress string enabled bool } // NewS3IAMIntegration creates a new S3 IAM integration func NewS3IAMIntegration(iamManager *integration.IAMManager, filerAddress string) *S3IAMIntegration { + var stsService *sts.STSService + if iamManager != nil { + stsService = iamManager.GetSTSService() + } + return &S3IAMIntegration{ iamManager: iamManager, + stsService: stsService, filerAddress: filerAddress, enabled: iamManager != nil, } @@ -32,24 +40,18 @@ func NewS3IAMIntegration(iamManager *integration.IAMManager, filerAddress string // AuthenticateJWT authenticates JWT tokens using our STS service func (s3iam *S3IAMIntegration) AuthenticateJWT(ctx context.Context, r *http.Request) (*IAMIdentity, s3err.ErrorCode) { - glog.V(0).Infof("AuthenticateJWT: enabled=%t", s3iam.enabled) if !s3iam.enabled { - glog.V(0).Info("S3 IAM integration not enabled") return nil, s3err.ErrNotImplemented } // Extract bearer token from Authorization header authHeader := r.Header.Get("Authorization") - glog.V(0).Infof("AuthenticateJWT: authHeader='%s'", authHeader) if !strings.HasPrefix(authHeader, "Bearer ") { - glog.V(0).Info("Invalid JWT authorization header format") return nil, s3err.ErrAccessDenied } sessionToken := strings.TrimPrefix(authHeader, "Bearer ") - glog.V(0).Infof("AuthenticateJWT: sessionToken length=%d", len(sessionToken)) if sessionToken == "" { - glog.V(0).Info("Empty session token") return nil, s3err.ErrAccessDenied } @@ -62,10 +64,9 @@ func (s3iam *S3IAMIntegration) AuthenticateJWT(ctx context.Context, r *http.Requ // Parse JWT token to extract claims tokenClaims, err := parseJWTToken(sessionToken) if err != nil { - glog.V(0).Infof("Failed to parse JWT token: %v", err) + glog.V(3).Infof("Failed to parse JWT token: %v", err) return nil, s3err.ErrAccessDenied } - glog.V(0).Infof("AuthenticateJWT: parsed JWT claims: %+v", tokenClaims) // Extract role information from token claims roleName, ok := tokenClaims["role"].(string) @@ -96,19 +97,12 @@ func (s3iam *S3IAMIntegration) AuthenticateJWT(ctx context.Context, r *http.Requ principalArn = fmt.Sprintf("arn:seaweed:sts::assumed-role/%s/%s", roleNameOnly, sessionName) } - // Validate the session using our IAM system - testRequest := &integration.ActionRequest{ - Principal: principalArn, - Action: "sts:ValidateSession", - Resource: "*", - SessionToken: sessionToken, - } - - glog.V(0).Infof("AuthenticateJWT: calling IsActionAllowed for principal=%s", principalArn) - allowed, err := s3iam.iamManager.IsActionAllowed(ctx, testRequest) - glog.V(0).Infof("AuthenticateJWT: IsActionAllowed returned allowed=%t, err=%v", allowed, err) - if err != nil || !allowed { - glog.V(0).Infof("IAM validation failed for %s: %v", principalArn, err) + // Validate the JWT token directly using STS service (avoid circular dependency) + // Note: We don't call IsActionAllowed here because that would create a circular dependency + // Authentication should only validate the token, authorization happens later + sessionInfo, err := s3iam.stsService.ValidateSessionToken(ctx, sessionToken) + if err != nil { + glog.V(3).Infof("STS session validation failed: %v", err) return nil, s3err.ErrAccessDenied }