package config import ( "errors" "os" "path/filepath" "runtime" "sort" "strings" "github.com/ilyakaznacheev/cleanenv" ) type Config struct { Addr string `yaml:"addr" env:"ADDR" env-default:":8080"` PDNSAPIURL string `yaml:"pdns_api_url" env:"PDNS_API_URL" env-default:"http://localhost:8081"` PDNSAPIKey string `yaml:"pdns_api_key" env:"PDNS_API_KEY"` PDNSServerID string `yaml:"pdns_server_id" env:"PDNS_SERVER_ID" env-default:"localhost"` Auth AuthConfig `yaml:"auth"` } type AuthConfig struct { Disabled bool `yaml:"disabled" env:"AUTH_DISABLED"` LDAP LDAPConfig `yaml:"ldap"` } type LDAPConfig struct { URL string `yaml:"url" env:"AUTH_LDAP_URL"` StartTLS bool `yaml:"start_tls" env:"AUTH_LDAP_START_TLS"` InsecureSkipVerify bool `yaml:"insecure_skip_verify" env:"AUTH_LDAP_INSECURE_SKIP_VERIFY"` BindDN string `yaml:"bind_dn" env:"AUTH_LDAP_BIND_DN"` BindPassword string `yaml:"bind_password" env:"AUTH_LDAP_BIND_PASSWORD"` UserBaseDN string `yaml:"user_base_dn" env:"AUTH_LDAP_USER_BASE_DN"` UsernameAttribute string `yaml:"username_attribute" env:"AUTH_LDAP_USERNAME_ATTRIBUTE" env-default:"uid"` UserFilter string `yaml:"user_filter" env:"AUTH_LDAP_USER_FILTER" env-default:"({username_attribute}={username})"` GroupBaseDN string `yaml:"group_base_dn" env:"AUTH_LDAP_GROUP_BASE_DN"` GroupFilter string `yaml:"group_filter" env:"AUTH_LDAP_GROUP_FILTER"` } func Load() (Config, error) { path := strings.TrimSpace(os.Getenv("CONFIG_FILE")) if path == "" { path = defaultConfigPath() } return LoadFile(path) } func LoadFile(path string) (Config, error) { var cfg Config var err error if strings.TrimSpace(path) == "" { err = cleanenv.ReadEnv(&cfg) } else { err = cleanenv.ReadConfig(path, &cfg) } if err != nil { return Config{}, err } normalize(&cfg) if cfg.PDNSAPIKey == "" { return Config{}, errors.New("PDNS_API_KEY is required") } if err := validateAuth(cfg.Auth); err != nil { return Config{}, err } return cfg, nil } func defaultConfigPath() string { homeDir, err := os.UserHomeDir() if err != nil { homeDir = "" } return firstExistingConfigPath(configSearchPaths(homeDir, runtime.GOOS)) } func configSearchPaths(homeDir, goos string) []string { paths := make([]string, 0, 3) if isUnixLike(goos) { paths = append(paths, "/etc/pdns_admin/config.yaml") } if homeDir != "" { paths = append(paths, filepath.Join(homeDir, "config.yaml")) } return append(paths, "config.yaml") } func isUnixLike(goos string) bool { switch goos { case "aix", "android", "darwin", "dragonfly", "freebsd", "hurd", "illumos", "ios", "linux", "netbsd", "openbsd", "solaris": return true default: return false } } func firstExistingConfigPath(paths []string) string { for _, path := range paths { if _, err := os.Stat(path); err == nil { return path } } return "" } func normalize(cfg *Config) { cfg.Addr = strings.TrimSpace(cfg.Addr) cfg.PDNSAPIURL = strings.TrimSpace(cfg.PDNSAPIURL) cfg.PDNSAPIKey = strings.TrimSpace(cfg.PDNSAPIKey) cfg.PDNSServerID = strings.TrimSpace(cfg.PDNSServerID) cfg.Auth.LDAP.URL = strings.TrimSpace(cfg.Auth.LDAP.URL) cfg.Auth.LDAP.BindDN = strings.TrimSpace(cfg.Auth.LDAP.BindDN) cfg.Auth.LDAP.BindPassword = strings.TrimSpace(cfg.Auth.LDAP.BindPassword) cfg.Auth.LDAP.UserBaseDN = strings.TrimSpace(cfg.Auth.LDAP.UserBaseDN) cfg.Auth.LDAP.UsernameAttribute = strings.TrimSpace(cfg.Auth.LDAP.UsernameAttribute) cfg.Auth.LDAP.UserFilter = strings.TrimSpace(cfg.Auth.LDAP.UserFilter) cfg.Auth.LDAP.GroupBaseDN = strings.TrimSpace(cfg.Auth.LDAP.GroupBaseDN) cfg.Auth.LDAP.GroupFilter = strings.TrimSpace(cfg.Auth.LDAP.GroupFilter) } func validateAuth(auth AuthConfig) error { if auth.Disabled { return nil } missing := make([]string, 0) for field, value := range map[string]string{ "AUTH_LDAP_URL": auth.LDAP.URL, "AUTH_LDAP_BIND_DN": auth.LDAP.BindDN, "AUTH_LDAP_BIND_PASSWORD": auth.LDAP.BindPassword, "AUTH_LDAP_USER_BASE_DN": auth.LDAP.UserBaseDN, "AUTH_LDAP_USERNAME_ATTRIBUTE": auth.LDAP.UsernameAttribute, "AUTH_LDAP_USER_FILTER": auth.LDAP.UserFilter, } { if value == "" { missing = append(missing, field) } } if auth.LDAP.GroupFilter != "" && auth.LDAP.GroupBaseDN == "" { missing = append(missing, "AUTH_LDAP_GROUP_BASE_DN") } if len(missing) > 0 { sort.Strings(missing) return errors.New("LDAP auth is enabled; missing required configuration: " + strings.Join(missing, ", ")) } return nil }