Lots more changes
This commit is contained in:
+165
-11
@@ -1,6 +1,7 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/netip"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -37,8 +38,17 @@ func TestValidateAppliesDefaultsAndParsedFields(t *testing.T) {
|
||||
if cfg.Server.MaxHeaderBytes != 1<<20 {
|
||||
t.Fatalf("MaxHeaderBytes = %d, want %d", cfg.Server.MaxHeaderBytes, 1<<20)
|
||||
}
|
||||
if cfg.Limits.DefaultTTLDuration != time.Hour {
|
||||
t.Fatalf("DefaultTTLDuration = %s, want 1h", cfg.Limits.DefaultTTLDuration)
|
||||
if !cfg.Server.AccessLog.Enabled {
|
||||
t.Fatalf("AccessLog.Enabled = %v, want true", cfg.Server.AccessLog.Enabled)
|
||||
}
|
||||
if cfg.Server.AccessLog.MaxSizeBytes != 100*1024*1024 {
|
||||
t.Fatalf("AccessLog.MaxSizeBytes = %d, want %d", cfg.Server.AccessLog.MaxSizeBytes, int64(100*1024*1024))
|
||||
}
|
||||
if cfg.Server.AccessLog.MaxBackups != 5 {
|
||||
t.Fatalf("AccessLog.MaxBackups = %d, want 5", cfg.Server.AccessLog.MaxBackups)
|
||||
}
|
||||
if cfg.Limits.DefaultTTLDuration != 15*time.Minute {
|
||||
t.Fatalf("DefaultTTLDuration = %s, want 15m", cfg.Limits.DefaultTTLDuration)
|
||||
}
|
||||
if cfg.Limits.RawCacheMaxBytes != 200*1024*1024 {
|
||||
t.Fatalf("RawCacheMaxBytes = %d, want %d", cfg.Limits.RawCacheMaxBytes, int64(200*1024*1024))
|
||||
@@ -49,6 +59,9 @@ func TestValidateAppliesDefaultsAndParsedFields(t *testing.T) {
|
||||
if cfg.Storage.CleanupIntervalParsed != time.Minute {
|
||||
t.Fatalf("CleanupIntervalParsed = %s, want 1m", cfg.Storage.CleanupIntervalParsed)
|
||||
}
|
||||
if len(cfg.Storage.EncryptionKeyBytes) != 32 {
|
||||
t.Fatalf("EncryptionKeyBytes length = %d, want 32", len(cfg.Storage.EncryptionKeyBytes))
|
||||
}
|
||||
if len(cfg.Security.AllowedPrefixes) != 1 {
|
||||
t.Fatalf("AllowedPrefixes len = %d, want 1", len(cfg.Security.AllowedPrefixes))
|
||||
}
|
||||
@@ -63,6 +76,7 @@ func TestLoadParsesConfiguredFields(t *testing.T) {
|
||||
tmp := t.TempDir()
|
||||
dataDir := filepath.Join(tmp, "store")
|
||||
configPath := filepath.Join(tmp, "config.yaml")
|
||||
keyPath := filepath.Join(tmp, "scratchbox.key")
|
||||
content := `
|
||||
server:
|
||||
listen_addr: "127.0.0.1:9090"
|
||||
@@ -71,6 +85,11 @@ server:
|
||||
write_timeout: "35s"
|
||||
idle_timeout: "90s"
|
||||
max_header_bytes: 2097152
|
||||
access_log:
|
||||
enabled: true
|
||||
file_path: "` + filepath.Join(tmp, "access.log") + `"
|
||||
max_size: "2MiB"
|
||||
max_backups: 7
|
||||
limits:
|
||||
max_upload_size: "2MiB"
|
||||
default_ttl: "2h"
|
||||
@@ -79,6 +98,7 @@ limits:
|
||||
storage:
|
||||
data_dir: "` + dataDir + `"
|
||||
cleanup_interval: "45s"
|
||||
encryption_key_file: "` + filepath.Base(keyPath) + `"
|
||||
security:
|
||||
allowed_ips:
|
||||
- "127.0.0.1"
|
||||
@@ -86,7 +106,15 @@ security:
|
||||
trust_proxy_headers: true
|
||||
trusted_proxy_ips:
|
||||
- "10.0.0.0/8"
|
||||
rate_limit:
|
||||
rate_limit_ui:
|
||||
enabled: true
|
||||
requests_per_minute: 12
|
||||
burst: 4
|
||||
rate_limit_api_read:
|
||||
enabled: true
|
||||
requests_per_minute: 12
|
||||
burst: 4
|
||||
rate_limit_api_write:
|
||||
enabled: true
|
||||
requests_per_minute: 12
|
||||
burst: 4
|
||||
@@ -94,6 +122,9 @@ security:
|
||||
if err := os.WriteFile(configPath, []byte(content), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile() error = %v", err)
|
||||
}
|
||||
if err := os.WriteFile(keyPath, []byte("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n"), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile(key) error = %v", err)
|
||||
}
|
||||
|
||||
cfg, err := Load(configPath)
|
||||
if err != nil {
|
||||
@@ -118,6 +149,15 @@ security:
|
||||
if cfg.Server.MaxHeaderBytes != 2*1024*1024 {
|
||||
t.Fatalf("MaxHeaderBytes = %d, want %d", cfg.Server.MaxHeaderBytes, 2*1024*1024)
|
||||
}
|
||||
if !cfg.Server.AccessLog.Enabled {
|
||||
t.Fatalf("AccessLog.Enabled = false, want true")
|
||||
}
|
||||
if cfg.Server.AccessLog.MaxSizeBytes != 2*1024*1024 {
|
||||
t.Fatalf("AccessLog.MaxSizeBytes = %d, want %d", cfg.Server.AccessLog.MaxSizeBytes, int64(2*1024*1024))
|
||||
}
|
||||
if cfg.Server.AccessLog.MaxBackups != 7 {
|
||||
t.Fatalf("AccessLog.MaxBackups = %d, want 7", cfg.Server.AccessLog.MaxBackups)
|
||||
}
|
||||
if cfg.Limits.MaxUploadSizeBytes != 2*1024*1024 {
|
||||
t.Fatalf("MaxUploadSizeBytes = %d, want %d", cfg.Limits.MaxUploadSizeBytes, int64(2*1024*1024))
|
||||
}
|
||||
@@ -133,11 +173,23 @@ security:
|
||||
if cfg.Storage.CleanupIntervalParsed != 45*time.Second {
|
||||
t.Fatalf("CleanupIntervalParsed = %s, want 45s", cfg.Storage.CleanupIntervalParsed)
|
||||
}
|
||||
if len(cfg.Storage.EncryptionKeyBytes) != 32 {
|
||||
t.Fatalf("EncryptionKeyBytes length = %d, want 32", len(cfg.Storage.EncryptionKeyBytes))
|
||||
}
|
||||
if cfg.Storage.EncryptionKeyBytes[0] != 0 {
|
||||
t.Fatalf("EncryptionKeyBytes[0] = %d, want 0", cfg.Storage.EncryptionKeyBytes[0])
|
||||
}
|
||||
if !cfg.Security.TrustProxyHeaders {
|
||||
t.Fatalf("TrustProxyHeaders = false, want true")
|
||||
}
|
||||
if cfg.Security.RateLimit.RequestsPerMinute != 12 || cfg.Security.RateLimit.Burst != 4 {
|
||||
t.Fatalf("unexpected rate_limit values: %+v", cfg.Security.RateLimit)
|
||||
if cfg.Security.RateLimitUI.RequestsPerMinute != 12 || cfg.Security.RateLimitUI.Burst != 4 {
|
||||
t.Fatalf("unexpected rate_limit_ui values: %+v", cfg.Security.RateLimitUI)
|
||||
}
|
||||
if cfg.Security.RateLimitAPIRead.RequestsPerMinute != 12 || cfg.Security.RateLimitAPIRead.Burst != 4 {
|
||||
t.Fatalf("unexpected rate_limit_api_read values: %+v", cfg.Security.RateLimitAPIRead)
|
||||
}
|
||||
if cfg.Security.RateLimitAPIWrite.RequestsPerMinute != 12 || cfg.Security.RateLimitAPIWrite.Burst != 4 {
|
||||
t.Fatalf("unexpected rate_limit_api_write values: %+v", cfg.Security.RateLimitAPIWrite)
|
||||
}
|
||||
if len(cfg.Security.AllowedPrefixes) != 2 {
|
||||
t.Fatalf("AllowedPrefixes len = %d, want 2", len(cfg.Security.AllowedPrefixes))
|
||||
@@ -204,6 +256,20 @@ func TestValidateRejectsInvalidValues(t *testing.T) {
|
||||
},
|
||||
wantErr: "server.max_header_bytes",
|
||||
},
|
||||
{
|
||||
name: "invalid access log max size",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Server.AccessLog.MaxSize = "oops"
|
||||
},
|
||||
wantErr: "server.access_log.max_size",
|
||||
},
|
||||
{
|
||||
name: "invalid access log max backups",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Server.AccessLog.MaxBackups = 0
|
||||
},
|
||||
wantErr: "server.access_log.max_backups",
|
||||
},
|
||||
{
|
||||
name: "invalid ttl parse",
|
||||
mutate: func(cfg *Config) {
|
||||
@@ -255,11 +321,25 @@ func TestValidateRejectsInvalidValues(t *testing.T) {
|
||||
wantErr: "security.trusted_proxy_ips is required",
|
||||
},
|
||||
{
|
||||
name: "invalid rate limit burst",
|
||||
name: "invalid ui rate limit burst",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Security.RateLimit.Burst = 0
|
||||
cfg.Security.RateLimitUI.Burst = 0
|
||||
},
|
||||
wantErr: "security.rate_limit.burst",
|
||||
wantErr: "security.rate_limit_ui.burst",
|
||||
},
|
||||
{
|
||||
name: "invalid api read rate limit burst",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Security.RateLimitAPIRead.Burst = 0
|
||||
},
|
||||
wantErr: "security.rate_limit_api_read.burst",
|
||||
},
|
||||
{
|
||||
name: "invalid api write rate limit burst",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Security.RateLimitAPIWrite.Burst = 0
|
||||
},
|
||||
wantErr: "security.rate_limit_api_write.burst",
|
||||
},
|
||||
{
|
||||
name: "data dir required",
|
||||
@@ -283,11 +363,39 @@ func TestValidateRejectsInvalidValues(t *testing.T) {
|
||||
wantErr: "storage.cleanup_interval",
|
||||
},
|
||||
{
|
||||
name: "invalid rate limit rpm",
|
||||
name: "encryption key file required",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Security.RateLimit.RequestsPerMinute = 0
|
||||
cfg.Storage.EncryptionKeyFile = " "
|
||||
},
|
||||
wantErr: "security.rate_limit.requests_per_minute",
|
||||
wantErr: "storage.encryption_key_file is required",
|
||||
},
|
||||
{
|
||||
name: "encryption key bytes invalid length",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Storage.EncryptionKeyBytes = []byte("short")
|
||||
},
|
||||
wantErr: "storage.encryption_key_file must decode to 32 bytes",
|
||||
},
|
||||
{
|
||||
name: "invalid ui rate limit rpm",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Security.RateLimitUI.RequestsPerMinute = 0
|
||||
},
|
||||
wantErr: "security.rate_limit_ui.requests_per_minute",
|
||||
},
|
||||
{
|
||||
name: "invalid api read rate limit rpm",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Security.RateLimitAPIRead.RequestsPerMinute = 0
|
||||
},
|
||||
wantErr: "security.rate_limit_api_read.requests_per_minute",
|
||||
},
|
||||
{
|
||||
name: "invalid api write rate limit rpm",
|
||||
mutate: func(cfg *Config) {
|
||||
cfg.Security.RateLimitAPIWrite.RequestsPerMinute = 0
|
||||
},
|
||||
wantErr: "security.rate_limit_api_write.requests_per_minute",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -328,6 +436,29 @@ func TestLoadErrors(t *testing.T) {
|
||||
if err == nil || !strings.Contains(err.Error(), "parse config yaml") {
|
||||
t.Fatalf("Load() error = %v, want yaml parse error", err)
|
||||
}
|
||||
|
||||
validConfigPath := filepath.Join(tmp, "valid.yaml")
|
||||
validConfig := `
|
||||
storage:
|
||||
data_dir: "` + filepath.Join(tmp, "store") + `"
|
||||
cleanup_interval: "10s"
|
||||
`
|
||||
if err := os.WriteFile(validConfigPath, []byte(validConfig), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile(valid config) error = %v", err)
|
||||
}
|
||||
_, err = Load(validConfigPath)
|
||||
if err == nil || !strings.Contains(err.Error(), "read storage.encryption_key_file") {
|
||||
t.Fatalf("Load() error = %v, want missing key file error", err)
|
||||
}
|
||||
|
||||
keyPath := filepath.Join(tmp, "config.key")
|
||||
if err := os.WriteFile(keyPath, []byte("not-base64"), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile(invalid key) error = %v", err)
|
||||
}
|
||||
_, err = Load(validConfigPath)
|
||||
if err == nil || !strings.Contains(err.Error(), "storage.encryption_key_file invalid: invalid base64") {
|
||||
t.Fatalf("Load() error = %v, want invalid key base64 error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseAllowedPrefixesTrimsAndParses(t *testing.T) {
|
||||
@@ -385,3 +516,26 @@ func TestParseHumanSizeVariants(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateEncryptionKey(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
k1, err := GenerateEncryptionKey()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateEncryptionKey() error = %v", err)
|
||||
}
|
||||
k2, err := GenerateEncryptionKey()
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateEncryptionKey() second error = %v", err)
|
||||
}
|
||||
if k1 == k2 {
|
||||
t.Fatalf("GenerateEncryptionKey() produced duplicate keys")
|
||||
}
|
||||
b1, err := base64.StdEncoding.DecodeString(k1)
|
||||
if err != nil {
|
||||
t.Fatalf("DecodeString(k1) error = %v", err)
|
||||
}
|
||||
if len(b1) != 32 {
|
||||
t.Fatalf("decoded key length = %d, want 32", len(b1))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user