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.
		
		
		
		
		
			
		
			
				
					
					
						
							341 lines
						
					
					
						
							13 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							341 lines
						
					
					
						
							13 KiB
						
					
					
				
								package sts
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"testing"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/stretchr/testify/assert"
							 | 
						|
									"github.com/stretchr/testify/require"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// TestDistributedSTSService verifies that multiple STS instances with identical configurations
							 | 
						|
								// behave consistently across distributed environments
							 | 
						|
								func TestDistributedSTSService(t *testing.T) {
							 | 
						|
									ctx := context.Background()
							 | 
						|
								
							 | 
						|
									// Common configuration for all instances
							 | 
						|
									commonConfig := &STSConfig{
							 | 
						|
										TokenDuration:    time.Hour,
							 | 
						|
										MaxSessionLength: 12 * time.Hour,
							 | 
						|
										Issuer:           "distributed-sts-test",
							 | 
						|
										SigningKey:       []byte("test-signing-key-32-characters-long"),
							 | 
						|
										SessionStoreType: "memory", // For testing - would be "filer" in production
							 | 
						|
										Providers: []*ProviderConfig{
							 | 
						|
											{
							 | 
						|
												Name:    "keycloak-oidc",
							 | 
						|
												Type:    "oidc",
							 | 
						|
												Enabled: true,
							 | 
						|
												Config: map[string]interface{}{
							 | 
						|
													"issuer":   "http://keycloak:8080/realms/seaweedfs-test",
							 | 
						|
													"clientId": "seaweedfs-s3",
							 | 
						|
													"jwksUri":  "http://keycloak:8080/realms/seaweedfs-test/protocol/openid-connect/certs",
							 | 
						|
												},
							 | 
						|
											},
							 | 
						|
											{
							 | 
						|
												Name:    "test-mock-provider",
							 | 
						|
												Type:    "mock",
							 | 
						|
												Enabled: true,
							 | 
						|
												Config: map[string]interface{}{
							 | 
						|
													"issuer":   "http://localhost:9999",
							 | 
						|
													"clientId": "test-client",
							 | 
						|
												},
							 | 
						|
											},
							 | 
						|
											{
							 | 
						|
												Name:    "disabled-ldap",
							 | 
						|
												Type:    "oidc", // Use OIDC as placeholder since LDAP isn't implemented
							 | 
						|
												Enabled: false,
							 | 
						|
												Config: map[string]interface{}{
							 | 
						|
													"issuer":   "ldap://company.com",
							 | 
						|
													"clientId": "ldap-client",
							 | 
						|
												},
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create multiple STS instances simulating distributed deployment
							 | 
						|
									instance1 := NewSTSService()
							 | 
						|
									instance2 := NewSTSService()
							 | 
						|
									instance3 := NewSTSService()
							 | 
						|
								
							 | 
						|
									// Initialize all instances with identical configuration
							 | 
						|
									err := instance1.Initialize(commonConfig)
							 | 
						|
									require.NoError(t, err, "Instance 1 should initialize successfully")
							 | 
						|
								
							 | 
						|
									err = instance2.Initialize(commonConfig)
							 | 
						|
									require.NoError(t, err, "Instance 2 should initialize successfully")
							 | 
						|
								
							 | 
						|
									err = instance3.Initialize(commonConfig)
							 | 
						|
									require.NoError(t, err, "Instance 3 should initialize successfully")
							 | 
						|
								
							 | 
						|
									// Verify all instances have identical provider configurations
							 | 
						|
									t.Run("provider_consistency", func(t *testing.T) {
							 | 
						|
										// All instances should have same number of providers
							 | 
						|
										assert.Len(t, instance1.providers, 2, "Instance 1 should have 2 enabled providers")
							 | 
						|
										assert.Len(t, instance2.providers, 2, "Instance 2 should have 2 enabled providers") 
							 | 
						|
										assert.Len(t, instance3.providers, 2, "Instance 3 should have 2 enabled providers")
							 | 
						|
								
							 | 
						|
										// All instances should have same provider names
							 | 
						|
										instance1Names := instance1.getProviderNames()
							 | 
						|
										instance2Names := instance2.getProviderNames()
							 | 
						|
										instance3Names := instance3.getProviderNames()
							 | 
						|
								
							 | 
						|
										assert.ElementsMatch(t, instance1Names, instance2Names, "Instance 1 and 2 should have same providers")
							 | 
						|
										assert.ElementsMatch(t, instance2Names, instance3Names, "Instance 2 and 3 should have same providers")
							 | 
						|
								
							 | 
						|
										// Verify specific providers exist on all instances
							 | 
						|
										expectedProviders := []string{"keycloak-oidc", "test-mock-provider"}
							 | 
						|
										assert.ElementsMatch(t, instance1Names, expectedProviders, "Instance 1 should have expected providers")
							 | 
						|
										assert.ElementsMatch(t, instance2Names, expectedProviders, "Instance 2 should have expected providers")
							 | 
						|
										assert.ElementsMatch(t, instance3Names, expectedProviders, "Instance 3 should have expected providers")
							 | 
						|
								
							 | 
						|
										// Verify disabled providers are not loaded
							 | 
						|
										assert.NotContains(t, instance1Names, "disabled-ldap", "Disabled providers should not be loaded")
							 | 
						|
										assert.NotContains(t, instance2Names, "disabled-ldap", "Disabled providers should not be loaded")
							 | 
						|
										assert.NotContains(t, instance3Names, "disabled-ldap", "Disabled providers should not be loaded")
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									// Test token generation consistency across instances
							 | 
						|
									t.Run("token_generation_consistency", func(t *testing.T) {
							 | 
						|
										sessionId := "test-session-123"
							 | 
						|
										expiresAt := time.Now().Add(time.Hour)
							 | 
						|
								
							 | 
						|
										// Generate tokens from different instances
							 | 
						|
										token1, err1 := instance1.tokenGenerator.GenerateSessionToken(sessionId, expiresAt)
							 | 
						|
										token2, err2 := instance2.tokenGenerator.GenerateSessionToken(sessionId, expiresAt)
							 | 
						|
										token3, err3 := instance3.tokenGenerator.GenerateSessionToken(sessionId, expiresAt)
							 | 
						|
								
							 | 
						|
										require.NoError(t, err1, "Instance 1 token generation should succeed")
							 | 
						|
										require.NoError(t, err2, "Instance 2 token generation should succeed")
							 | 
						|
										require.NoError(t, err3, "Instance 3 token generation should succeed")
							 | 
						|
								
							 | 
						|
										// All tokens should be different (due to timestamp variations)
							 | 
						|
										// But they should all be valid JWTs with same signing key
							 | 
						|
										assert.NotEmpty(t, token1)
							 | 
						|
										assert.NotEmpty(t, token2)
							 | 
						|
										assert.NotEmpty(t, token3)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									// Test token validation consistency - any instance should validate tokens from any other instance
							 | 
						|
									t.Run("cross_instance_token_validation", func(t *testing.T) {
							 | 
						|
										sessionId := "cross-validation-session"
							 | 
						|
										expiresAt := time.Now().Add(time.Hour)
							 | 
						|
								
							 | 
						|
										// Generate token on instance 1
							 | 
						|
										token, err := instance1.tokenGenerator.GenerateSessionToken(sessionId, expiresAt)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										// Validate on all instances
							 | 
						|
										claims1, err1 := instance1.tokenGenerator.ValidateSessionToken(token)
							 | 
						|
										claims2, err2 := instance2.tokenGenerator.ValidateSessionToken(token)
							 | 
						|
										claims3, err3 := instance3.tokenGenerator.ValidateSessionToken(token)
							 | 
						|
								
							 | 
						|
										require.NoError(t, err1, "Instance 1 should validate token from instance 1")
							 | 
						|
										require.NoError(t, err2, "Instance 2 should validate token from instance 1")
							 | 
						|
										require.NoError(t, err3, "Instance 3 should validate token from instance 1")
							 | 
						|
								
							 | 
						|
										// All instances should extract same session ID
							 | 
						|
										assert.Equal(t, sessionId, claims1.SessionId)
							 | 
						|
										assert.Equal(t, sessionId, claims2.SessionId)
							 | 
						|
										assert.Equal(t, sessionId, claims3.SessionId)
							 | 
						|
								
							 | 
						|
										assert.Equal(t, claims1.SessionId, claims2.SessionId)
							 | 
						|
										assert.Equal(t, claims2.SessionId, claims3.SessionId)
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									// Test provider access consistency
							 | 
						|
									t.Run("provider_access_consistency", func(t *testing.T) {
							 | 
						|
										// All instances should be able to access the same providers
							 | 
						|
										provider1, exists1 := instance1.providers["test-mock-provider"]
							 | 
						|
										provider2, exists2 := instance2.providers["test-mock-provider"]
							 | 
						|
										provider3, exists3 := instance3.providers["test-mock-provider"]
							 | 
						|
								
							 | 
						|
										assert.True(t, exists1, "Instance 1 should have test-mock-provider")
							 | 
						|
										assert.True(t, exists2, "Instance 2 should have test-mock-provider")
							 | 
						|
										assert.True(t, exists3, "Instance 3 should have test-mock-provider")
							 | 
						|
								
							 | 
						|
										assert.Equal(t, provider1.Name(), provider2.Name())
							 | 
						|
										assert.Equal(t, provider2.Name(), provider3.Name())
							 | 
						|
								
							 | 
						|
										// Test authentication with the mock provider on all instances
							 | 
						|
										testToken := "valid_test_token"
							 | 
						|
								
							 | 
						|
										identity1, err1 := provider1.Authenticate(ctx, testToken)
							 | 
						|
										identity2, err2 := provider2.Authenticate(ctx, testToken)
							 | 
						|
										identity3, err3 := provider3.Authenticate(ctx, testToken)
							 | 
						|
								
							 | 
						|
										require.NoError(t, err1, "Instance 1 provider should authenticate successfully")
							 | 
						|
										require.NoError(t, err2, "Instance 2 provider should authenticate successfully")
							 | 
						|
										require.NoError(t, err3, "Instance 3 provider should authenticate successfully")
							 | 
						|
								
							 | 
						|
										// All instances should return identical identity information
							 | 
						|
										assert.Equal(t, identity1.UserID, identity2.UserID)
							 | 
						|
										assert.Equal(t, identity2.UserID, identity3.UserID)
							 | 
						|
										assert.Equal(t, identity1.Email, identity2.Email)
							 | 
						|
										assert.Equal(t, identity2.Email, identity3.Email)
							 | 
						|
										assert.Equal(t, identity1.Provider, identity2.Provider)
							 | 
						|
										assert.Equal(t, identity2.Provider, identity3.Provider)
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestSTSConfigurationValidation tests configuration validation for distributed deployments
							 | 
						|
								func TestSTSConfigurationValidation(t *testing.T) {
							 | 
						|
									t.Run("consistent_signing_keys_required", func(t *testing.T) {
							 | 
						|
										// Different signing keys should result in incompatible token validation
							 | 
						|
										config1 := &STSConfig{
							 | 
						|
											TokenDuration:    time.Hour,
							 | 
						|
											MaxSessionLength: 12 * time.Hour,
							 | 
						|
											Issuer:           "test-sts",
							 | 
						|
											SigningKey:       []byte("signing-key-1-32-characters-long"),
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										config2 := &STSConfig{
							 | 
						|
											TokenDuration:    time.Hour,
							 | 
						|
											MaxSessionLength: 12 * time.Hour,
							 | 
						|
											Issuer:           "test-sts",
							 | 
						|
											SigningKey:       []byte("signing-key-2-32-characters-long"), // Different key!
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										instance1 := NewSTSService()
							 | 
						|
										instance2 := NewSTSService()
							 | 
						|
								
							 | 
						|
										err1 := instance1.Initialize(config1)
							 | 
						|
										err2 := instance2.Initialize(config2)
							 | 
						|
								
							 | 
						|
										require.NoError(t, err1)
							 | 
						|
										require.NoError(t, err2)
							 | 
						|
								
							 | 
						|
										// Generate token on instance 1
							 | 
						|
										sessionId := "test-session"
							 | 
						|
										expiresAt := time.Now().Add(time.Hour)
							 | 
						|
										token, err := instance1.tokenGenerator.GenerateSessionToken(sessionId, expiresAt)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										// Instance 1 should validate its own token
							 | 
						|
										_, err = instance1.tokenGenerator.ValidateSessionToken(token)
							 | 
						|
										assert.NoError(t, err, "Instance 1 should validate its own token")
							 | 
						|
								
							 | 
						|
										// Instance 2 should reject token from instance 1 (different signing key)
							 | 
						|
										_, err = instance2.tokenGenerator.ValidateSessionToken(token)
							 | 
						|
										assert.Error(t, err, "Instance 2 should reject token with different signing key")
							 | 
						|
									})
							 | 
						|
								
							 | 
						|
									t.Run("consistent_issuer_required", func(t *testing.T) {
							 | 
						|
										// Different issuers should result in incompatible tokens
							 | 
						|
										commonSigningKey := []byte("shared-signing-key-32-characters-lo")
							 | 
						|
								
							 | 
						|
										config1 := &STSConfig{
							 | 
						|
											TokenDuration:    time.Hour,
							 | 
						|
											MaxSessionLength: 12 * time.Hour,
							 | 
						|
											Issuer:           "sts-instance-1",
							 | 
						|
											SigningKey:       commonSigningKey,
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										config2 := &STSConfig{
							 | 
						|
											TokenDuration:    time.Hour,
							 | 
						|
											MaxSessionLength: 12 * time.Hour,
							 | 
						|
											Issuer:           "sts-instance-2", // Different issuer!
							 | 
						|
											SigningKey:       commonSigningKey,
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										instance1 := NewSTSService()
							 | 
						|
										instance2 := NewSTSService()
							 | 
						|
								
							 | 
						|
										err1 := instance1.Initialize(config1)
							 | 
						|
										err2 := instance2.Initialize(config2)
							 | 
						|
								
							 | 
						|
										require.NoError(t, err1)
							 | 
						|
										require.NoError(t, err2)
							 | 
						|
								
							 | 
						|
										// Generate token on instance 1
							 | 
						|
										sessionId := "test-session"
							 | 
						|
										expiresAt := time.Now().Add(time.Hour)
							 | 
						|
										token, err := instance1.tokenGenerator.GenerateSessionToken(sessionId, expiresAt)
							 | 
						|
										require.NoError(t, err)
							 | 
						|
								
							 | 
						|
										// Instance 2 should reject token due to issuer mismatch
							 | 
						|
										// (Even though signing key is the same, issuer validation will fail)
							 | 
						|
										_, err = instance2.tokenGenerator.ValidateSessionToken(token)
							 | 
						|
										assert.Error(t, err, "Instance 2 should reject token with different issuer")
							 | 
						|
									})
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestProviderFactoryDistributed tests the provider factory in distributed scenarios
							 | 
						|
								func TestProviderFactoryDistributed(t *testing.T) {
							 | 
						|
									factory := NewProviderFactory()
							 | 
						|
								
							 | 
						|
									// Simulate configuration that would be identical across all instances
							 | 
						|
									configs := []*ProviderConfig{
							 | 
						|
										{
							 | 
						|
											Name:    "production-keycloak",
							 | 
						|
											Type:    "oidc",
							 | 
						|
											Enabled: true,
							 | 
						|
											Config: map[string]interface{}{
							 | 
						|
												"issuer":       "https://keycloak.company.com/realms/seaweedfs",
							 | 
						|
												"clientId":     "seaweedfs-prod",
							 | 
						|
												"clientSecret": "super-secret-key",
							 | 
						|
												"jwksUri":      "https://keycloak.company.com/realms/seaweedfs/protocol/openid-connect/certs",
							 | 
						|
												"scopes":       []string{"openid", "profile", "email", "roles"},
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											Name:    "backup-oidc",
							 | 
						|
											Type:    "oidc",
							 | 
						|
											Enabled: false, // Disabled by default
							 | 
						|
											Config: map[string]interface{}{
							 | 
						|
												"issuer":   "https://backup-oidc.company.com",
							 | 
						|
												"clientId": "seaweedfs-backup",
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											Name:    "dev-mock",
							 | 
						|
											Type:    "mock",
							 | 
						|
											Enabled: true,
							 | 
						|
											Config: map[string]interface{}{
							 | 
						|
												"issuer":   "http://dev-mock:9999",
							 | 
						|
												"clientId": "mock-client",
							 | 
						|
											},
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create providers multiple times (simulating multiple instances)
							 | 
						|
									providers1, err1 := factory.LoadProvidersFromConfig(configs)
							 | 
						|
									providers2, err2 := factory.LoadProvidersFromConfig(configs)
							 | 
						|
									providers3, err3 := factory.LoadProvidersFromConfig(configs)
							 | 
						|
								
							 | 
						|
									require.NoError(t, err1, "First load should succeed")
							 | 
						|
									require.NoError(t, err2, "Second load should succeed")
							 | 
						|
									require.NoError(t, err3, "Third load should succeed")
							 | 
						|
								
							 | 
						|
									// All instances should have same provider counts
							 | 
						|
									assert.Len(t, providers1, 2, "First instance should have 2 enabled providers")
							 | 
						|
									assert.Len(t, providers2, 2, "Second instance should have 2 enabled providers")
							 | 
						|
									assert.Len(t, providers3, 2, "Third instance should have 2 enabled providers")
							 | 
						|
								
							 | 
						|
									// All instances should have same provider names
							 | 
						|
									names1 := make([]string, 0, len(providers1))
							 | 
						|
									names2 := make([]string, 0, len(providers2))
							 | 
						|
									names3 := make([]string, 0, len(providers3))
							 | 
						|
								
							 | 
						|
									for name := range providers1 {
							 | 
						|
										names1 = append(names1, name)
							 | 
						|
									}
							 | 
						|
									for name := range providers2 {
							 | 
						|
										names2 = append(names2, name)
							 | 
						|
									}
							 | 
						|
									for name := range providers3 {
							 | 
						|
										names3 = append(names3, name)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									assert.ElementsMatch(t, names1, names2, "Instance 1 and 2 should have same provider names")
							 | 
						|
									assert.ElementsMatch(t, names2, names3, "Instance 2 and 3 should have same provider names")
							 | 
						|
								
							 | 
						|
									// Verify specific providers
							 | 
						|
									expectedProviders := []string{"production-keycloak", "dev-mock"}
							 | 
						|
									assert.ElementsMatch(t, names1, expectedProviders, "Should have expected enabled providers")
							 | 
						|
								
							 | 
						|
									// Verify disabled providers are not included
							 | 
						|
									assert.NotContains(t, names1, "backup-oidc", "Disabled providers should not be loaded")
							 | 
						|
									assert.NotContains(t, names2, "backup-oidc", "Disabled providers should not be loaded")
							 | 
						|
									assert.NotContains(t, names3, "backup-oidc", "Disabled providers should not be loaded")
							 | 
						|
								}
							 |