package config import ( "crypto/ed25519" "crypto/sha256" "crypto/x509" "encoding/pem" "os" "path/filepath" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var configEnvMu sync.Mutex func TestLoadFromEnv(t *testing.T) { customResponseSignerPrivateKeyPEMPath := new(string) *customResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) customShutdownTimeout := new(string) *customShutdownTimeout = "17s" customPublicHTTPAddr := new(string) *customPublicHTTPAddr = "127.0.0.1:9090" customAuthServiceBaseURL := new(string) *customAuthServiceBaseURL = " http://127.0.0.1:8082/ " customUserServiceBaseURL := new(string) *customUserServiceBaseURL = " http://127.0.0.1:8083/ " customAuthenticatedGRPCAddr := new(string) *customAuthenticatedGRPCAddr = "127.0.0.1:9191" customAuthenticatedGRPCFreshnessWindow := new(string) *customAuthenticatedGRPCFreshnessWindow = "90s" customSessionCacheRedisAddr := new(string) *customSessionCacheRedisAddr = "127.0.0.1:6379" customSessionEventsRedisStream := new(string) *customSessionEventsRedisStream = "gateway:session_events" customClientEventsRedisStream := new(string) *customClientEventsRedisStream = "gateway:client_events" emptyPublicHTTPAddr := new(string) *emptyPublicHTTPAddr = "" whitespacePublicHTTPAddr := new(string) *whitespacePublicHTTPAddr = " " emptyAuthenticatedGRPCAddr := new(string) *emptyAuthenticatedGRPCAddr = "" whitespaceAuthenticatedGRPCAddr := new(string) *whitespaceAuthenticatedGRPCAddr = " " emptySessionCacheRedisAddr := new(string) *emptySessionCacheRedisAddr = "" whitespaceSessionCacheRedisAddr := new(string) *whitespaceSessionCacheRedisAddr = " " zeroShutdownTimeout := new(string) *zeroShutdownTimeout = "0s" negativeShutdownTimeout := new(string) *negativeShutdownTimeout = "-1s" invalidShutdownTimeout := new(string) *invalidShutdownTimeout = "later" zeroAuthenticatedGRPCFreshnessWindow := new(string) *zeroAuthenticatedGRPCFreshnessWindow = "0s" invalidAuthenticatedGRPCFreshnessWindow := new(string) *invalidAuthenticatedGRPCFreshnessWindow = "later" tests := []struct { name string shutdownTimeout *string publicHTTPAddr *string authServiceBaseURL *string userServiceBaseURL *string authenticatedGRPCAddr *string authenticatedGRPCFreshnessWindow *string sessionCacheRedisAddr *string responseSignerPrivateKeyPEMPath *string want Config wantErr string }{ { name: "required redis address with default optional values", sessionCacheRedisAddr: customSessionCacheRedisAddr, responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, want: Config{ ShutdownTimeout: 5 * time.Second, Logging: DefaultLoggingConfig(), PublicHTTP: DefaultPublicHTTPConfig(), AdminHTTP: DefaultAdminHTTPConfig(), AuthenticatedGRPC: DefaultAuthenticatedGRPCConfig(), SessionCacheRedis: SessionCacheRedisConfig{ Addr: "127.0.0.1:6379", DB: defaultSessionCacheRedisDB, KeyPrefix: defaultSessionCacheRedisKeyPrefix, LookupTimeout: defaultSessionCacheRedisLookupTimeout, }, ReplayRedis: DefaultReplayRedisConfig(), SessionEventsRedis: SessionEventsRedisConfig{ Stream: "gateway:session_events", ReadBlockTimeout: defaultSessionEventsRedisReadBlockTimeout, }, ClientEventsRedis: ClientEventsRedisConfig{ Stream: "gateway:client_events", ReadBlockTimeout: defaultClientEventsRedisReadBlockTimeout, }, ResponseSigner: ResponseSignerConfig{ PrivateKeyPEMPath: *customResponseSignerPrivateKeyPEMPath, }, }, }, { name: "custom shutdown timeout", shutdownTimeout: customShutdownTimeout, sessionCacheRedisAddr: customSessionCacheRedisAddr, responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, want: Config{ ShutdownTimeout: 17 * time.Second, Logging: DefaultLoggingConfig(), PublicHTTP: DefaultPublicHTTPConfig(), AdminHTTP: DefaultAdminHTTPConfig(), AuthenticatedGRPC: DefaultAuthenticatedGRPCConfig(), SessionCacheRedis: SessionCacheRedisConfig{ Addr: "127.0.0.1:6379", DB: defaultSessionCacheRedisDB, KeyPrefix: defaultSessionCacheRedisKeyPrefix, LookupTimeout: defaultSessionCacheRedisLookupTimeout, }, ReplayRedis: DefaultReplayRedisConfig(), SessionEventsRedis: SessionEventsRedisConfig{ Stream: "gateway:session_events", ReadBlockTimeout: defaultSessionEventsRedisReadBlockTimeout, }, ClientEventsRedis: ClientEventsRedisConfig{ Stream: "gateway:client_events", ReadBlockTimeout: defaultClientEventsRedisReadBlockTimeout, }, ResponseSigner: ResponseSignerConfig{ PrivateKeyPEMPath: *customResponseSignerPrivateKeyPEMPath, }, }, }, { name: "custom public http address", publicHTTPAddr: customPublicHTTPAddr, sessionCacheRedisAddr: customSessionCacheRedisAddr, responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, want: Config{ ShutdownTimeout: 5 * time.Second, Logging: DefaultLoggingConfig(), PublicHTTP: func() PublicHTTPConfig { cfg := DefaultPublicHTTPConfig() cfg.Addr = "127.0.0.1:9090" return cfg }(), AdminHTTP: DefaultAdminHTTPConfig(), AuthenticatedGRPC: DefaultAuthenticatedGRPCConfig(), SessionCacheRedis: SessionCacheRedisConfig{ Addr: "127.0.0.1:6379", DB: defaultSessionCacheRedisDB, KeyPrefix: defaultSessionCacheRedisKeyPrefix, LookupTimeout: defaultSessionCacheRedisLookupTimeout, }, ReplayRedis: DefaultReplayRedisConfig(), SessionEventsRedis: SessionEventsRedisConfig{ Stream: "gateway:session_events", ReadBlockTimeout: defaultSessionEventsRedisReadBlockTimeout, }, ClientEventsRedis: ClientEventsRedisConfig{ Stream: "gateway:client_events", ReadBlockTimeout: defaultClientEventsRedisReadBlockTimeout, }, ResponseSigner: ResponseSignerConfig{ PrivateKeyPEMPath: *customResponseSignerPrivateKeyPEMPath, }, }, }, { name: "custom auth service base url", authServiceBaseURL: customAuthServiceBaseURL, sessionCacheRedisAddr: customSessionCacheRedisAddr, responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, want: Config{ ShutdownTimeout: 5 * time.Second, Logging: DefaultLoggingConfig(), PublicHTTP: DefaultPublicHTTPConfig(), AuthService: AuthServiceConfig{ BaseURL: "http://127.0.0.1:8082", }, AdminHTTP: DefaultAdminHTTPConfig(), AuthenticatedGRPC: DefaultAuthenticatedGRPCConfig(), SessionCacheRedis: SessionCacheRedisConfig{ Addr: "127.0.0.1:6379", DB: defaultSessionCacheRedisDB, KeyPrefix: defaultSessionCacheRedisKeyPrefix, LookupTimeout: defaultSessionCacheRedisLookupTimeout, }, ReplayRedis: DefaultReplayRedisConfig(), SessionEventsRedis: SessionEventsRedisConfig{ Stream: "gateway:session_events", ReadBlockTimeout: defaultSessionEventsRedisReadBlockTimeout, }, ClientEventsRedis: ClientEventsRedisConfig{ Stream: "gateway:client_events", ReadBlockTimeout: defaultClientEventsRedisReadBlockTimeout, }, ResponseSigner: ResponseSignerConfig{ PrivateKeyPEMPath: *customResponseSignerPrivateKeyPEMPath, }, }, }, { name: "custom user service base url", userServiceBaseURL: customUserServiceBaseURL, sessionCacheRedisAddr: customSessionCacheRedisAddr, responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, want: Config{ ShutdownTimeout: 5 * time.Second, Logging: DefaultLoggingConfig(), PublicHTTP: DefaultPublicHTTPConfig(), UserService: UserServiceConfig{ BaseURL: "http://127.0.0.1:8083", }, AdminHTTP: DefaultAdminHTTPConfig(), AuthenticatedGRPC: DefaultAuthenticatedGRPCConfig(), SessionCacheRedis: SessionCacheRedisConfig{ Addr: "127.0.0.1:6379", DB: defaultSessionCacheRedisDB, KeyPrefix: defaultSessionCacheRedisKeyPrefix, LookupTimeout: defaultSessionCacheRedisLookupTimeout, }, ReplayRedis: DefaultReplayRedisConfig(), SessionEventsRedis: SessionEventsRedisConfig{ Stream: "gateway:session_events", ReadBlockTimeout: defaultSessionEventsRedisReadBlockTimeout, }, ClientEventsRedis: ClientEventsRedisConfig{ Stream: "gateway:client_events", ReadBlockTimeout: defaultClientEventsRedisReadBlockTimeout, }, ResponseSigner: ResponseSignerConfig{ PrivateKeyPEMPath: *customResponseSignerPrivateKeyPEMPath, }, }, }, { name: "custom authenticated grpc address", authenticatedGRPCAddr: customAuthenticatedGRPCAddr, sessionCacheRedisAddr: customSessionCacheRedisAddr, responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, want: Config{ ShutdownTimeout: 5 * time.Second, Logging: DefaultLoggingConfig(), PublicHTTP: DefaultPublicHTTPConfig(), AdminHTTP: DefaultAdminHTTPConfig(), AuthenticatedGRPC: func() AuthenticatedGRPCConfig { cfg := DefaultAuthenticatedGRPCConfig() cfg.Addr = "127.0.0.1:9191" return cfg }(), SessionCacheRedis: SessionCacheRedisConfig{ Addr: "127.0.0.1:6379", DB: defaultSessionCacheRedisDB, KeyPrefix: defaultSessionCacheRedisKeyPrefix, LookupTimeout: defaultSessionCacheRedisLookupTimeout, }, ReplayRedis: DefaultReplayRedisConfig(), SessionEventsRedis: SessionEventsRedisConfig{ Stream: "gateway:session_events", ReadBlockTimeout: defaultSessionEventsRedisReadBlockTimeout, }, ClientEventsRedis: ClientEventsRedisConfig{ Stream: "gateway:client_events", ReadBlockTimeout: defaultClientEventsRedisReadBlockTimeout, }, ResponseSigner: ResponseSignerConfig{ PrivateKeyPEMPath: *customResponseSignerPrivateKeyPEMPath, }, }, }, { name: "custom authenticated grpc freshness window", authenticatedGRPCFreshnessWindow: customAuthenticatedGRPCFreshnessWindow, sessionCacheRedisAddr: customSessionCacheRedisAddr, responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, want: Config{ ShutdownTimeout: 5 * time.Second, Logging: DefaultLoggingConfig(), PublicHTTP: DefaultPublicHTTPConfig(), AdminHTTP: DefaultAdminHTTPConfig(), AuthenticatedGRPC: func() AuthenticatedGRPCConfig { cfg := DefaultAuthenticatedGRPCConfig() cfg.FreshnessWindow = 90 * time.Second return cfg }(), SessionCacheRedis: SessionCacheRedisConfig{ Addr: "127.0.0.1:6379", DB: defaultSessionCacheRedisDB, KeyPrefix: defaultSessionCacheRedisKeyPrefix, LookupTimeout: defaultSessionCacheRedisLookupTimeout, }, ReplayRedis: DefaultReplayRedisConfig(), SessionEventsRedis: SessionEventsRedisConfig{ Stream: "gateway:session_events", ReadBlockTimeout: defaultSessionEventsRedisReadBlockTimeout, }, ClientEventsRedis: ClientEventsRedisConfig{ Stream: "gateway:client_events", ReadBlockTimeout: defaultClientEventsRedisReadBlockTimeout, }, ResponseSigner: ResponseSignerConfig{ PrivateKeyPEMPath: *customResponseSignerPrivateKeyPEMPath, }, }, }, { name: "zero shutdown timeout", shutdownTimeout: zeroShutdownTimeout, wantErr: "must be positive", }, { name: "negative shutdown timeout", shutdownTimeout: negativeShutdownTimeout, wantErr: "must be positive", }, { name: "invalid shutdown timeout", shutdownTimeout: invalidShutdownTimeout, wantErr: "parse GATEWAY_SHUTDOWN_TIMEOUT", }, { name: "empty public http address", publicHTTPAddr: emptyPublicHTTPAddr, wantErr: "GATEWAY_PUBLIC_HTTP_ADDR must not be empty", }, { name: "whitespace public http address", publicHTTPAddr: whitespacePublicHTTPAddr, wantErr: "GATEWAY_PUBLIC_HTTP_ADDR must not be empty", }, { name: "empty authenticated grpc address", authenticatedGRPCAddr: emptyAuthenticatedGRPCAddr, sessionCacheRedisAddr: customSessionCacheRedisAddr, wantErr: "GATEWAY_AUTHENTICATED_GRPC_ADDR must not be empty", }, { name: "whitespace authenticated grpc address", authenticatedGRPCAddr: whitespaceAuthenticatedGRPCAddr, sessionCacheRedisAddr: customSessionCacheRedisAddr, wantErr: "GATEWAY_AUTHENTICATED_GRPC_ADDR must not be empty", }, { name: "zero authenticated grpc freshness window", authenticatedGRPCFreshnessWindow: zeroAuthenticatedGRPCFreshnessWindow, sessionCacheRedisAddr: customSessionCacheRedisAddr, wantErr: authenticatedGRPCFreshnessWindowEnvVar + " must be positive", }, { name: "invalid authenticated grpc freshness window", authenticatedGRPCFreshnessWindow: invalidAuthenticatedGRPCFreshnessWindow, sessionCacheRedisAddr: customSessionCacheRedisAddr, wantErr: "parse " + authenticatedGRPCFreshnessWindowEnvVar, }, { name: "missing session cache redis address", responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, wantErr: "GATEWAY_SESSION_CACHE_REDIS_ADDR must not be empty", }, { name: "empty session cache redis address", sessionCacheRedisAddr: emptySessionCacheRedisAddr, responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, wantErr: "GATEWAY_SESSION_CACHE_REDIS_ADDR must not be empty", }, { name: "whitespace session cache redis address", sessionCacheRedisAddr: whitespaceSessionCacheRedisAddr, responseSignerPrivateKeyPEMPath: customResponseSignerPrivateKeyPEMPath, wantErr: "GATEWAY_SESSION_CACHE_REDIS_ADDR must not be empty", }, { name: "missing response signer private key path", sessionCacheRedisAddr: customSessionCacheRedisAddr, wantErr: responseSignerPrivateKeyPEMPathEnvVar + " must not be empty", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { restoreEnvs(t, shutdownTimeoutEnvVar, publicHTTPAddrEnvVar, authServiceBaseURLEnvVar, userServiceBaseURLEnvVar, authenticatedGRPCAddrEnvVar, authenticatedGRPCFreshnessWindowEnvVar, sessionCacheRedisAddrEnvVar, sessionEventsRedisStreamEnvVar, clientEventsRedisStreamEnvVar, responseSignerPrivateKeyPEMPathEnvVar, ) setEnvValue(t, shutdownTimeoutEnvVar, tt.shutdownTimeout) setEnvValue(t, publicHTTPAddrEnvVar, tt.publicHTTPAddr) setEnvValue(t, authServiceBaseURLEnvVar, tt.authServiceBaseURL) setEnvValue(t, userServiceBaseURLEnvVar, tt.userServiceBaseURL) setEnvValue(t, authenticatedGRPCAddrEnvVar, tt.authenticatedGRPCAddr) setEnvValue(t, authenticatedGRPCFreshnessWindowEnvVar, tt.authenticatedGRPCFreshnessWindow) setEnvValue(t, sessionCacheRedisAddrEnvVar, tt.sessionCacheRedisAddr) setEnvValue(t, sessionEventsRedisStreamEnvVar, customSessionEventsRedisStream) setEnvValue(t, clientEventsRedisStreamEnvVar, customClientEventsRedisStream) setEnvValue(t, responseSignerPrivateKeyPEMPathEnvVar, tt.responseSignerPrivateKeyPEMPath) cfg, err := LoadFromEnv() if tt.wantErr != "" { require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want, cfg) }) } } func TestLoadFromEnvOperationalSettings(t *testing.T) { t.Parallel() customSessionCacheRedisAddr := new(string) *customSessionCacheRedisAddr = "127.0.0.1:6379" customSessionEventsRedisStream := new(string) *customSessionEventsRedisStream = "gateway:session_events" customClientEventsRedisStream := new(string) *customClientEventsRedisStream = "gateway:client_events" customResponseSignerPrivateKeyPEMPath := new(string) *customResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) customLogLevel := new(string) *customLogLevel = "debug" customAdminAddr := new(string) *customAdminAddr = "127.0.0.1:8081" customAdminReadTimeout := new(string) *customAdminReadTimeout = "4s" customPublicReadTimeout := new(string) *customPublicReadTimeout = "12s" customPublicAuthUpstreamTimeout := new(string) *customPublicAuthUpstreamTimeout = "1500ms" customGRPCConnectionTimeout := new(string) *customGRPCConnectionTimeout = "7s" customGRPCDownstreamTimeout := new(string) *customGRPCDownstreamTimeout = "9s" invalidLogLevel := new(string) *invalidLogLevel = "verbose" tests := []struct { name string envs map[string]*string assert func(t *testing.T, cfg Config) wantErr string }{ { name: "custom operational settings", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, sessionEventsRedisStreamEnvVar: customSessionEventsRedisStream, clientEventsRedisStreamEnvVar: customClientEventsRedisStream, responseSignerPrivateKeyPEMPathEnvVar: customResponseSignerPrivateKeyPEMPath, logLevelEnvVar: customLogLevel, adminHTTPAddrEnvVar: customAdminAddr, adminHTTPReadTimeoutEnvVar: customAdminReadTimeout, publicHTTPReadTimeoutEnvVar: customPublicReadTimeout, publicAuthUpstreamTimeoutEnvVar: customPublicAuthUpstreamTimeout, authenticatedGRPCConnectionTimeoutEnvVar: customGRPCConnectionTimeout, authenticatedGRPCDownstreamTimeoutEnvVar: customGRPCDownstreamTimeout, }, assert: func(t *testing.T, cfg Config) { t.Helper() assert.Equal(t, "debug", cfg.Logging.Level) assert.Equal(t, "127.0.0.1:8081", cfg.AdminHTTP.Addr) assert.Equal(t, 4*time.Second, cfg.AdminHTTP.ReadTimeout) assert.Equal(t, 12*time.Second, cfg.PublicHTTP.ReadTimeout) assert.Equal(t, 1500*time.Millisecond, cfg.PublicHTTP.AuthUpstreamTimeout) assert.Equal(t, 7*time.Second, cfg.AuthenticatedGRPC.ConnectionTimeout) assert.Equal(t, 9*time.Second, cfg.AuthenticatedGRPC.DownstreamTimeout) }, }, { name: "invalid log level", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, sessionEventsRedisStreamEnvVar: customSessionEventsRedisStream, clientEventsRedisStreamEnvVar: customClientEventsRedisStream, responseSignerPrivateKeyPEMPathEnvVar: customResponseSignerPrivateKeyPEMPath, logLevelEnvVar: invalidLogLevel, }, wantErr: logLevelEnvVar + " must be one of", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() restoreEnvs(t, append( append( append( append(append(operationalEnvVars(), authServiceBaseURLEnvVar, userServiceBaseURLEnvVar), sessionCacheRedisEnvVars()...), sessionEventsRedisEnvVars()..., ), clientEventsRedisEnvVars()..., ), responseSignerPrivateKeyPEMPathEnvVar, )...) for envVar, value := range tt.envs { setEnvValue(t, envVar, value) } cfg, err := LoadFromEnv() if tt.wantErr != "" { require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) tt.assert(t, cfg) }) } } func TestLoadFromEnvAuthService(t *testing.T) { t.Parallel() customSessionCacheRedisAddr := new(string) *customSessionCacheRedisAddr = "127.0.0.1:6379" customSessionEventsRedisStream := new(string) *customSessionEventsRedisStream = "gateway:session_events" customClientEventsRedisStream := new(string) *customClientEventsRedisStream = "gateway:client_events" customResponseSignerPrivateKeyPEMPath := new(string) *customResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) invalidRelativeURL := new(string) *invalidRelativeURL = "/authsession" invalidURL := new(string) *invalidURL = "://bad" tests := []struct { name string value *string wantErr string }{ { name: "relative url rejected", value: invalidRelativeURL, wantErr: authServiceBaseURLEnvVar + " must be an absolute URL", }, { name: "malformed url rejected", value: invalidURL, wantErr: "parse " + authServiceBaseURLEnvVar, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() restoreEnvs(t, authServiceBaseURLEnvVar, userServiceBaseURLEnvVar, logLevelEnvVar, sessionCacheRedisAddrEnvVar, sessionEventsRedisStreamEnvVar, clientEventsRedisStreamEnvVar, responseSignerPrivateKeyPEMPathEnvVar, ) setEnvValue(t, authServiceBaseURLEnvVar, tt.value) setEnvValue(t, sessionCacheRedisAddrEnvVar, customSessionCacheRedisAddr) setEnvValue(t, sessionEventsRedisStreamEnvVar, customSessionEventsRedisStream) setEnvValue(t, clientEventsRedisStreamEnvVar, customClientEventsRedisStream) setEnvValue(t, responseSignerPrivateKeyPEMPathEnvVar, customResponseSignerPrivateKeyPEMPath) _, err := LoadFromEnv() require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) }) } } func TestLoadFromEnvUserService(t *testing.T) { t.Parallel() customSessionCacheRedisAddr := new(string) *customSessionCacheRedisAddr = "127.0.0.1:6379" customSessionEventsRedisStream := new(string) *customSessionEventsRedisStream = "gateway:session_events" customClientEventsRedisStream := new(string) *customClientEventsRedisStream = "gateway:client_events" customResponseSignerPrivateKeyPEMPath := new(string) *customResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) invalidRelativeURL := new(string) *invalidRelativeURL = "/user" invalidURL := new(string) *invalidURL = "://bad" tests := []struct { name string value *string wantErr string }{ { name: "relative url rejected", value: invalidRelativeURL, wantErr: userServiceBaseURLEnvVar + " must be an absolute URL", }, { name: "malformed url rejected", value: invalidURL, wantErr: "parse " + userServiceBaseURLEnvVar, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() restoreEnvs(t, authServiceBaseURLEnvVar, userServiceBaseURLEnvVar, logLevelEnvVar, sessionCacheRedisAddrEnvVar, sessionEventsRedisStreamEnvVar, clientEventsRedisStreamEnvVar, responseSignerPrivateKeyPEMPathEnvVar, ) setEnvValue(t, userServiceBaseURLEnvVar, tt.value) setEnvValue(t, sessionCacheRedisAddrEnvVar, customSessionCacheRedisAddr) setEnvValue(t, sessionEventsRedisStreamEnvVar, customSessionEventsRedisStream) setEnvValue(t, clientEventsRedisStreamEnvVar, customClientEventsRedisStream) setEnvValue(t, responseSignerPrivateKeyPEMPathEnvVar, customResponseSignerPrivateKeyPEMPath) _, err := LoadFromEnv() require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) }) } } func TestLoadFromEnvAuthenticatedGRPCAntiAbuse(t *testing.T) { customSessionCacheRedisAddr := new(string) *customSessionCacheRedisAddr = "127.0.0.1:6379" customSessionEventsRedisStream := new(string) *customSessionEventsRedisStream = "gateway:session_events" customClientEventsRedisStream := new(string) *customClientEventsRedisStream = "gateway:client_events" customResponseSignerPrivateKeyPEMPath := new(string) *customResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) customIPRequests := new(string) *customIPRequests = "240" customIPWindow := new(string) *customIPWindow = "2m" customIPBurst := new(string) *customIPBurst = "60" customSessionRequests := new(string) *customSessionRequests = "120" customSessionWindow := new(string) *customSessionWindow = "90s" customSessionBurst := new(string) *customSessionBurst = "30" customUserRequests := new(string) *customUserRequests = "180" customUserWindow := new(string) *customUserWindow = "3m" customUserBurst := new(string) *customUserBurst = "45" customMessageClassRequests := new(string) *customMessageClassRequests = "75" customMessageClassWindow := new(string) *customMessageClassWindow = "45s" customMessageClassBurst := new(string) *customMessageClassBurst = "15" zeroIPRequests := new(string) *zeroIPRequests = "0" tests := []struct { name string ipRequests *string ipWindow *string ipBurst *string sessionRequests *string sessionWindow *string sessionBurst *string userRequests *string userWindow *string userBurst *string messageClassRequests *string messageClassWindow *string messageClassBurst *string want AuthenticatedGRPCAntiAbuseConfig wantErr string }{ { name: "custom authenticated grpc anti abuse config", ipRequests: customIPRequests, ipWindow: customIPWindow, ipBurst: customIPBurst, sessionRequests: customSessionRequests, sessionWindow: customSessionWindow, sessionBurst: customSessionBurst, userRequests: customUserRequests, userWindow: customUserWindow, userBurst: customUserBurst, messageClassRequests: customMessageClassRequests, messageClassWindow: customMessageClassWindow, messageClassBurst: customMessageClassBurst, want: AuthenticatedGRPCAntiAbuseConfig{ IP: AuthenticatedRateLimitConfig{ Requests: 240, Window: 2 * time.Minute, Burst: 60, }, Session: AuthenticatedRateLimitConfig{ Requests: 120, Window: 90 * time.Second, Burst: 30, }, User: AuthenticatedRateLimitConfig{ Requests: 180, Window: 3 * time.Minute, Burst: 45, }, MessageClass: AuthenticatedRateLimitConfig{ Requests: 75, Window: 45 * time.Second, Burst: 15, }, }, }, { name: "zero authenticated grpc ip requests", ipRequests: zeroIPRequests, wantErr: authenticatedGRPCIPRateLimitRequestsEnvVar + " must be positive", }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { restoreEnvs( t, sessionCacheRedisAddrEnvVar, authenticatedGRPCIPRateLimitRequestsEnvVar, authenticatedGRPCIPRateLimitWindowEnvVar, authenticatedGRPCIPRateLimitBurstEnvVar, authenticatedGRPCSessionRateLimitRequestsEnvVar, authenticatedGRPCSessionRateLimitWindowEnvVar, authenticatedGRPCSessionRateLimitBurstEnvVar, authenticatedGRPCUserRateLimitRequestsEnvVar, authenticatedGRPCUserRateLimitWindowEnvVar, authenticatedGRPCUserRateLimitBurstEnvVar, authenticatedGRPCMessageClassRateLimitRequestsEnvVar, authenticatedGRPCMessageClassRateLimitWindowEnvVar, authenticatedGRPCMessageClassRateLimitBurstEnvVar, sessionEventsRedisStreamEnvVar, clientEventsRedisStreamEnvVar, responseSignerPrivateKeyPEMPathEnvVar, ) setEnvValue(t, sessionCacheRedisAddrEnvVar, customSessionCacheRedisAddr) setEnvValue(t, sessionEventsRedisStreamEnvVar, customSessionEventsRedisStream) setEnvValue(t, clientEventsRedisStreamEnvVar, customClientEventsRedisStream) setEnvValue(t, responseSignerPrivateKeyPEMPathEnvVar, customResponseSignerPrivateKeyPEMPath) setEnvValue(t, authenticatedGRPCIPRateLimitRequestsEnvVar, tt.ipRequests) setEnvValue(t, authenticatedGRPCIPRateLimitWindowEnvVar, tt.ipWindow) setEnvValue(t, authenticatedGRPCIPRateLimitBurstEnvVar, tt.ipBurst) setEnvValue(t, authenticatedGRPCSessionRateLimitRequestsEnvVar, tt.sessionRequests) setEnvValue(t, authenticatedGRPCSessionRateLimitWindowEnvVar, tt.sessionWindow) setEnvValue(t, authenticatedGRPCSessionRateLimitBurstEnvVar, tt.sessionBurst) setEnvValue(t, authenticatedGRPCUserRateLimitRequestsEnvVar, tt.userRequests) setEnvValue(t, authenticatedGRPCUserRateLimitWindowEnvVar, tt.userWindow) setEnvValue(t, authenticatedGRPCUserRateLimitBurstEnvVar, tt.userBurst) setEnvValue(t, authenticatedGRPCMessageClassRateLimitRequestsEnvVar, tt.messageClassRequests) setEnvValue(t, authenticatedGRPCMessageClassRateLimitWindowEnvVar, tt.messageClassWindow) setEnvValue(t, authenticatedGRPCMessageClassRateLimitBurstEnvVar, tt.messageClassBurst) cfg, err := LoadFromEnv() if tt.wantErr != "" { require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want, cfg.AuthenticatedGRPC.AntiAbuse) }) } } func TestLoadFromEnvSessionCacheRedis(t *testing.T) { customResponseSignerPrivateKeyPEMPath := new(string) *customResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) customSessionEventsRedisStream := new(string) *customSessionEventsRedisStream = "gateway:session_events" customClientEventsRedisStream := new(string) *customClientEventsRedisStream = "gateway:client_events" customRedisAddr := new(string) *customRedisAddr = "127.0.0.1:6380" customRedisUsername := new(string) *customRedisUsername = "gateway" customRedisPassword := new(string) *customRedisPassword = "secret" customRedisDB := new(string) *customRedisDB = "7" customRedisKeyPrefix := new(string) *customRedisKeyPrefix = "edge:session:" customRedisLookupTimeout := new(string) *customRedisLookupTimeout = "750ms" customRedisTLSEnabled := new(string) *customRedisTLSEnabled = "true" negativeRedisDB := new(string) *negativeRedisDB = "-1" invalidRedisLookupTimeout := new(string) *invalidRedisLookupTimeout = "later" invalidRedisTLSEnabled := new(string) *invalidRedisTLSEnabled = "maybe" tests := []struct { name string envs map[string]*string want SessionCacheRedisConfig wantErr string }{ { name: "custom redis config", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customRedisAddr, sessionCacheRedisUsernameEnvVar: customRedisUsername, sessionCacheRedisPasswordEnvVar: customRedisPassword, sessionCacheRedisDBEnvVar: customRedisDB, sessionCacheRedisKeyPrefixEnvVar: customRedisKeyPrefix, sessionCacheRedisLookupTimeoutEnvVar: customRedisLookupTimeout, sessionCacheRedisTLSEnabledEnvVar: customRedisTLSEnabled, }, want: SessionCacheRedisConfig{ Addr: "127.0.0.1:6380", Username: "gateway", Password: "secret", DB: 7, KeyPrefix: "edge:session:", LookupTimeout: 750 * time.Millisecond, TLSEnabled: true, }, }, { name: "negative redis db", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customRedisAddr, sessionCacheRedisDBEnvVar: negativeRedisDB, }, wantErr: sessionCacheRedisDBEnvVar + " must not be negative", }, { name: "invalid redis lookup timeout", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customRedisAddr, sessionCacheRedisLookupTimeoutEnvVar: invalidRedisLookupTimeout, }, wantErr: "parse " + sessionCacheRedisLookupTimeoutEnvVar, }, { name: "invalid redis tls flag", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customRedisAddr, sessionCacheRedisTLSEnabledEnvVar: invalidRedisTLSEnabled, }, wantErr: "parse " + sessionCacheRedisTLSEnabledEnvVar, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { restoreEnvs(t, append(append(append(sessionCacheRedisEnvVars(), sessionEventsRedisEnvVars()...), clientEventsRedisEnvVars()...), responseSignerPrivateKeyPEMPathEnvVar)...) setEnvValue(t, responseSignerPrivateKeyPEMPathEnvVar, customResponseSignerPrivateKeyPEMPath) setEnvValue(t, sessionEventsRedisStreamEnvVar, customSessionEventsRedisStream) setEnvValue(t, clientEventsRedisStreamEnvVar, customClientEventsRedisStream) for envVar, value := range tt.envs { setEnvValue(t, envVar, value) } cfg, err := LoadFromEnv() if tt.wantErr != "" { require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want, cfg.SessionCacheRedis) }) } } func TestLoadFromEnvReplayRedis(t *testing.T) { customSessionCacheRedisAddr := new(string) *customSessionCacheRedisAddr = "127.0.0.1:6380" customSessionEventsRedisStream := new(string) *customSessionEventsRedisStream = "gateway:session_events" customClientEventsRedisStream := new(string) *customClientEventsRedisStream = "gateway:client_events" customResponseSignerPrivateKeyPEMPath := new(string) *customResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) customReplayRedisKeyPrefix := new(string) *customReplayRedisKeyPrefix = "edge:replay:" customReplayRedisReserveTimeout := new(string) *customReplayRedisReserveTimeout = "500ms" emptyReplayRedisKeyPrefix := new(string) *emptyReplayRedisKeyPrefix = "" invalidReplayRedisReserveTimeout := new(string) *invalidReplayRedisReserveTimeout = "later" tests := []struct { name string envs map[string]*string want ReplayRedisConfig wantErr string }{ { name: "custom replay redis config", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, replayRedisKeyPrefixEnvVar: customReplayRedisKeyPrefix, replayRedisReserveTimeoutEnvVar: customReplayRedisReserveTimeout, }, want: ReplayRedisConfig{ KeyPrefix: "edge:replay:", ReserveTimeout: 500 * time.Millisecond, }, }, { name: "empty replay redis key prefix", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, replayRedisKeyPrefixEnvVar: emptyReplayRedisKeyPrefix, }, wantErr: replayRedisKeyPrefixEnvVar + " must not be empty", }, { name: "invalid replay redis reserve timeout", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, replayRedisReserveTimeoutEnvVar: invalidReplayRedisReserveTimeout, }, wantErr: "parse " + replayRedisReserveTimeoutEnvVar, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { restoreEnvs(t, append(append(append(append(sessionCacheRedisEnvVars(), replayRedisEnvVars()...), sessionEventsRedisEnvVars()...), clientEventsRedisEnvVars()...), responseSignerPrivateKeyPEMPathEnvVar)...) setEnvValue(t, responseSignerPrivateKeyPEMPathEnvVar, customResponseSignerPrivateKeyPEMPath) setEnvValue(t, sessionEventsRedisStreamEnvVar, customSessionEventsRedisStream) setEnvValue(t, clientEventsRedisStreamEnvVar, customClientEventsRedisStream) for envVar, value := range tt.envs { setEnvValue(t, envVar, value) } cfg, err := LoadFromEnv() if tt.wantErr != "" { require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want, cfg.ReplayRedis) }) } } func TestLoadFromEnvSessionEventsRedis(t *testing.T) { customSessionCacheRedisAddr := new(string) *customSessionCacheRedisAddr = "127.0.0.1:6380" customResponseSignerPrivateKeyPEMPath := new(string) *customResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) customClientEventsRedisStream := new(string) *customClientEventsRedisStream = "gateway:client_events" customStream := new(string) *customStream = "edge:session_events" customReadBlockTimeout := new(string) *customReadBlockTimeout = "1500ms" emptyStream := new(string) *emptyStream = "" invalidReadBlockTimeout := new(string) *invalidReadBlockTimeout = "later" tests := []struct { name string envs map[string]*string want SessionEventsRedisConfig wantErr string }{ { name: "custom session events redis config", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, sessionEventsRedisStreamEnvVar: customStream, sessionEventsRedisReadBlockTimeoutEnvVar: customReadBlockTimeout, }, want: SessionEventsRedisConfig{ Stream: "edge:session_events", ReadBlockTimeout: 1500 * time.Millisecond, }, }, { name: "missing session events redis stream", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, }, wantErr: sessionEventsRedisStreamEnvVar + " must not be empty", }, { name: "empty session events redis stream", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, sessionEventsRedisStreamEnvVar: emptyStream, }, wantErr: sessionEventsRedisStreamEnvVar + " must not be empty", }, { name: "invalid session events read block timeout", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, sessionEventsRedisStreamEnvVar: customStream, sessionEventsRedisReadBlockTimeoutEnvVar: invalidReadBlockTimeout, }, wantErr: "parse " + sessionEventsRedisReadBlockTimeoutEnvVar, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { restoreEnvs(t, append(append(append(sessionCacheRedisEnvVars(), sessionEventsRedisEnvVars()...), clientEventsRedisEnvVars()...), responseSignerPrivateKeyPEMPathEnvVar)...) setEnvValue(t, responseSignerPrivateKeyPEMPathEnvVar, customResponseSignerPrivateKeyPEMPath) setEnvValue(t, clientEventsRedisStreamEnvVar, customClientEventsRedisStream) for envVar, value := range tt.envs { setEnvValue(t, envVar, value) } cfg, err := LoadFromEnv() if tt.wantErr != "" { require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want, cfg.SessionEventsRedis) }) } } func TestLoadFromEnvClientEventsRedis(t *testing.T) { customSessionCacheRedisAddr := new(string) *customSessionCacheRedisAddr = "127.0.0.1:6380" customResponseSignerPrivateKeyPEMPath := new(string) *customResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) customSessionEventsRedisStream := new(string) *customSessionEventsRedisStream = "gateway:session_events" customStream := new(string) *customStream = "edge:client_events" customReadBlockTimeout := new(string) *customReadBlockTimeout = "1500ms" emptyStream := new(string) *emptyStream = "" invalidReadBlockTimeout := new(string) *invalidReadBlockTimeout = "later" tests := []struct { name string envs map[string]*string want ClientEventsRedisConfig wantErr string }{ { name: "custom client events redis config", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, clientEventsRedisStreamEnvVar: customStream, clientEventsRedisReadBlockTimeoutEnvVar: customReadBlockTimeout, }, want: ClientEventsRedisConfig{ Stream: "edge:client_events", ReadBlockTimeout: 1500 * time.Millisecond, }, }, { name: "missing client events redis stream", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, }, wantErr: clientEventsRedisStreamEnvVar + " must not be empty", }, { name: "empty client events redis stream", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, clientEventsRedisStreamEnvVar: emptyStream, }, wantErr: clientEventsRedisStreamEnvVar + " must not be empty", }, { name: "invalid client events read block timeout", envs: map[string]*string{ sessionCacheRedisAddrEnvVar: customSessionCacheRedisAddr, clientEventsRedisStreamEnvVar: customStream, clientEventsRedisReadBlockTimeoutEnvVar: invalidReadBlockTimeout, }, wantErr: "parse " + clientEventsRedisReadBlockTimeoutEnvVar, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { restoreEnvs(t, append(append(append(sessionCacheRedisEnvVars(), sessionEventsRedisEnvVars()...), clientEventsRedisEnvVars()...), responseSignerPrivateKeyPEMPathEnvVar)...) setEnvValue(t, responseSignerPrivateKeyPEMPathEnvVar, customResponseSignerPrivateKeyPEMPath) setEnvValue(t, sessionEventsRedisStreamEnvVar, customSessionEventsRedisStream) for envVar, value := range tt.envs { setEnvValue(t, envVar, value) } cfg, err := LoadFromEnv() if tt.wantErr != "" { require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want, cfg.ClientEventsRedis) }) } } func TestLoadFromEnvPublicHTTPAntiAbuse(t *testing.T) { requiredSessionCacheRedisAddr := new(string) *requiredSessionCacheRedisAddr = "127.0.0.1:6379" requiredSessionEventsRedisStream := new(string) *requiredSessionEventsRedisStream = "gateway:session_events" requiredClientEventsRedisStream := new(string) *requiredClientEventsRedisStream = "gateway:client_events" requiredResponseSignerPrivateKeyPEMPath := new(string) *requiredResponseSignerPrivateKeyPEMPath = writeTestResponseSignerPEMFile(t) customPublicAuthMaxBodyBytes := new(string) *customPublicAuthMaxBodyBytes = "4096" customBrowserAssetRequests := new(string) *customBrowserAssetRequests = "150" customBrowserAssetWindow := new(string) *customBrowserAssetWindow = "2m" customConfirmBurst := new(string) *customConfirmBurst = "3" negativePublicAuthMaxBodyBytes := new(string) *negativePublicAuthMaxBodyBytes = "-1" zeroPublicMiscRequests := new(string) *zeroPublicMiscRequests = "0" invalidSendIdentityWindow := new(string) *invalidSendIdentityWindow = "later" tests := []struct { name string envs map[string]*string want PublicHTTPAntiAbuseConfig wantErr string }{ { name: "custom anti abuse config", envs: map[string]*string{ publicAuthMaxBodyBytesEnvVar: customPublicAuthMaxBodyBytes, browserAssetRateLimitRequestsEnvVar: customBrowserAssetRequests, browserAssetRateLimitWindowEnvVar: customBrowserAssetWindow, confirmEmailCodeIdentityRateLimitBurstEnvVar: customConfirmBurst, }, want: func() PublicHTTPAntiAbuseConfig { cfg := DefaultPublicHTTPConfig().AntiAbuse cfg.PublicAuth.MaxBodyBytes = 4096 cfg.BrowserAsset.RateLimit.Requests = 150 cfg.BrowserAsset.RateLimit.Window = 2 * time.Minute cfg.ConfirmEmailCodeIdentity.RateLimit.Burst = 3 return cfg }(), }, { name: "negative public auth max body bytes", envs: map[string]*string{ publicAuthMaxBodyBytesEnvVar: negativePublicAuthMaxBodyBytes, }, wantErr: publicAuthMaxBodyBytesEnvVar + " must not be negative", }, { name: "zero public misc requests", envs: map[string]*string{ publicMiscRateLimitRequestsEnvVar: zeroPublicMiscRequests, }, wantErr: publicMiscRateLimitRequestsEnvVar + " must be positive", }, { name: "invalid send identity window", envs: map[string]*string{ sendEmailCodeIdentityRateLimitWindowEnvVar: invalidSendIdentityWindow, }, wantErr: "parse " + sendEmailCodeIdentityRateLimitWindowEnvVar, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { restoreEnvs(t, append(append(append(append(publicAntiAbuseEnvVars(), sessionCacheRedisAddrEnvVar), sessionEventsRedisEnvVars()...), clientEventsRedisEnvVars()...), responseSignerPrivateKeyPEMPathEnvVar)...) setEnvValue(t, sessionCacheRedisAddrEnvVar, requiredSessionCacheRedisAddr) setEnvValue(t, sessionEventsRedisStreamEnvVar, requiredSessionEventsRedisStream) setEnvValue(t, clientEventsRedisStreamEnvVar, requiredClientEventsRedisStream) setEnvValue(t, responseSignerPrivateKeyPEMPathEnvVar, requiredResponseSignerPrivateKeyPEMPath) for envVar, value := range tt.envs { setEnvValue(t, envVar, value) } cfg, err := LoadFromEnv() if tt.wantErr != "" { require.Error(t, err) require.ErrorContains(t, err, tt.wantErr) return } require.NoError(t, err) assert.Equal(t, tt.want, cfg.PublicHTTP.AntiAbuse) }) } } // restoreEnv resets envVar after the test mutates process-wide environment // state. func restoreEnv(t *testing.T, envVar string) { t.Helper() previousValue, hadPreviousValue := os.LookupEnv(envVar) t.Cleanup(func() { var err error if hadPreviousValue { err = os.Setenv(envVar, previousValue) } else { err = os.Unsetenv(envVar) } require.NoError(t, err) }) } // setEnvValue updates envVar to value or unsets it when value is nil. func setEnvValue(t *testing.T, envVar string, value *string) { t.Helper() var err error if value == nil { err = os.Unsetenv(envVar) } else { err = os.Setenv(envVar, *value) } require.NoError(t, err) } func restoreEnvs(t *testing.T, envVars ...string) { t.Helper() configEnvMu.Lock() t.Cleanup(configEnvMu.Unlock) for _, envVar := range envVars { restoreEnv(t, envVar) } } func publicAntiAbuseEnvVars() []string { return []string{ publicAuthMaxBodyBytesEnvVar, publicAuthRateLimitRequestsEnvVar, publicAuthRateLimitWindowEnvVar, publicAuthRateLimitBurstEnvVar, browserBootstrapMaxBodyBytesEnvVar, browserBootstrapRateLimitRequestsEnvVar, browserBootstrapRateLimitWindowEnvVar, browserBootstrapRateLimitBurstEnvVar, browserAssetMaxBodyBytesEnvVar, browserAssetRateLimitRequestsEnvVar, browserAssetRateLimitWindowEnvVar, browserAssetRateLimitBurstEnvVar, publicMiscMaxBodyBytesEnvVar, publicMiscRateLimitRequestsEnvVar, publicMiscRateLimitWindowEnvVar, publicMiscRateLimitBurstEnvVar, sendEmailCodeIdentityRateLimitRequestsEnvVar, sendEmailCodeIdentityRateLimitWindowEnvVar, sendEmailCodeIdentityRateLimitBurstEnvVar, confirmEmailCodeIdentityRateLimitRequestsEnvVar, confirmEmailCodeIdentityRateLimitWindowEnvVar, confirmEmailCodeIdentityRateLimitBurstEnvVar, } } func operationalEnvVars() []string { return []string{ logLevelEnvVar, publicHTTPAddrEnvVar, publicHTTPReadHeaderTimeoutEnvVar, publicHTTPReadTimeoutEnvVar, publicHTTPIdleTimeoutEnvVar, publicAuthUpstreamTimeoutEnvVar, authServiceBaseURLEnvVar, adminHTTPAddrEnvVar, adminHTTPReadHeaderTimeoutEnvVar, adminHTTPReadTimeoutEnvVar, adminHTTPIdleTimeoutEnvVar, authenticatedGRPCAddrEnvVar, authenticatedGRPCConnectionTimeoutEnvVar, authenticatedGRPCDownstreamTimeoutEnvVar, authenticatedGRPCFreshnessWindowEnvVar, } } func sessionCacheRedisEnvVars() []string { return []string{ sessionCacheRedisAddrEnvVar, sessionCacheRedisUsernameEnvVar, sessionCacheRedisPasswordEnvVar, sessionCacheRedisDBEnvVar, sessionCacheRedisKeyPrefixEnvVar, sessionCacheRedisLookupTimeoutEnvVar, sessionCacheRedisTLSEnabledEnvVar, } } func replayRedisEnvVars() []string { return []string{ replayRedisKeyPrefixEnvVar, replayRedisReserveTimeoutEnvVar, } } func sessionEventsRedisEnvVars() []string { return []string{ sessionEventsRedisStreamEnvVar, sessionEventsRedisReadBlockTimeoutEnvVar, } } func clientEventsRedisEnvVars() []string { return []string{ clientEventsRedisStreamEnvVar, clientEventsRedisReadBlockTimeoutEnvVar, } } func writeTestResponseSignerPEMFile(t *testing.T) string { t.Helper() seed := sha256.Sum256([]byte("gateway-config-test-response-signer")) privateKey := ed25519.NewKeyFromSeed(seed[:]) encodedPrivateKey, err := x509.MarshalPKCS8PrivateKey(privateKey) require.NoError(t, err) path := filepath.Join(t.TempDir(), "response-signer.pem") err = os.WriteFile(path, pem.EncodeToMemory(&pem.Block{ Type: "PRIVATE KEY", Bytes: encodedPrivateKey, }), 0o600) require.NoError(t, err) return path }