62 lines
1.8 KiB
Go
62 lines
1.8 KiB
Go
// 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
|
|
}
|