fix: harden login for MLPS requirements
This commit is contained in:
@@ -44,9 +44,15 @@ type Config struct {
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Port int `mapstructure:"port"`
|
||||
Mode string `mapstructure:"mode"`
|
||||
TrustedProxies []string `mapstructure:"trusted_proxies"`
|
||||
Port int `mapstructure:"port"`
|
||||
Mode string `mapstructure:"mode"`
|
||||
TrustedProxies []string `mapstructure:"trusted_proxies"`
|
||||
AllowedOrigins []string `mapstructure:"allowed_origins"`
|
||||
SecurityHeaders SecurityHeaders `mapstructure:"security_headers"`
|
||||
}
|
||||
|
||||
type SecurityHeaders struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
}
|
||||
|
||||
type DatabaseConfig struct {
|
||||
@@ -296,13 +302,20 @@ type JWTConfig struct {
|
||||
}
|
||||
|
||||
type AuthConfig struct {
|
||||
LoginGuard LoginGuardConfig `mapstructure:"login_guard"`
|
||||
LoginGuard LoginGuardConfig `mapstructure:"login_guard"`
|
||||
PasswordCipher PasswordCipherConfig `mapstructure:"password_cipher"`
|
||||
}
|
||||
|
||||
type LoginGuardConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
}
|
||||
|
||||
type PasswordCipherConfig struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
KeyID string `mapstructure:"key_id"`
|
||||
PrivateKeyPEM string `mapstructure:"private_key_pem"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `mapstructure:"level"`
|
||||
Format string `mapstructure:"format"`
|
||||
@@ -498,6 +511,11 @@ func decodeResolvedConfig(configFile, localConfigFile string) (*Config, error) {
|
||||
}
|
||||
|
||||
func applyConfigDefaults(settings map[string]any) {
|
||||
serverSettings := ensureMapSetting(settings, "server")
|
||||
securityHeadersSettings := ensureMapSetting(serverSettings, "security_headers")
|
||||
if _, ok := securityHeadersSettings["enabled"]; !ok {
|
||||
securityHeadersSettings["enabled"] = true
|
||||
}
|
||||
cacheSettings := ensureMapSetting(settings, "cache")
|
||||
if _, ok := cacheSettings["metrics_enabled"]; !ok {
|
||||
cacheSettings["metrics_enabled"] = true
|
||||
@@ -540,11 +558,12 @@ func NormalizeServerConfig(cfg *ServerConfig) {
|
||||
return
|
||||
}
|
||||
cfg.Mode = strings.TrimSpace(cfg.Mode)
|
||||
cfg.AllowedOrigins = compactStringSlice(cfg.AllowedOrigins)
|
||||
if cfg.TrustedProxies == nil {
|
||||
cfg.TrustedProxies = DefaultTrustedProxies()
|
||||
return
|
||||
} else {
|
||||
cfg.TrustedProxies = compactStringSlice(cfg.TrustedProxies)
|
||||
}
|
||||
cfg.TrustedProxies = compactStringSlice(cfg.TrustedProxies)
|
||||
}
|
||||
|
||||
func compactStringSlice(values []string) []string {
|
||||
@@ -964,6 +983,21 @@ func applyEnvOverrides(cfg *Config) {
|
||||
if trustedProxies, ok := lookupNonEmptyEnv("SERVER_TRUSTED_PROXIES"); ok {
|
||||
cfg.Server.TrustedProxies = strings.Split(trustedProxies, ",")
|
||||
}
|
||||
if origins, ok := lookupNonEmptyEnv("SERVER_ALLOWED_ORIGINS"); ok {
|
||||
cfg.Server.AllowedOrigins = strings.Split(origins, ",")
|
||||
}
|
||||
if enabled, ok := lookupBoolEnv("SERVER_SECURITY_HEADERS_ENABLED"); ok {
|
||||
cfg.Server.SecurityHeaders.Enabled = enabled
|
||||
}
|
||||
if enabled, ok := lookupBoolEnv("AUTH_PASSWORD_CIPHER_ENABLED"); ok {
|
||||
cfg.Auth.PasswordCipher.Enabled = enabled
|
||||
}
|
||||
if keyID, ok := lookupNonEmptyEnv("AUTH_PASSWORD_CIPHER_KEY_ID"); ok {
|
||||
cfg.Auth.PasswordCipher.KeyID = keyID
|
||||
}
|
||||
if privateKey, ok := lookupNonEmptyEnv("AUTH_PASSWORD_CIPHER_PRIVATE_KEY_PEM"); ok {
|
||||
cfg.Auth.PasswordCipher.PrivateKeyPEM = privateKey
|
||||
}
|
||||
if path, ok := lookupNonEmptyEnv("IP_REGION_V4_XDB_PATH"); ok {
|
||||
cfg.IPRegion.V4XDBPath = path
|
||||
}
|
||||
|
||||
@@ -168,6 +168,32 @@ server:
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAppliesServerSecurityDefaultsAndOriginOverrides(t *testing.T) {
|
||||
t.Setenv("SERVER_ALLOWED_ORIGINS", "https://admin.example.com, https://ops.example.com")
|
||||
|
||||
configPath := writeTestConfig(t, `
|
||||
server: {}
|
||||
`)
|
||||
|
||||
cfg, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
|
||||
if !cfg.Server.SecurityHeaders.Enabled {
|
||||
t.Fatalf("expected security headers enabled by default")
|
||||
}
|
||||
wantOrigins := []string{"https://admin.example.com", "https://ops.example.com"}
|
||||
if len(cfg.Server.AllowedOrigins) != len(wantOrigins) {
|
||||
t.Fatalf("allowed origins = %#v, want %#v", cfg.Server.AllowedOrigins, wantOrigins)
|
||||
}
|
||||
for i := range wantOrigins {
|
||||
if cfg.Server.AllowedOrigins[i] != wantOrigins[i] {
|
||||
t.Fatalf("allowed origins = %#v, want %#v", cfg.Server.AllowedOrigins, wantOrigins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAppliesSchedulerHTTPDefaultsAndDisableSentinel(t *testing.T) {
|
||||
defaultConfigPath := writeTestConfig(t, `
|
||||
scheduler: {}
|
||||
@@ -358,6 +384,34 @@ jwt:
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadPrefersEnvPasswordCipherSettings(t *testing.T) {
|
||||
t.Setenv("AUTH_PASSWORD_CIPHER_ENABLED", "true")
|
||||
t.Setenv("AUTH_PASSWORD_CIPHER_KEY_ID", "env-key")
|
||||
t.Setenv("AUTH_PASSWORD_CIPHER_PRIVATE_KEY_PEM", "pem-value")
|
||||
|
||||
configPath := writeTestConfig(t, `
|
||||
auth:
|
||||
password_cipher:
|
||||
enabled: false
|
||||
key_id: config-key
|
||||
private_key_pem: config-pem
|
||||
`)
|
||||
|
||||
cfg, err := Load(configPath)
|
||||
if err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
if !cfg.Auth.PasswordCipher.Enabled {
|
||||
t.Fatalf("expected env password cipher enabled")
|
||||
}
|
||||
if cfg.Auth.PasswordCipher.KeyID != "env-key" {
|
||||
t.Fatalf("expected env key id, got %q", cfg.Auth.PasswordCipher.KeyID)
|
||||
}
|
||||
if cfg.Auth.PasswordCipher.PrivateKeyPEM != "pem-value" {
|
||||
t.Fatalf("expected env private key pem, got %q", cfg.Auth.PasswordCipher.PrivateKeyPEM)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadAppliesMembershipDefaults(t *testing.T) {
|
||||
configPath := writeTestConfig(t, `
|
||||
membership: {}
|
||||
|
||||
Reference in New Issue
Block a user