91 lines
2.3 KiB
Go
91 lines
2.3 KiB
Go
package tlsreload
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"sync"
|
|
)
|
|
|
|
var ErrInvalidTrustPath = errors.New("invalid TLS trust bundle path")
|
|
|
|
// TrustProvider reloads a PEM trust bundle for each new control-plane
|
|
// handshake. It keeps the previous valid bundle while a projected volume is
|
|
// momentarily incomplete during an update.
|
|
type TrustProvider struct {
|
|
path string
|
|
|
|
mu sync.RWMutex
|
|
last *x509.CertPool
|
|
}
|
|
|
|
func NewTrustProvider(path string) (*TrustProvider, error) {
|
|
if path == "" {
|
|
return nil, ErrInvalidTrustPath
|
|
}
|
|
pool, err := loadTrustPool(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &TrustProvider{path: path, last: pool}, nil
|
|
}
|
|
|
|
// Pool returns the latest valid roots. Returned pools are immutable after
|
|
// construction, so callers can safely use them for an in-flight handshake.
|
|
func (provider *TrustProvider) Pool() (*x509.CertPool, error) {
|
|
if provider == nil {
|
|
return nil, ErrInvalidTrustPath
|
|
}
|
|
pool, err := loadTrustPool(provider.path)
|
|
if err == nil {
|
|
provider.mu.Lock()
|
|
provider.last = pool
|
|
provider.mu.Unlock()
|
|
return pool, nil
|
|
}
|
|
provider.mu.RLock()
|
|
fallback := provider.last
|
|
provider.mu.RUnlock()
|
|
if fallback == nil {
|
|
return nil, err
|
|
}
|
|
return fallback, nil
|
|
}
|
|
|
|
// VerifyServer returns a TLS verifier that uses the latest trust bundle while
|
|
// retaining normal DNS-name and server-authentication verification.
|
|
func (provider *TrustProvider) VerifyServer(serverName string) func(tls.ConnectionState) error {
|
|
return func(state tls.ConnectionState) error {
|
|
if len(state.PeerCertificates) == 0 {
|
|
return errors.New("control-plane server did not provide a certificate")
|
|
}
|
|
roots, err := provider.Pool()
|
|
if err != nil {
|
|
return fmt.Errorf("load control-plane trust bundle: %w", err)
|
|
}
|
|
intermediates := x509.NewCertPool()
|
|
for _, certificate := range state.PeerCertificates[1:] {
|
|
intermediates.AddCert(certificate)
|
|
}
|
|
_, err = state.PeerCertificates[0].Verify(x509.VerifyOptions{
|
|
DNSName: serverName, Roots: roots, Intermediates: intermediates,
|
|
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
})
|
|
return err
|
|
}
|
|
}
|
|
|
|
func loadTrustPool(path string) (*x509.CertPool, error) {
|
|
payload, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pool := x509.NewCertPool()
|
|
if !pool.AppendCertsFromPEM(payload) {
|
|
return nil, errors.New("parse TLS trust bundle")
|
|
}
|
|
return pool, nil
|
|
}
|