opnsense-portal/internal/config/secrets_test.go
Carsten Abele b47ee11ba4 feat(config): Strict-YAML, Secret-Dateien, Env-Overrides, Dateirechte und Validierung
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBHF4R9EAejDJUMdwr6C68
2026-08-14 09:06:43 +02:00

96 lines
2.6 KiB
Go

package config
import (
"os"
"path/filepath"
"strings"
"testing"
)
func noEnv(string) string { return "" }
func writeConfig(t *testing.T, body string) string {
t.Helper()
dir := t.TempDir()
p := filepath.Join(dir, "config.yaml")
if err := os.WriteFile(p, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Chmod(p, 0o600); err != nil {
t.Fatal(err)
}
return p
}
func TestLoadRejectsWorldReadableConfig(t *testing.T) {
p := writeConfig(t, minimalYAML)
if err := os.Chmod(p, 0o644); err != nil {
t.Fatal(err)
}
if _, err := Load(p, noEnv); err == nil {
t.Fatal("0644-Config muss den Start abbrechen")
}
}
func TestLoadResolvesSecretFiles(t *testing.T) {
dir := t.TempDir()
secret := filepath.Join(dir, "apisecret")
if err := os.WriteFile(secret, []byte(" FROM-FILE\n"), 0o600); err != nil {
t.Fatal(err)
}
if err := os.Chmod(secret, 0o600); err != nil {
t.Fatal(err)
}
body := strings.Replace(minimalYAML,
` api_secret: "SECRET"`, ` api_secret_file: "`+secret+`"`, 1)
cfg, err := Load(writeConfig(t, body), noEnv)
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.OPNsense.APISecret != "FROM-FILE" {
t.Errorf("APISecret = %q, want FROM-FILE (getrimmt)", cfg.OPNsense.APISecret)
}
}
func TestLoadRejectsLooseSecretFile(t *testing.T) {
dir := t.TempDir()
secret := filepath.Join(dir, "apisecret")
if err := os.WriteFile(secret, []byte("x"), 0o644); err != nil {
t.Fatal(err)
}
if err := os.Chmod(secret, 0o644); err != nil {
t.Fatal(err)
}
body := strings.Replace(minimalYAML,
` api_secret: "SECRET"`, ` api_secret_file: "`+secret+`"`, 1)
if _, err := Load(writeConfig(t, body), noEnv); err == nil {
t.Fatal("Secret-Datei mit 0644 muss abgelehnt werden")
}
}
func TestLoadEnvOverridesEverything(t *testing.T) {
env := map[string]string{
"VPNPORTAL_OPNSENSE_API_SECRET": "FROM-ENV",
"VPNPORTAL_AD_BIND_PASSWORD": "ENV-PW",
}
cfg, err := Load(writeConfig(t, minimalYAML), func(k string) string { return env[k] })
if err != nil {
t.Fatalf("Load: %v", err)
}
if cfg.OPNsense.APISecret != "FROM-ENV" {
t.Errorf("APISecret = %q, want FROM-ENV", cfg.OPNsense.APISecret)
}
if cfg.AD.BindPassword != "ENV-PW" {
t.Errorf("BindPassword = %q, want ENV-PW", cfg.AD.BindPassword)
}
}
func TestLoadRejectsBothInlineAndFile(t *testing.T) {
body := strings.Replace(minimalYAML,
` api_secret: "SECRET"`,
" api_secret: \"SECRET\"\n api_secret_file: \"/tmp/x\"", 1)
_, err := Load(writeConfig(t, body), noEnv)
if err == nil || !strings.Contains(err.Error(), "api_secret") {
t.Fatalf("gleichzeitig gesetzte api_secret/api_secret_file müssen abgelehnt werden, got: %v", err)
}
}