package config import ( "crypto/tls" "crypto/x509" "errors" "fmt" "os" ) // TLS mode values for TLS_MODE env var. const ( TLSModeNone = "none" // Plain HTTP (default). Use behind a proxy. TLSModeServer = "server" // Server-side TLS only. No client cert required. TLSModeMTLS = "mtls" // Mutual TLS. Client cert required and verified. ) var ( ErrTLSCertRequired = errors.New("TLS_CERT and TLS_KEY required when TLS_MODE is server or mtls") ErrTLSCARequired = errors.New("TLS_CA required when TLS_MODE is mtls") ErrTLSCALoad = errors.New("failed to load TLS CA certificate") ErrTLSModeInvalid = errors.New("TLS_MODE must be none, server, or mtls") ) // ValidateTLSMode returns an error if the TLS_MODE value is not recognized. func ValidateTLSMode(mode string) error { switch mode { case TLSModeNone, TLSModeServer, TLSModeMTLS, "": return nil default: return fmt.Errorf("%w: %q", ErrTLSModeInvalid, mode) } } // BuildTLSConfig constructs a *tls.Config from the application config. // Returns nil when TLS_MODE is "none" (or empty). // Returns an error if required cert/key/CA files are missing or unreadable. func BuildTLSConfig(cfg *Config) (*tls.Config, error) { mode := cfg.TLSMode if mode == "" || mode == TLSModeNone { return nil, nil } if cfg.TLSCert == "" || cfg.TLSKey == "" { return nil, ErrTLSCertRequired } tlsCfg := &tls.Config{ MinVersion: tls.VersionTLS13, } if mode == TLSModeMTLS { if cfg.TLSCA == "" { return nil, ErrTLSCARequired } caCert, err := os.ReadFile(cfg.TLSCA) if err != nil { return nil, fmt.Errorf("%w: %v", ErrTLSCALoad, err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(caCert) { return nil, fmt.Errorf("%w: no valid certificates in %s", ErrTLSCALoad, cfg.TLSCA) } tlsCfg.ClientAuth = tls.RequireAndVerifyClientCert tlsCfg.ClientCAs = pool } return tlsCfg, nil } // BuildPeerTLSConfig constructs a *tls.Config for outbound node-to-node // connections. The node presents its own cert and verifies peers against // the cluster CA. Returns nil when TLS_MODE is not "mtls". func BuildPeerTLSConfig(cfg *Config) (*tls.Config, error) { if cfg.TLSMode != TLSModeMTLS { return nil, nil } cert, err := tls.LoadX509KeyPair(cfg.TLSCert, cfg.TLSKey) if err != nil { return nil, fmt.Errorf("failed to load node keypair: %w", err) } caCert, err := os.ReadFile(cfg.TLSCA) if err != nil { return nil, fmt.Errorf("%w: %v", ErrTLSCALoad, err) } pool := x509.NewCertPool() if !pool.AppendCertsFromPEM(caCert) { return nil, fmt.Errorf("%w: no valid certificates in %s", ErrTLSCALoad, cfg.TLSCA) } return &tls.Config{ Certificates: []tls.Certificate{cert}, RootCAs: pool, MinVersion: tls.VersionTLS13, }, nil }