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:
Carsten Abele 2026-08-14 09:06:43 +02:00
parent fbfff90b38
commit b47ee11ba4
10 changed files with 777 additions and 0 deletions

109
internal/config/config.go Normal file
View file

@ -0,0 +1,109 @@
// Package config lädt und validiert die Portal-Konfiguration.
package config
import (
"fmt"
"time"
"gopkg.in/yaml.v3"
)
// Duration erlaubt Dauerangaben als String ("10m") in YAML.
type Duration time.Duration
func (d *Duration) UnmarshalYAML(node *yaml.Node) error {
var s string
if err := node.Decode(&s); err != nil {
return fmt.Errorf("Dauer muss eine Zeichenkette sein (z. B. \"10m\"): %w", err)
}
parsed, err := time.ParseDuration(s)
if err != nil {
return fmt.Errorf("ungültige Dauer %q (erwartet z. B. \"10m\", \"90s\"): %w", s, err)
}
if parsed <= 0 {
return fmt.Errorf("Dauer %q muss positiv sein", s)
}
*d = Duration(parsed)
return nil
}
func (d Duration) String() string { return time.Duration(d).String() }
type Config struct {
Portal PortalConfig `yaml:"portal"`
OPNsense OPNsenseConfig `yaml:"opnsense"`
AD ADConfig `yaml:"ad"`
Matching MatchingConfig `yaml:"matching"`
Logging LoggingConfig `yaml:"logging"`
}
type PortalConfig struct {
Listen string `yaml:"listen"`
TLSCert string `yaml:"tls_cert"`
TLSKey string `yaml:"tls_key"`
SessionTTL Duration `yaml:"session_ttl"`
Title string `yaml:"title"`
LogoFile string `yaml:"logo_file"`
SupportContact string `yaml:"support_contact"`
UpdateCheck bool `yaml:"update_check"`
}
type OPNsenseConfig struct {
URL string `yaml:"url"`
APIKey string `yaml:"api_key"`
APIKeyFile string `yaml:"api_key_file"`
APISecret string `yaml:"api_secret"`
APISecretFile string `yaml:"api_secret_file"`
CAFile string `yaml:"ca_file"`
InsecureSkipVerify bool `yaml:"insecure_skip_verify"`
}
type ADConfig struct {
Domain string `yaml:"domain"`
BaseDN string `yaml:"base_dn"`
Servers []string `yaml:"servers"`
Port int `yaml:"port"`
TLSMode string `yaml:"tls_mode"` // "ldaps" (Default) oder "starttls"
BindUser string `yaml:"bind_user"`
BindPassword string `yaml:"bind_password"`
BindPasswordFile string `yaml:"bind_password_file"`
VPNGroup string `yaml:"vpn_group"`
CAFile string `yaml:"ca_file"`
Timeout Duration `yaml:"timeout"`
}
type MatchingConfig struct {
CNPattern string `yaml:"cn_pattern"`
CNRegex string `yaml:"cn_regex"`
}
type LoggingConfig struct {
Level string `yaml:"level"`
AuditLog string `yaml:"audit_log"`
MaxSizeMB int `yaml:"max_size_mb"`
MaxBackups int `yaml:"max_backups"`
Compress bool `yaml:"compress"`
}
// Defaults liefert eine Config mit allen Vorgabewerten.
func Defaults() *Config {
return &Config{
Portal: PortalConfig{
Listen: "0.0.0.0:8443",
SessionTTL: Duration(10 * time.Minute),
Title: "VPN-Portal",
},
AD: ADConfig{
Port: 636,
TLSMode: "ldaps",
Timeout: Duration(8 * time.Second),
},
Matching: MatchingConfig{CNPattern: "{username}"},
Logging: LoggingConfig{
Level: "info",
MaxSizeMB: 50,
MaxBackups: 5,
Compress: true,
},
}
}