Documentation
¶
Overview ¶
Package reload provides hot-reloading of TLS keypairs that are rotated in place on disk (e.g. cert-manager rewriting a Kubernetes Secret mount). A long-lived process that loads its keypair once at startup keeps serving the stale leaf until it restarts; once that leaf expires, every TLS handshake fails. The primitives here — a validated Bundle, an atomic Store, an fsnotify-backed Watch, and a Reloader exposing tls.Config.GetCertificate — let a server pick up rotations without a restart.
This is the single implementation shared by the kube-controller upstream client cert, the tunnelproxy QUIC server cert, and the tunnel relay.
Index ¶
Constants ¶
const ( SecretCertFile = "tls.crt" SecretKeyFile = "tls.key" SecretCAFile = "ca.crt" )
Standard file names inside a Kubernetes TLS Secret mount.
Variables ¶
var ErrNoCertificate = errors.New("reload: no certificate loaded")
ErrNoCertificate is returned by Reloader.GetCertificate before any bundle has been successfully loaded.
Functions ¶
func Watch ¶
Watch watches the directories holding p's files and atomically swaps the live bundle in store on every successful reload, until ctx is done. It also resyncs from disk periodically as a backstop against missed events.
Watch is best-effort: if no directory can be watched (e.g. a pod whose Secret isn't mounted) it logs once and returns nil rather than failing the caller — hot-reload is an enhancement, not a hard dependency.
Types ¶
type Bundle ¶
type Bundle struct {
Cert tls.Certificate
RootCAs *x509.CertPool // system pool plus the optional CA file
Fingerprint string
NotAfter time.Time
}
Bundle is an immutable view of one validated keypair generation. It is published via Store as a whole-pointer swap; nothing inside is mutated after publication, so readers need no locks.
func BundleFromPEM ¶
BundleFromPEM validates the keypair, parses the leaf to extract its fingerprint + expiry, and builds the root pool. All validation happens before construction so a half-written input surfaces as an error rather than a partially-populated bundle.
func LoadBundle ¶
LoadBundle reads and validates the keypair (and optional CA) from disk. A missing CA file is tolerated (RootCAs falls back to the system pool); a missing or malformed cert/key surfaces as an error so a partial-write race is retried rather than published.
type Metrics ¶
type Metrics interface {
// ReloadAttempt records one reload attempt and whether it swapped in a
// new bundle. Identical-content resyncs are no-ops and are not recorded.
ReloadAttempt(success bool)
// SetExpiry publishes the NotAfter of the live leaf.
SetExpiry(t time.Time)
}
Metrics records reload outcomes and live-cert expiry. Implementations must be safe for concurrent use. A nil Metrics disables metric recording, which lets callers keep their own pre-existing series by passing an adapter instead of the default below.
func DefaultMetrics ¶
DefaultMetrics records to the shared apoxy_tls_cert_* series, labelled by component. Multiple Reloaders may share the same component label.
type Paths ¶
type Paths struct {
Cert string
Key string
CA string // optional; empty means there is no CA file to read
}
Paths locates the cert, key, and optional CA files on disk.
type Reloader ¶
type Reloader struct {
// contains filtered or unexported fields
}
Reloader serves a TLS server certificate from disk and hot-reloads it when the backing files rotate (e.g. cert-manager rewriting a Kubernetes Secret). GetCertificate reads are lock-free atomic-pointer loads with no syscalls — a background watcher (Start) does all the disk work — so they stay cheap on the handshake-accept hot path.
Wire GetCertificate into tls.Config.GetCertificate and run Start in a goroutine (or add it to a controller-runtime manager as a Runnable).
func NewReloader ¶
NewReloader loads the initial bundle (retrying briefly past a torn-write race) so construction fails fast on a genuinely missing/malformed keypair, and returns a Reloader ready to serve it. component labels the metrics and logs; metrics default to the shared apoxy_tls_cert_* series.
func (*Reloader) Bundle ¶
Bundle returns the live bundle (cert + root pool + expiry), or nil before the first load. Useful for client-side consumers that need RootCAs.
func (*Reloader) GetCertificate ¶
func (r *Reloader) GetCertificate(*tls.ClientHelloInfo) (*tls.Certificate, error)
GetCertificate implements tls.Config.GetCertificate, returning the live leaf from the atomic store with no disk access.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store holds the live Bundle behind an atomic pointer so handshakes, the metrics emitter, and the watcher can all read/write without locks.
type WatchOptions ¶
type WatchOptions struct {
// OnSwap, if set, is called after each successful bundle swap.
OnSwap func(*Bundle)
// Metrics records reload outcomes; nil disables metric recording.
Metrics Metrics
// Component labels this watcher's log lines.
Component string
}
WatchOptions configures a watcher run.