You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							278 lines
						
					
					
						
							9.5 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							278 lines
						
					
					
						
							9.5 KiB
						
					
					
				| package sts | |
| 
 | |
| import ( | |
| 	"context" | |
| 	"testing" | |
| 	"time" | |
| 
 | |
| 	"github.com/golang-jwt/jwt/v5" | |
| 	"github.com/stretchr/testify/assert" | |
| 	"github.com/stretchr/testify/require" | |
| ) | |
| 
 | |
| // createSessionPolicyTestJWT creates a test JWT token for session policy tests | |
| func createSessionPolicyTestJWT(t *testing.T, issuer, subject string) string { | |
| 	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ | |
| 		"iss": issuer, | |
| 		"sub": subject, | |
| 		"aud": "test-client", | |
| 		"exp": time.Now().Add(time.Hour).Unix(), | |
| 		"iat": time.Now().Unix(), | |
| 	}) | |
| 
 | |
| 	tokenString, err := token.SignedString([]byte("test-signing-key")) | |
| 	require.NoError(t, err) | |
| 	return tokenString | |
| } | |
| 
 | |
| // TestAssumeRoleWithWebIdentity_SessionPolicy tests the handling of the Policy field | |
| // in AssumeRoleWithWebIdentityRequest to ensure users are properly informed that | |
| // session policies are not currently supported | |
| func TestAssumeRoleWithWebIdentity_SessionPolicy(t *testing.T) { | |
| 	service := setupTestSTSService(t) | |
| 
 | |
| 	t.Run("should_reject_request_with_session_policy", func(t *testing.T) { | |
| 		ctx := context.Background() | |
| 
 | |
| 		// Create a request with a session policy | |
| 		sessionPolicy := `{ | |
| 			"Version": "2012-10-17", | |
| 			"Statement": [{ | |
| 				"Effect": "Allow", | |
| 				"Action": "s3:GetObject", | |
| 				"Resource": "arn:aws:s3:::example-bucket/*" | |
| 			}] | |
| 		}` | |
| 
 | |
| 		testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user") | |
| 
 | |
| 		request := &AssumeRoleWithWebIdentityRequest{ | |
| 			RoleArn:          "arn:seaweed:iam::role/TestRole", | |
| 			WebIdentityToken: testToken, | |
| 			RoleSessionName:  "test-session", | |
| 			DurationSeconds:  nil,            // Use default | |
| 			Policy:           &sessionPolicy, // ← Session policy provided | |
| 		} | |
| 
 | |
| 		// Should return an error indicating session policies are not supported | |
| 		response, err := service.AssumeRoleWithWebIdentity(ctx, request) | |
| 
 | |
| 		// Verify the error | |
| 		assert.Error(t, err) | |
| 		assert.Nil(t, response) | |
| 		assert.Contains(t, err.Error(), "session policies are not currently supported") | |
| 		assert.Contains(t, err.Error(), "Policy parameter must be omitted") | |
| 	}) | |
| 
 | |
| 	t.Run("should_succeed_without_session_policy", func(t *testing.T) { | |
| 		ctx := context.Background() | |
| 		testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user") | |
| 
 | |
| 		request := &AssumeRoleWithWebIdentityRequest{ | |
| 			RoleArn:          "arn:seaweed:iam::role/TestRole", | |
| 			WebIdentityToken: testToken, | |
| 			RoleSessionName:  "test-session", | |
| 			DurationSeconds:  nil, // Use default | |
| 			Policy:           nil, // ← No session policy | |
| 		} | |
| 
 | |
| 		// Should succeed without session policy | |
| 		response, err := service.AssumeRoleWithWebIdentity(ctx, request) | |
| 
 | |
| 		// Verify success | |
| 		require.NoError(t, err) | |
| 		require.NotNil(t, response) | |
| 		assert.NotNil(t, response.Credentials) | |
| 		assert.NotEmpty(t, response.Credentials.AccessKeyId) | |
| 		assert.NotEmpty(t, response.Credentials.SecretAccessKey) | |
| 		assert.NotEmpty(t, response.Credentials.SessionToken) | |
| 	}) | |
| 
 | |
| 	t.Run("should_succeed_with_empty_policy_pointer", func(t *testing.T) { | |
| 		ctx := context.Background() | |
| 		testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user") | |
| 
 | |
| 		request := &AssumeRoleWithWebIdentityRequest{ | |
| 			RoleArn:          "arn:seaweed:iam::role/TestRole", | |
| 			WebIdentityToken: testToken, | |
| 			RoleSessionName:  "test-session", | |
| 			Policy:           nil, // ← Explicitly nil | |
| 		} | |
| 
 | |
| 		// Should succeed with nil policy pointer | |
| 		response, err := service.AssumeRoleWithWebIdentity(ctx, request) | |
| 
 | |
| 		require.NoError(t, err) | |
| 		require.NotNil(t, response) | |
| 		assert.NotNil(t, response.Credentials) | |
| 	}) | |
| 
 | |
| 	t.Run("should_reject_empty_string_policy", func(t *testing.T) { | |
| 		ctx := context.Background() | |
| 
 | |
| 		emptyPolicy := "" // Empty string, but still a non-nil pointer | |
|  | |
| 		request := &AssumeRoleWithWebIdentityRequest{ | |
| 			RoleArn:          "arn:seaweed:iam::role/TestRole", | |
| 			WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"), | |
| 			RoleSessionName:  "test-session", | |
| 			Policy:           &emptyPolicy, // ← Non-nil pointer to empty string | |
| 		} | |
| 
 | |
| 		// Should still reject because pointer is not nil | |
| 		response, err := service.AssumeRoleWithWebIdentity(ctx, request) | |
| 
 | |
| 		assert.Error(t, err) | |
| 		assert.Nil(t, response) | |
| 		assert.Contains(t, err.Error(), "session policies are not currently supported") | |
| 	}) | |
| } | |
| 
 | |
| // TestAssumeRoleWithWebIdentity_SessionPolicy_ErrorMessage tests that the error message | |
| // is clear and helps users understand what they need to do | |
| func TestAssumeRoleWithWebIdentity_SessionPolicy_ErrorMessage(t *testing.T) { | |
| 	service := setupTestSTSService(t) | |
| 
 | |
| 	ctx := context.Background() | |
| 	complexPolicy := `{ | |
| 		"Version": "2012-10-17", | |
| 		"Statement": [ | |
| 			{ | |
| 				"Sid": "AllowS3Access", | |
| 				"Effect": "Allow", | |
| 				"Action": [ | |
| 					"s3:GetObject", | |
| 					"s3:PutObject" | |
| 				], | |
| 				"Resource": [ | |
| 					"arn:aws:s3:::my-bucket/*", | |
| 					"arn:aws:s3:::my-bucket" | |
| 				], | |
| 				"Condition": { | |
| 					"StringEquals": { | |
| 						"s3:prefix": ["documents/", "images/"] | |
| 					} | |
| 				} | |
| 			} | |
| 		] | |
| 	}` | |
| 
 | |
| 	testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user") | |
| 
 | |
| 	request := &AssumeRoleWithWebIdentityRequest{ | |
| 		RoleArn:          "arn:seaweed:iam::role/TestRole", | |
| 		WebIdentityToken: testToken, | |
| 		RoleSessionName:  "test-session-with-complex-policy", | |
| 		Policy:           &complexPolicy, | |
| 	} | |
| 
 | |
| 	response, err := service.AssumeRoleWithWebIdentity(ctx, request) | |
| 
 | |
| 	// Verify error details | |
| 	require.Error(t, err) | |
| 	assert.Nil(t, response) | |
| 
 | |
| 	errorMsg := err.Error() | |
| 
 | |
| 	// The error should be clear and actionable | |
| 	assert.Contains(t, errorMsg, "session policies are not currently supported", | |
| 		"Error should explain that session policies aren't supported") | |
| 	assert.Contains(t, errorMsg, "Policy parameter must be omitted", | |
| 		"Error should specify what action the user needs to take") | |
| 
 | |
| 	// Should NOT contain internal implementation details | |
| 	assert.NotContains(t, errorMsg, "nil pointer", | |
| 		"Error should not expose internal implementation details") | |
| 	assert.NotContains(t, errorMsg, "struct field", | |
| 		"Error should not expose internal struct details") | |
| } | |
| 
 | |
| // Test edge case scenarios for the Policy field handling | |
| func TestAssumeRoleWithWebIdentity_SessionPolicy_EdgeCases(t *testing.T) { | |
| 	service := setupTestSTSService(t) | |
| 
 | |
| 	t.Run("malformed_json_policy_still_rejected", func(t *testing.T) { | |
| 		ctx := context.Background() | |
| 		malformedPolicy := `{"Version": "2012-10-17", "Statement": [` // Incomplete JSON | |
|  | |
| 		request := &AssumeRoleWithWebIdentityRequest{ | |
| 			RoleArn:          "arn:seaweed:iam::role/TestRole", | |
| 			WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"), | |
| 			RoleSessionName:  "test-session", | |
| 			Policy:           &malformedPolicy, | |
| 		} | |
| 
 | |
| 		// Should reject before even parsing the policy JSON | |
| 		response, err := service.AssumeRoleWithWebIdentity(ctx, request) | |
| 
 | |
| 		assert.Error(t, err) | |
| 		assert.Nil(t, response) | |
| 		assert.Contains(t, err.Error(), "session policies are not currently supported") | |
| 	}) | |
| 
 | |
| 	t.Run("policy_with_whitespace_still_rejected", func(t *testing.T) { | |
| 		ctx := context.Background() | |
| 		whitespacePolicy := "   \t\n   " // Only whitespace | |
|  | |
| 		request := &AssumeRoleWithWebIdentityRequest{ | |
| 			RoleArn:          "arn:seaweed:iam::role/TestRole", | |
| 			WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"), | |
| 			RoleSessionName:  "test-session", | |
| 			Policy:           &whitespacePolicy, | |
| 		} | |
| 
 | |
| 		// Should reject any non-nil policy, even whitespace | |
| 		response, err := service.AssumeRoleWithWebIdentity(ctx, request) | |
| 
 | |
| 		assert.Error(t, err) | |
| 		assert.Nil(t, response) | |
| 		assert.Contains(t, err.Error(), "session policies are not currently supported") | |
| 	}) | |
| } | |
| 
 | |
| // TestAssumeRoleWithWebIdentity_PolicyFieldDocumentation verifies that the struct | |
| // field is properly documented to help developers understand the limitation | |
| func TestAssumeRoleWithWebIdentity_PolicyFieldDocumentation(t *testing.T) { | |
| 	// This test documents the current behavior and ensures the struct field | |
| 	// exists with proper typing | |
| 	request := &AssumeRoleWithWebIdentityRequest{} | |
| 
 | |
| 	// Verify the Policy field exists and has the correct type | |
| 	assert.IsType(t, (*string)(nil), request.Policy, | |
| 		"Policy field should be *string type for optional JSON policy") | |
| 
 | |
| 	// Verify initial value is nil (no policy by default) | |
| 	assert.Nil(t, request.Policy, | |
| 		"Policy field should default to nil (no session policy)") | |
| 
 | |
| 	// Test that we can set it to a string pointer (even though it will be rejected) | |
| 	policyValue := `{"Version": "2012-10-17"}` | |
| 	request.Policy = &policyValue | |
| 	assert.NotNil(t, request.Policy, "Should be able to assign policy value") | |
| 	assert.Equal(t, policyValue, *request.Policy, "Policy value should be preserved") | |
| } | |
| 
 | |
| // TestAssumeRoleWithCredentials_NoSessionPolicySupport verifies that | |
| // AssumeRoleWithCredentialsRequest doesn't have a Policy field, which is correct | |
| // since credential-based role assumption typically doesn't support session policies | |
| func TestAssumeRoleWithCredentials_NoSessionPolicySupport(t *testing.T) { | |
| 	// Verify that AssumeRoleWithCredentialsRequest doesn't have a Policy field | |
| 	// This is the expected behavior since session policies are typically only | |
| 	// supported with web identity (OIDC/SAML) flows in AWS STS | |
| 	request := &AssumeRoleWithCredentialsRequest{ | |
| 		RoleArn:         "arn:seaweed:iam::role/TestRole", | |
| 		Username:        "testuser", | |
| 		Password:        "testpass", | |
| 		RoleSessionName: "test-session", | |
| 		ProviderName:    "ldap", | |
| 	} | |
| 
 | |
| 	// The struct should compile and work without a Policy field | |
| 	assert.NotNil(t, request) | |
| 	assert.Equal(t, "arn:seaweed:iam::role/TestRole", request.RoleArn) | |
| 	assert.Equal(t, "testuser", request.Username) | |
| 
 | |
| 	// This documents that credential-based assume role does NOT support session policies | |
| 	// which matches AWS STS behavior where session policies are primarily for | |
| 	// web identity (OIDC/SAML) and federation scenarios | |
| }
 |