// Package config handles loading and validating the KeyCape server configuration // from a YAML file. The config path is resolved from the --config flag or the // KEYCAPE_CONFIG environment variable. package config import ( "fmt" "os" "gopkg.in/yaml.v3" "keycape/internal/adapters/authelia" "keycape/internal/adapters/lldap" "keycape/internal/adapters/privacyidea" ) // Config is the top-level server configuration. type Config struct { Issuer string `yaml:"issuer"` Port int `yaml:"port"` TokenLifetime string `yaml:"tokenLifetime"` PrivateKeyPEM string `yaml:"privateKeyPem"` LLDAP lldap.Config `yaml:"lldap"` Authelia authelia.Config `yaml:"authelia"` PrivacyIDEA privacyidea.Config `yaml:"privacyidea"` Clients []ClientConfig `yaml:"clients"` Environment string `yaml:"environment"` } // ClientConfig is a static OIDC client registration. type ClientConfig struct { ClientID string `yaml:"clientId"` DisplayName string `yaml:"displayName"` RedirectURIs []string `yaml:"redirectUris"` AllowedScopes []string `yaml:"allowedScopes"` GrantTypes []string `yaml:"grantTypes"` ClientType string `yaml:"clientType"` // "confidential" | "public" SecretRef string `yaml:"secretRef,omitempty"` } // Load reads and parses the YAML config file at path. // If path is empty, it falls back to the KEYCAPE_CONFIG environment variable. // Returns an error if the file cannot be read or parsed. func Load(path string) (*Config, error) { if path == "" { path = os.Getenv("KEYCAPE_CONFIG") } if path == "" { return nil, fmt.Errorf("config: no config path specified (use --config or KEYCAPE_CONFIG)") } data, err := os.ReadFile(path) if err != nil { return nil, fmt.Errorf("config: read %q: %w", path, err) } var cfg Config if err := yaml.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("config: parse %q: %w", path, err) } return &cfg, nil }