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
109
internal/config/config.go
Normal file
109
internal/config/config.go
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue