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
This commit is contained in:
parent
fbfff90b38
commit
b47ee11ba4
10 changed files with 777 additions and 0 deletions
96
internal/config/secrets_test.go
Normal file
96
internal/config/secrets_test.go
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue