| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340 |
- 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: FlexibleDuration{time.Hour},
- MaxSessionLength: FlexibleDuration{12 * time.Hour},
- Issuer: "distributed-sts-test",
- SigningKey: []byte("test-signing-key-32-characters-long"),
- 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: "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")
- // Manually register mock providers for testing (not available in production)
- mockProviderConfig := map[string]interface{}{
- "issuer": "http://localhost:9999",
- "clientId": "test-client",
- }
- mockProvider1, err := createMockOIDCProvider("test-mock-provider", mockProviderConfig)
- require.NoError(t, err)
- mockProvider2, err := createMockOIDCProvider("test-mock-provider", mockProviderConfig)
- require.NoError(t, err)
- mockProvider3, err := createMockOIDCProvider("test-mock-provider", mockProviderConfig)
- require.NoError(t, err)
- instance1.RegisterProvider(mockProvider1)
- instance2.RegisterProvider(mockProvider2)
- instance3.RegisterProvider(mockProvider3)
- // 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: FlexibleDuration{time.Hour},
- MaxSessionLength: FlexibleDuration{12 * time.Hour},
- Issuer: "test-sts",
- SigningKey: []byte("signing-key-1-32-characters-long"),
- }
- config2 := &STSConfig{
- TokenDuration: FlexibleDuration{time.Hour},
- MaxSessionLength: FlexibleDuration{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: FlexibleDuration{time.Hour},
- MaxSessionLength: FlexibleDuration{12 * time.Hour},
- Issuer: "sts-instance-1",
- SigningKey: commonSigningKey,
- }
- config2 := &STSConfig{
- TokenDuration: FlexibleDuration{time.Hour},
- MaxSessionLength: FlexibleDuration{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",
- },
- },
- }
- // 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, 1, "First instance should have 1 enabled provider")
- assert.Len(t, providers2, 1, "Second instance should have 1 enabled provider")
- assert.Len(t, providers3, 1, "Third instance should have 1 enabled provider")
- // 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"}
- 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")
- }
|