// Package tlsreload provides last-known-good leaf certificate reloads for the // low-frequency control-plane TLS handshakes. package tlsreload import ( "crypto/tls" "errors" "sync" ) var ErrInvalidCertificatePaths = errors.New("invalid TLS certificate paths") // CertificateProvider reloads a certificate/key pair for each new handshake. // Kubernetes projected volumes update file pairs atomically, but retaining the // last valid pair also bridges short writer or filesystem visibility gaps. type CertificateProvider struct { certificateFile string keyFile string mu sync.RWMutex last tls.Certificate } func NewCertificateProvider(certificateFile, keyFile string) (*CertificateProvider, error) { if certificateFile == "" || keyFile == "" { return nil, ErrInvalidCertificatePaths } certificate, err := tls.LoadX509KeyPair(certificateFile, keyFile) if err != nil { return nil, err } return &CertificateProvider{certificateFile: certificateFile, keyFile: keyFile, last: certificate}, nil } func (provider *CertificateProvider) ServerCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error) { return provider.certificate() } func (provider *CertificateProvider) ClientCertificate(*tls.CertificateRequestInfo) (*tls.Certificate, error) { return provider.certificate() } func (provider *CertificateProvider) certificate() (*tls.Certificate, error) { if provider == nil { return nil, ErrInvalidCertificatePaths } certificate, err := tls.LoadX509KeyPair(provider.certificateFile, provider.keyFile) if err == nil { provider.mu.Lock() provider.last = certificate provider.mu.Unlock() return &certificate, nil } provider.mu.RLock() fallback := provider.last provider.mu.RUnlock() if len(fallback.Certificate) == 0 { return nil, err } return &fallback, nil }