移除字段加密硬编码主密钥,改为 FIELD_ENCRYPTION_KEY 注入。 保留 FIELD_ENCRYPTION_LEGACY_KEY 透明回退旧密文,新写入统一使用主密钥;生产环境校验主密钥和 legacy 密钥长度、占位符及相等关系,并统一生产环境判断口径。 补充配置与旧密文兼容回归测试。
105 lines
4.0 KiB
Go
105 lines
4.0 KiB
Go
package config
|
|
|
|
import (
|
|
"os"
|
|
"testing"
|
|
)
|
|
|
|
const historicKey = "hfb-sys-2024-secret-key-32bytes!"
|
|
|
|
func TestIsProductionEnv(t *testing.T) {
|
|
cases := []struct {
|
|
in string
|
|
want bool
|
|
}{
|
|
{"production", true},
|
|
{"PRODUCTION", true},
|
|
{" production ", true},
|
|
{"development", false},
|
|
{"", false},
|
|
{"staging", false},
|
|
}
|
|
for _, c := range cases {
|
|
if got := IsProductionEnv(c.in); got != c.want {
|
|
t.Fatalf("IsProductionEnv(%q) = %v, want %v", c.in, got, c.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestFieldEncryptionLegacyKeyNonProductionDefaultsToHistoric 验证非生产环境未设置 legacy 时
|
|
// 回退到历史硬编码密钥(开发态零配置兼容旧密文)。
|
|
func TestFieldEncryptionLegacyKeyNonProductionDefaultsToHistoric(t *testing.T) {
|
|
unsetEnv(t, "FIELD_ENCRYPTION_LEGACY_KEY")
|
|
if got := fieldEncryptionLegacyKey("development"); got != historicKey {
|
|
t.Fatalf("non-production default legacy = %q, want %q", got, historicKey)
|
|
}
|
|
}
|
|
|
|
// TestFieldEncryptionLegacyKeyNonProductionExplicitOverride 验证非生产环境显式设置 legacy 时用该值。
|
|
func TestFieldEncryptionLegacyKeyNonProductionExplicitOverride(t *testing.T) {
|
|
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "explicit-legacy-16bytes!")
|
|
if got := fieldEncryptionLegacyKey("development"); got != "explicit-legacy-16bytes!" {
|
|
t.Fatalf("non-production explicit legacy = %q, want explicit-legacy-16bytes!", got)
|
|
}
|
|
}
|
|
|
|
// TestFieldEncryptionLegacyKeyProductionEmptyDisablesFallback 验证生产环境未设置/留空 legacy
|
|
// 返回空(禁用回退),而不是注入历史硬编码密钥。
|
|
// 这是密钥轮换闭环的关键:删除 env 必须真正关闭旧密钥。
|
|
func TestFieldEncryptionLegacyKeyProductionEmptyDisablesFallback(t *testing.T) {
|
|
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "")
|
|
if got := fieldEncryptionLegacyKey("production"); got != "" {
|
|
t.Fatalf("production empty legacy = %q, want empty (fallback disabled); injecting historic key breaks rotation closure", got)
|
|
}
|
|
}
|
|
|
|
// TestFieldEncryptionLegacyKeyProductionExplicitValue 验证生产环境显式设置 legacy 时用该值
|
|
// (轮换期间保留旧密钥解密存量)。
|
|
func TestFieldEncryptionLegacyKeyProductionExplicitValue(t *testing.T) {
|
|
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "prod-old-key-32bytes-0123456789")
|
|
if got := fieldEncryptionLegacyKey("production"); got != "prod-old-key-32bytes-0123456789" {
|
|
t.Fatalf("production explicit legacy = %q, want prod-old-key-32bytes-0123456789", got)
|
|
}
|
|
}
|
|
|
|
// TestValidateProductionSecurityRejectsPrimaryEqualsLegacy 验证生产环境 primary 等于 legacy 时报错。
|
|
func TestValidateProductionSecurityRejectsPrimaryEqualsLegacy(t *testing.T) {
|
|
t.Setenv("APP_ENV", "production")
|
|
t.Setenv("JWT_SECRET", "a-very-long-random-jwt-secret-at-least-32-bytes!!")
|
|
t.Setenv("PAYMENT_CONFIG_ENCRYPTION_KEY", "0123456789abcdef0123456789abcdef")
|
|
t.Setenv("FIELD_ENCRYPTION_KEY", "0123456789abcdef0123456789abcdef")
|
|
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "0123456789abcdef0123456789abcdef")
|
|
cfg := Load()
|
|
if err := cfg.ValidateProductionSecurity(); err == nil {
|
|
t.Fatal("ValidateProductionSecurity should reject FIELD_ENCRYPTION_KEY == LEGACY_KEY")
|
|
}
|
|
}
|
|
|
|
// TestValidateProductionSecurityRejectsInvalidLegacyKey 验证生产环境显式设置 legacy 时也校验长度。
|
|
func TestValidateProductionSecurityRejectsInvalidLegacyKey(t *testing.T) {
|
|
t.Setenv("APP_ENV", "PRODUCTION")
|
|
t.Setenv("JWT_SECRET", "a-very-long-random-jwt-secret-at-least-32-bytes!!")
|
|
t.Setenv("PAYMENT_CONFIG_ENCRYPTION_KEY", "0123456789abcdef0123456789abcdef")
|
|
t.Setenv("FIELD_ENCRYPTION_KEY", "abcdef0123456789abcdef0123456789")
|
|
t.Setenv("FIELD_ENCRYPTION_LEGACY_KEY", "too-short")
|
|
cfg := Load()
|
|
if err := cfg.ValidateProductionSecurity(); err == nil {
|
|
t.Fatal("ValidateProductionSecurity should reject invalid FIELD_ENCRYPTION_LEGACY_KEY")
|
|
}
|
|
}
|
|
|
|
func unsetEnv(t *testing.T, key string) {
|
|
t.Helper()
|
|
old, ok := os.LookupEnv(key)
|
|
if err := os.Unsetenv(key); err != nil {
|
|
t.Fatalf("unset %s: %v", key, err)
|
|
}
|
|
t.Cleanup(func() {
|
|
if ok {
|
|
_ = os.Setenv(key, old)
|
|
return
|
|
}
|
|
_ = os.Unsetenv(key)
|
|
})
|
|
}
|