aboutsummaryrefslogtreecommitdiff
path: root/weed/iam/integration/iam_integration_test.go
diff options
context:
space:
mode:
authorchrislu <chris.lu@gmail.com>2025-08-30 11:18:03 -0700
committerchrislu <chris.lu@gmail.com>2025-08-30 11:18:03 -0700
commit87021a146027f83f911619f71b9c27bd51e9d55a (patch)
treec7720f1c285683ce19d28931bd7c11b5475a2844 /weed/iam/integration/iam_integration_test.go
parent0748214c8e2f497a84b9392d2d7d4ec976bc84eb (diff)
parent879d512b552d834136cfb746a239e6168e5c4ffb (diff)
downloadseaweedfs-origin/add-ec-vacuum.tar.xz
seaweedfs-origin/add-ec-vacuum.zip
Merge branch 'master' into add-ec-vacuumorigin/add-ec-vacuum
Diffstat (limited to 'weed/iam/integration/iam_integration_test.go')
-rw-r--r--weed/iam/integration/iam_integration_test.go513
1 files changed, 513 insertions, 0 deletions
diff --git a/weed/iam/integration/iam_integration_test.go b/weed/iam/integration/iam_integration_test.go
new file mode 100644
index 000000000..7684656ce
--- /dev/null
+++ b/weed/iam/integration/iam_integration_test.go
@@ -0,0 +1,513 @@
+package integration
+
+import (
+ "context"
+ "testing"
+ "time"
+
+ "github.com/golang-jwt/jwt/v5"
+ "github.com/seaweedfs/seaweedfs/weed/iam/ldap"
+ "github.com/seaweedfs/seaweedfs/weed/iam/oidc"
+ "github.com/seaweedfs/seaweedfs/weed/iam/policy"
+ "github.com/seaweedfs/seaweedfs/weed/iam/sts"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+// TestFullOIDCWorkflow tests the complete OIDC → STS → Policy workflow
+func TestFullOIDCWorkflow(t *testing.T) {
+ // Set up integrated IAM system
+ iamManager := setupIntegratedIAMSystem(t)
+
+ // Create JWT tokens for testing with the correct issuer
+ validJWTToken := createTestJWT(t, "https://test-issuer.com", "test-user-123", "test-signing-key")
+ invalidJWTToken := createTestJWT(t, "https://invalid-issuer.com", "test-user", "wrong-key")
+
+ tests := []struct {
+ name string
+ roleArn string
+ sessionName string
+ webToken string
+ expectedAllow bool
+ testAction string
+ testResource string
+ }{
+ {
+ name: "successful role assumption with policy validation",
+ roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
+ sessionName: "oidc-session",
+ webToken: validJWTToken,
+ expectedAllow: true,
+ testAction: "s3:GetObject",
+ testResource: "arn:seaweed:s3:::test-bucket/file.txt",
+ },
+ {
+ name: "role assumption denied by trust policy",
+ roleArn: "arn:seaweed:iam::role/RestrictedRole",
+ sessionName: "oidc-session",
+ webToken: validJWTToken,
+ expectedAllow: false,
+ },
+ {
+ name: "invalid token rejected",
+ roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
+ sessionName: "oidc-session",
+ webToken: invalidJWTToken,
+ expectedAllow: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+
+ // Step 1: Attempt role assumption
+ assumeRequest := &sts.AssumeRoleWithWebIdentityRequest{
+ RoleArn: tt.roleArn,
+ WebIdentityToken: tt.webToken,
+ RoleSessionName: tt.sessionName,
+ }
+
+ response, err := iamManager.AssumeRoleWithWebIdentity(ctx, assumeRequest)
+
+ if !tt.expectedAllow {
+ assert.Error(t, err)
+ assert.Nil(t, response)
+ return
+ }
+
+ // Should succeed if expectedAllow is true
+ require.NoError(t, err)
+ require.NotNil(t, response)
+ require.NotNil(t, response.Credentials)
+
+ // Step 2: Test policy enforcement with assumed credentials
+ if tt.testAction != "" && tt.testResource != "" {
+ allowed, err := iamManager.IsActionAllowed(ctx, &ActionRequest{
+ Principal: response.AssumedRoleUser.Arn,
+ Action: tt.testAction,
+ Resource: tt.testResource,
+ SessionToken: response.Credentials.SessionToken,
+ })
+
+ require.NoError(t, err)
+ assert.True(t, allowed, "Action should be allowed by role policy")
+ }
+ })
+ }
+}
+
+// TestFullLDAPWorkflow tests the complete LDAP → STS → Policy workflow
+func TestFullLDAPWorkflow(t *testing.T) {
+ iamManager := setupIntegratedIAMSystem(t)
+
+ tests := []struct {
+ name string
+ roleArn string
+ sessionName string
+ username string
+ password string
+ expectedAllow bool
+ testAction string
+ testResource string
+ }{
+ {
+ name: "successful LDAP role assumption",
+ roleArn: "arn:seaweed:iam::role/LDAPUserRole",
+ sessionName: "ldap-session",
+ username: "testuser",
+ password: "testpass",
+ expectedAllow: true,
+ testAction: "filer:CreateEntry",
+ testResource: "arn:seaweed:filer::path/user-docs/*",
+ },
+ {
+ name: "invalid LDAP credentials",
+ roleArn: "arn:seaweed:iam::role/LDAPUserRole",
+ sessionName: "ldap-session",
+ username: "testuser",
+ password: "wrongpass",
+ expectedAllow: false,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ ctx := context.Background()
+
+ // Step 1: Attempt role assumption with LDAP credentials
+ assumeRequest := &sts.AssumeRoleWithCredentialsRequest{
+ RoleArn: tt.roleArn,
+ Username: tt.username,
+ Password: tt.password,
+ RoleSessionName: tt.sessionName,
+ ProviderName: "test-ldap",
+ }
+
+ response, err := iamManager.AssumeRoleWithCredentials(ctx, assumeRequest)
+
+ if !tt.expectedAllow {
+ assert.Error(t, err)
+ assert.Nil(t, response)
+ return
+ }
+
+ require.NoError(t, err)
+ require.NotNil(t, response)
+
+ // Step 2: Test policy enforcement
+ if tt.testAction != "" && tt.testResource != "" {
+ allowed, err := iamManager.IsActionAllowed(ctx, &ActionRequest{
+ Principal: response.AssumedRoleUser.Arn,
+ Action: tt.testAction,
+ Resource: tt.testResource,
+ SessionToken: response.Credentials.SessionToken,
+ })
+
+ require.NoError(t, err)
+ assert.True(t, allowed)
+ }
+ })
+ }
+}
+
+// TestPolicyEnforcement tests policy evaluation for various scenarios
+func TestPolicyEnforcement(t *testing.T) {
+ iamManager := setupIntegratedIAMSystem(t)
+
+ // Create a valid JWT token for testing
+ validJWTToken := createTestJWT(t, "https://test-issuer.com", "test-user-123", "test-signing-key")
+
+ // Create a session for testing
+ ctx := context.Background()
+ assumeRequest := &sts.AssumeRoleWithWebIdentityRequest{
+ RoleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
+ WebIdentityToken: validJWTToken,
+ RoleSessionName: "policy-test-session",
+ }
+
+ response, err := iamManager.AssumeRoleWithWebIdentity(ctx, assumeRequest)
+ require.NoError(t, err)
+
+ sessionToken := response.Credentials.SessionToken
+ principal := response.AssumedRoleUser.Arn
+
+ tests := []struct {
+ name string
+ action string
+ resource string
+ shouldAllow bool
+ reason string
+ }{
+ {
+ name: "allow read access",
+ action: "s3:GetObject",
+ resource: "arn:seaweed:s3:::test-bucket/file.txt",
+ shouldAllow: true,
+ reason: "S3ReadOnlyRole should allow GetObject",
+ },
+ {
+ name: "allow list bucket",
+ action: "s3:ListBucket",
+ resource: "arn:seaweed:s3:::test-bucket",
+ shouldAllow: true,
+ reason: "S3ReadOnlyRole should allow ListBucket",
+ },
+ {
+ name: "deny write access",
+ action: "s3:PutObject",
+ resource: "arn:seaweed:s3:::test-bucket/newfile.txt",
+ shouldAllow: false,
+ reason: "S3ReadOnlyRole should deny write operations",
+ },
+ {
+ name: "deny delete access",
+ action: "s3:DeleteObject",
+ resource: "arn:seaweed:s3:::test-bucket/file.txt",
+ shouldAllow: false,
+ reason: "S3ReadOnlyRole should deny delete operations",
+ },
+ {
+ name: "deny filer access",
+ action: "filer:CreateEntry",
+ resource: "arn:seaweed:filer::path/test",
+ shouldAllow: false,
+ reason: "S3ReadOnlyRole should not allow filer operations",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ allowed, err := iamManager.IsActionAllowed(ctx, &ActionRequest{
+ Principal: principal,
+ Action: tt.action,
+ Resource: tt.resource,
+ SessionToken: sessionToken,
+ })
+
+ require.NoError(t, err)
+ assert.Equal(t, tt.shouldAllow, allowed, tt.reason)
+ })
+ }
+}
+
+// TestSessionExpiration tests session expiration and cleanup
+func TestSessionExpiration(t *testing.T) {
+ iamManager := setupIntegratedIAMSystem(t)
+ ctx := context.Background()
+
+ // Create a valid JWT token for testing
+ validJWTToken := createTestJWT(t, "https://test-issuer.com", "test-user-123", "test-signing-key")
+
+ // Create a short-lived session
+ assumeRequest := &sts.AssumeRoleWithWebIdentityRequest{
+ RoleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
+ WebIdentityToken: validJWTToken,
+ RoleSessionName: "expiration-test",
+ DurationSeconds: int64Ptr(900), // 15 minutes
+ }
+
+ response, err := iamManager.AssumeRoleWithWebIdentity(ctx, assumeRequest)
+ require.NoError(t, err)
+
+ sessionToken := response.Credentials.SessionToken
+
+ // Verify session is initially valid
+ allowed, err := iamManager.IsActionAllowed(ctx, &ActionRequest{
+ Principal: response.AssumedRoleUser.Arn,
+ Action: "s3:GetObject",
+ Resource: "arn:seaweed:s3:::test-bucket/file.txt",
+ SessionToken: sessionToken,
+ })
+ require.NoError(t, err)
+ assert.True(t, allowed)
+
+ // Verify the expiration time is set correctly
+ assert.True(t, response.Credentials.Expiration.After(time.Now()))
+ assert.True(t, response.Credentials.Expiration.Before(time.Now().Add(16*time.Minute)))
+
+ // Test session expiration behavior in stateless JWT system
+ // In a stateless system, manual expiration is not supported
+ err = iamManager.ExpireSessionForTesting(ctx, sessionToken)
+ require.Error(t, err, "Manual session expiration should not be supported in stateless system")
+ assert.Contains(t, err.Error(), "manual session expiration not supported")
+
+ // Verify session is still valid (since it hasn't naturally expired)
+ allowed, err = iamManager.IsActionAllowed(ctx, &ActionRequest{
+ Principal: response.AssumedRoleUser.Arn,
+ Action: "s3:GetObject",
+ Resource: "arn:seaweed:s3:::test-bucket/file.txt",
+ SessionToken: sessionToken,
+ })
+ require.NoError(t, err, "Session should still be valid in stateless system")
+ assert.True(t, allowed, "Access should still be allowed since token hasn't naturally expired")
+}
+
+// TestTrustPolicyValidation tests role trust policy validation
+func TestTrustPolicyValidation(t *testing.T) {
+ iamManager := setupIntegratedIAMSystem(t)
+ ctx := context.Background()
+
+ tests := []struct {
+ name string
+ roleArn string
+ provider string
+ userID string
+ shouldAllow bool
+ reason string
+ }{
+ {
+ name: "OIDC user allowed by trust policy",
+ roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
+ provider: "oidc",
+ userID: "test-user-id",
+ shouldAllow: true,
+ reason: "Trust policy should allow OIDC users",
+ },
+ {
+ name: "LDAP user allowed by different role",
+ roleArn: "arn:seaweed:iam::role/LDAPUserRole",
+ provider: "ldap",
+ userID: "testuser",
+ shouldAllow: true,
+ reason: "Trust policy should allow LDAP users for LDAP role",
+ },
+ {
+ name: "Wrong provider for role",
+ roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
+ provider: "ldap",
+ userID: "testuser",
+ shouldAllow: false,
+ reason: "S3ReadOnlyRole trust policy should reject LDAP users",
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // This would test trust policy evaluation
+ // For now, we'll implement this as part of the IAM manager
+ result := iamManager.ValidateTrustPolicy(ctx, tt.roleArn, tt.provider, tt.userID)
+ assert.Equal(t, tt.shouldAllow, result, tt.reason)
+ })
+ }
+}
+
+// Helper functions and test setup
+
+// createTestJWT creates a test JWT token with the specified issuer, subject and signing key
+func createTestJWT(t *testing.T, issuer, subject, signingKey string) string {
+ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
+ "iss": issuer,
+ "sub": subject,
+ "aud": "test-client-id",
+ "exp": time.Now().Add(time.Hour).Unix(),
+ "iat": time.Now().Unix(),
+ // Add claims that trust policy validation expects
+ "idp": "test-oidc", // Identity provider claim for trust policy matching
+ })
+
+ tokenString, err := token.SignedString([]byte(signingKey))
+ require.NoError(t, err)
+ return tokenString
+}
+
+func setupIntegratedIAMSystem(t *testing.T) *IAMManager {
+ // Create IAM manager with all components
+ manager := NewIAMManager()
+
+ // Configure and initialize
+ config := &IAMConfig{
+ STS: &sts.STSConfig{
+ TokenDuration: sts.FlexibleDuration{time.Hour},
+ MaxSessionLength: sts.FlexibleDuration{time.Hour * 12},
+ Issuer: "test-sts",
+ SigningKey: []byte("test-signing-key-32-characters-long"),
+ },
+ Policy: &policy.PolicyEngineConfig{
+ DefaultEffect: "Deny",
+ StoreType: "memory", // Use memory for unit tests
+ },
+ Roles: &RoleStoreConfig{
+ StoreType: "memory", // Use memory for unit tests
+ },
+ }
+
+ err := manager.Initialize(config, func() string {
+ return "localhost:8888" // Mock filer address for testing
+ })
+ require.NoError(t, err)
+
+ // Set up test providers
+ setupTestProviders(t, manager)
+
+ // Set up test policies and roles
+ setupTestPoliciesAndRoles(t, manager)
+
+ return manager
+}
+
+func setupTestProviders(t *testing.T, manager *IAMManager) {
+ // Set up OIDC provider
+ oidcProvider := oidc.NewMockOIDCProvider("test-oidc")
+ oidcConfig := &oidc.OIDCConfig{
+ Issuer: "https://test-issuer.com",
+ ClientID: "test-client-id",
+ }
+ err := oidcProvider.Initialize(oidcConfig)
+ require.NoError(t, err)
+ oidcProvider.SetupDefaultTestData()
+
+ // Set up LDAP mock provider (no config needed for mock)
+ ldapProvider := ldap.NewMockLDAPProvider("test-ldap")
+ err = ldapProvider.Initialize(nil) // Mock doesn't need real config
+ require.NoError(t, err)
+ ldapProvider.SetupDefaultTestData()
+
+ // Register providers
+ err = manager.RegisterIdentityProvider(oidcProvider)
+ require.NoError(t, err)
+ err = manager.RegisterIdentityProvider(ldapProvider)
+ require.NoError(t, err)
+}
+
+func setupTestPoliciesAndRoles(t *testing.T, manager *IAMManager) {
+ ctx := context.Background()
+
+ // Create S3 read-only policy
+ s3ReadPolicy := &policy.PolicyDocument{
+ Version: "2012-10-17",
+ Statement: []policy.Statement{
+ {
+ Sid: "S3ReadAccess",
+ Effect: "Allow",
+ Action: []string{"s3:GetObject", "s3:ListBucket"},
+ Resource: []string{
+ "arn:seaweed:s3:::*",
+ "arn:seaweed:s3:::*/*",
+ },
+ },
+ },
+ }
+
+ err := manager.CreatePolicy(ctx, "", "S3ReadOnlyPolicy", s3ReadPolicy)
+ require.NoError(t, err)
+
+ // Create LDAP user policy
+ ldapUserPolicy := &policy.PolicyDocument{
+ Version: "2012-10-17",
+ Statement: []policy.Statement{
+ {
+ Sid: "FilerAccess",
+ Effect: "Allow",
+ Action: []string{"filer:*"},
+ Resource: []string{
+ "arn:seaweed:filer::path/user-docs/*",
+ },
+ },
+ },
+ }
+
+ err = manager.CreatePolicy(ctx, "", "LDAPUserPolicy", ldapUserPolicy)
+ require.NoError(t, err)
+
+ // Create roles with trust policies
+ err = manager.CreateRole(ctx, "", "S3ReadOnlyRole", &RoleDefinition{
+ RoleName: "S3ReadOnlyRole",
+ TrustPolicy: &policy.PolicyDocument{
+ Version: "2012-10-17",
+ Statement: []policy.Statement{
+ {
+ Effect: "Allow",
+ Principal: map[string]interface{}{
+ "Federated": "test-oidc",
+ },
+ Action: []string{"sts:AssumeRoleWithWebIdentity"},
+ },
+ },
+ },
+ AttachedPolicies: []string{"S3ReadOnlyPolicy"},
+ })
+ require.NoError(t, err)
+
+ err = manager.CreateRole(ctx, "", "LDAPUserRole", &RoleDefinition{
+ RoleName: "LDAPUserRole",
+ TrustPolicy: &policy.PolicyDocument{
+ Version: "2012-10-17",
+ Statement: []policy.Statement{
+ {
+ Effect: "Allow",
+ Principal: map[string]interface{}{
+ "Federated": "test-ldap",
+ },
+ Action: []string{"sts:AssumeRoleWithCredentials"},
+ },
+ },
+ },
+ AttachedPolicies: []string{"LDAPUserPolicy"},
+ })
+ require.NoError(t, err)
+}
+
+func int64Ptr(v int64) *int64 {
+ return &v
+}