Add a golangci-lint config and fix its findings: the OIDC state cookie gets the Secure flag on https deployments, and pool sizes are clamped before the int32 conversion.
143 lines
3.5 KiB
Go
143 lines
3.5 KiB
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/spf13/viper"
|
|
)
|
|
|
|
type Config struct {
|
|
Server ServerConfig
|
|
DB DBConfig
|
|
OIDC OIDCConfig
|
|
Security SecurityConfig
|
|
Catalog CatalogConfig
|
|
}
|
|
|
|
type CatalogConfig struct {
|
|
SyncInterval time.Duration // how often to refresh the model catalog
|
|
}
|
|
|
|
type ServerConfig struct {
|
|
Port int
|
|
Env string // "development" | "production"
|
|
PublicURL string // canonical external URL, required when OIDC is enabled
|
|
}
|
|
|
|
type DBConfig struct {
|
|
URL string
|
|
MaxConns int32
|
|
MinConns int32
|
|
MaxConnLifetime int // minutes
|
|
}
|
|
|
|
type OIDCConfig struct {
|
|
Issuer string
|
|
ClientID string
|
|
ClientSecret string
|
|
AdminGroup string
|
|
}
|
|
|
|
type SecurityConfig struct {
|
|
EncryptionKey string // hex-encoded 32 bytes for AES-256-GCM
|
|
}
|
|
|
|
func Load() (*Config, error) {
|
|
v := viper.New()
|
|
|
|
// Config comes from GATEWAY_* environment variables. A .env file, when used,
|
|
// is loaded into the environment by the launcher (see the Makefile / Docker
|
|
// env_file), not parsed here. Maps GATEWAY_SERVER_PORT → server.port, etc.
|
|
v.SetEnvPrefix("GATEWAY")
|
|
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
|
v.AutomaticEnv()
|
|
|
|
// defaults
|
|
v.SetDefault("server.port", 8080)
|
|
v.SetDefault("server.env", "development")
|
|
v.SetDefault("db.max_conns", 20)
|
|
v.SetDefault("db.min_conns", 2)
|
|
v.SetDefault("db.max_conn_lifetime", 30)
|
|
v.SetDefault("catalog.sync_interval", "30m")
|
|
|
|
cfg := &Config{
|
|
Server: ServerConfig{
|
|
Port: v.GetInt("server.port"),
|
|
Env: v.GetString("server.env"),
|
|
PublicURL: v.GetString("server.public_url"),
|
|
},
|
|
DB: DBConfig{
|
|
URL: v.GetString("db.url"),
|
|
MaxConns: clampInt32(v.GetInt("db.max_conns")),
|
|
MinConns: clampInt32(v.GetInt("db.min_conns")),
|
|
MaxConnLifetime: v.GetInt("db.max_conn_lifetime"),
|
|
},
|
|
OIDC: OIDCConfig{
|
|
Issuer: v.GetString("oidc.issuer"),
|
|
ClientID: v.GetString("oidc.client_id"),
|
|
ClientSecret: v.GetString("oidc.client_secret"),
|
|
AdminGroup: v.GetString("oidc.admin_group"),
|
|
},
|
|
Security: SecurityConfig{
|
|
EncryptionKey: v.GetString("security.encryption_key"),
|
|
},
|
|
Catalog: CatalogConfig{
|
|
SyncInterval: v.GetDuration("catalog.sync_interval"),
|
|
},
|
|
}
|
|
|
|
return cfg, cfg.validate()
|
|
}
|
|
|
|
func (c *Config) validate() error {
|
|
if c.DB.URL == "" {
|
|
return fmt.Errorf("missing required config: db.url")
|
|
}
|
|
if c.Security.EncryptionKey == "" {
|
|
return fmt.Errorf("missing required config: security.encryption_key")
|
|
}
|
|
if len(c.Security.EncryptionKey) != 64 {
|
|
return fmt.Errorf("security.encryption_key must be 64 hex chars (32 bytes)")
|
|
}
|
|
var missing []string
|
|
if c.OIDC.Issuer == "" {
|
|
missing = append(missing, "oidc.issuer")
|
|
}
|
|
if c.OIDC.ClientID == "" {
|
|
missing = append(missing, "oidc.client_id")
|
|
}
|
|
if c.OIDC.ClientSecret == "" {
|
|
missing = append(missing, "oidc.client_secret")
|
|
}
|
|
if c.OIDC.AdminGroup == "" {
|
|
missing = append(missing, "oidc.admin_group")
|
|
}
|
|
// PublicURL pins the OAuth redirect_uri and the post-login redirect so
|
|
// neither can be overridden by a forged Host / X-Forwarded-Host header.
|
|
if c.Server.PublicURL == "" {
|
|
missing = append(missing, "server.public_url")
|
|
}
|
|
if len(missing) > 0 {
|
|
return fmt.Errorf("missing required OIDC config: %s", strings.Join(missing, ", "))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) IsDev() bool {
|
|
return c.Server.Env == "development"
|
|
}
|
|
|
|
// clampInt32 converts a pool size setting to int32 without overflow.
|
|
func clampInt32(n int) int32 {
|
|
if n < 0 {
|
|
return 0
|
|
}
|
|
if n > math.MaxInt32 {
|
|
return math.MaxInt32
|
|
}
|
|
return int32(n)
|
|
}
|