diff --git a/weed/s3api/s3_end_to_end_test.go b/weed/s3api/s3_end_to_end_test.go index d6a044a44..a30efa497 100644 --- a/weed/s3api/s3_end_to_end_test.go +++ b/weed/s3api/s3_end_to_end_test.go @@ -319,8 +319,23 @@ func setupCompleteS3IAMSystem(t *testing.T) (http.Handler, *integration.IAMManag return } - // Test authorization - authErrCode := s3IAMIntegration.AuthorizeAction(r.Context(), identity, Action("Read"), "test-bucket", "test-object", r) + // Map HTTP method to S3 action for more realistic testing + var action Action + switch r.Method { + case "GET": + action = Action("s3:GetObject") + case "PUT": + action = Action("s3:PutObject") + case "DELETE": + action = Action("s3:DeleteObject") + case "HEAD": + action = Action("s3:HeadObject") + default: + action = Action("s3:GetObject") // Default fallback + } + + // Test authorization with appropriate action + authErrCode := s3IAMIntegration.AuthorizeAction(r.Context(), identity, action, "test-bucket", "test-object", r) if authErrCode != s3err.ErrNone { w.WriteHeader(http.StatusForbidden) w.Write([]byte("Authorization failed")) @@ -329,7 +344,7 @@ func setupCompleteS3IAMSystem(t *testing.T) (http.Handler, *integration.IAMManag w.WriteHeader(http.StatusOK) w.Write([]byte("Success")) - }).Methods("GET") + }).Methods("GET", "PUT", "DELETE", "HEAD") return router, iamManager } @@ -376,6 +391,12 @@ func setupS3ReadOnlyRole(ctx context.Context, manager *integration.IAMManager) { "arn:seaweed:s3:::*/*", }, }, + { + Sid: "AllowSTSSessionValidation", + Effect: "Allow", + Action: []string{"sts:ValidateSession"}, + Resource: []string{"*"}, + }, }, } @@ -414,6 +435,12 @@ func setupS3AdminRole(ctx context.Context, manager *integration.IAMManager) { "arn:seaweed:s3:::*/*", }, }, + { + Sid: "AllowSTSSessionValidation", + Effect: "Allow", + Action: []string{"sts:ValidateSession"}, + Resource: []string{"*"}, + }, }, } @@ -452,6 +479,12 @@ func setupS3WriteRole(ctx context.Context, manager *integration.IAMManager) { "arn:seaweed:s3:::*/*", }, }, + { + Sid: "AllowSTSSessionValidation", + Effect: "Allow", + Action: []string{"sts:ValidateSession"}, + Resource: []string{"*"}, + }, }, } @@ -495,6 +528,12 @@ func setupS3IPRestrictedRole(ctx context.Context, manager *integration.IAMManage }, }, }, + { + Sid: "AllowSTSSessionValidation", + Effect: "Allow", + Action: []string{"sts:ValidateSession"}, + Resource: []string{"*"}, + }, }, } @@ -520,8 +559,8 @@ func setupS3IPRestrictedRole(ctx context.Context, manager *integration.IAMManage } func executeS3OperationWithJWT(t *testing.T, s3Server http.Handler, operation S3Operation, jwtToken string) bool { - // Use our simplified test endpoint for IAM validation - req := httptest.NewRequest("GET", "/test-auth", nil) + // Use our simplified test endpoint for IAM validation with the correct HTTP method + req := httptest.NewRequest(operation.Method, "/test-auth", nil) req.Header.Set("Authorization", "Bearer "+jwtToken) req.Header.Set("Content-Type", "application/octet-stream") diff --git a/weed/s3api/s3_iam_middleware.go b/weed/s3api/s3_iam_middleware.go index 2334e06f1..71a808fb6 100644 --- a/weed/s3api/s3_iam_middleware.go +++ b/weed/s3api/s3_iam_middleware.go @@ -62,18 +62,19 @@ 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(3).Infof("Failed to parse JWT token: %v", err) + glog.V(0).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_name"].(string) + roleName, ok := tokenClaims["role"].(string) if !ok || roleName == "" { - glog.V(3).Info("No role_name found in JWT token") + glog.V(0).Info("No role found in JWT token") return nil, s3err.ErrAccessDenied } - sessionName, ok := tokenClaims["session_name"].(string) + sessionName, ok := tokenClaims["snam"].(string) if !ok || sessionName == "" { sessionName = "jwt-session" // Default fallback } @@ -83,8 +84,17 @@ func (s3iam *S3IAMIntegration) AuthenticateJWT(ctx context.Context, r *http.Requ subject = "jwt-user" // Default fallback } - // Build principal ARN from token claims - principalArn := fmt.Sprintf("arn:seaweed:sts::assumed-role/%s/%s", roleName, sessionName) + // Use the principal ARN directly from token claims, or build it if not available + principalArn, ok := tokenClaims["principal"].(string) + if !ok || principalArn == "" { + // Fallback: extract role name from role ARN and build principal ARN + roleNameOnly := roleName + if strings.Contains(roleName, "/") { + parts := strings.Split(roleName, "/") + roleNameOnly = parts[len(parts)-1] + } + principalArn = fmt.Sprintf("arn:seaweed:sts::assumed-role/%s/%s", roleNameOnly, sessionName) + } // Validate the session using our IAM system testRequest := &integration.ActionRequest{