1277 lines
41 KiB
Go
1277 lines
41 KiB
Go
package config
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"encoding/pem"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
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"
|
|
|
|
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
|
|
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 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,
|
|
authenticatedGRPCAddrEnvVar,
|
|
authenticatedGRPCFreshnessWindowEnvVar,
|
|
sessionCacheRedisAddrEnvVar,
|
|
sessionEventsRedisStreamEnvVar,
|
|
clientEventsRedisStreamEnvVar,
|
|
responseSignerPrivateKeyPEMPathEnvVar,
|
|
)
|
|
|
|
setEnvValue(t, shutdownTimeoutEnvVar, tt.shutdownTimeout)
|
|
setEnvValue(t, publicHTTPAddrEnvVar, tt.publicHTTPAddr)
|
|
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(operationalEnvVars(), 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 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()
|
|
|
|
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,
|
|
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
|
|
}
|