certificate

package
v3.0.6 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package certificate provides pluggable TLSBackend abstractions for certificate management in Kubernetes operators.

It supports three backends:

  • selfmanaged: generates and rotates CA + leaf certificates using Go's crypto/x509, returning them as Kubernetes Secrets for SSA apply.
  • byo (bring-your-own): references an existing user-managed Secret, emitting no child objects.
  • certmanager (optional subpackage): emits cert-manager Issuer/Certificate CRs and relies on cert-manager to produce the Secret.

The selfmanaged backend supports ECDSA curve selection (P-256/P-384/P-521), IP SANs, an optional CA CRL, and a renewal window; the reusable rotation saga lives in pkg/controller/certificate/rotation.

Index

Constants

View Source
const (
	// CurveP256 is the default ECDSA curve (secp256r1).
	CurveP256 = "P-256"
	// CurveP384 selects ECDSA P-384 (secp384r1).
	CurveP384 = "P-384"
	// CurveP521 selects ECDSA P-521 (secp521r1).
	CurveP521 = "P-521"

	// DefaultRenewalDays is the renewal window before NotAfter when renewal
	// is triggered, used when TLSSpec.RenewalDays is not set.
	DefaultRenewalDays = 30

	// MaxRenewalDays is the largest accepted renewal window. Values above
	// this are clamped down to it. This prevents integer overflow when
	// computing `RenewalDays * 24 * time.Hour` (a time.Duration in int64
	// nanoseconds overflows around 106_752 days, which would produce a
	// negative window and silently disable renewal — or a huge positive
	// window causing perpetual renewal). 100 years is well below the
	// overflow threshold and far exceeds any legitimate cert validity.
	MaxRenewalDays = 36500

	// MaxValidityDays is the largest accepted certificate validity (leaf or
	// CA) in days. It mirrors MaxRenewalDays: `ValidityDays * 24 * time.Hour`
	// (a time.Duration in int64 nanoseconds) overflows around 106_752 days,
	// which would either fail certificate creation or wrap to a small negative
	// (already-expired) NotAfter and trigger a perpetual renewal loop. 100
	// years is safely below the overflow threshold and exceeds any legitimate
	// certificate validity.
	MaxValidityDays = 36500

	// KeyAlgorithmECDSA selects an ECDSA private key (P-256/P-384/P-521).
	KeyAlgorithmECDSA = "ECDSA"
	// KeyAlgorithmRSA selects an RSA private key.
	KeyAlgorithmRSA = "RSA"

	// DefaultECDSAKeySize is the default ECDSA key size in bits (P-256).
	DefaultECDSAKeySize = 256
	// DefaultRSAKeySize is the default RSA key size in bits.
	DefaultRSAKeySize = 2048

	// MaxRSAKeySize is the largest accepted RSA key size in bits. RSA key
	// generation is CPU- and memory-intensive and scales super-linearly with
	// the bit size. An unbounded KeySize would let a misconfiguration (or a
	// tampered CRD in a multi-tenant deployment) drive rsa.GenerateKey to
	// synthesize an enormous key and exhaust the controller's CPU/memory
	// (CWE-400/CWE-770). 8192 is a generous ceiling that covers even very
	// high-assurance deployments while keeping generation bounded.
	MaxRSAKeySize = 8192

	// ExtKeyUsage (leaf) string vocabulary, mapped per backend.
	UsageServerAuth      = "serverAuth"
	UsageClientAuth      = "clientAuth"
	UsageCodeSigning     = "codeSigning"
	UsageEmailProtection = "emailProtection"
	UsageTimestamping    = "timestamping"
	UsageOCSPSigning     = "ocspSigning"

	// KeyUsage string vocabulary, mapped per backend.
	KeyUsageDigitalSignature  = "digitalSignature"
	KeyUsageContentCommitment = "contentCommitment"
	KeyUsageKeyEncipherment   = "keyEncipherment"
	KeyUsageDataEncipherment  = "dataEncipherment"
	KeyUsageKeyAgreement      = "keyAgreement"
	KeyUsageCertSign          = "certSign"
	KeyUsageCRLSign           = "crlSign"
)
View Source
const (
	// AnnotationSecretHash is the annotation key used to store a stable
	// hash of the certificate Secret contents on pod templates. When the
	// Secret changes, the hash changes, triggering a natural rolling restart.
	AnnotationSecretHash = "operator-sdk-extra.webcenter.fr/certificate-hash"

	// AnnotationForceRegenerateAll triggers a full CA+leaf rotation + rollout.
	// Read as == "true" (the ignoreReconcile convention). Overridable via
	// rotation.WithForceRegenerateAllAnnotation.
	AnnotationForceRegenerateAll = "operator-sdk-extra.webcenter.fr/force-regenerate-tls"

	// AnnotationForceRegenerateLeaf triggers a leaf-only regen + rollout.
	AnnotationForceRegenerateLeaf = "operator-sdk-extra.webcenter.fr/force-regenerate-certificates"
)

Variables

View Source
var ErrInvalidKeySize = errors.Sentinel("invalid key size")

ErrInvalidKeySize is returned when TLSSpec.KeySize is invalid for the selected key algorithm (or disagrees with TLSSpec.Curve).

View Source
var ErrUnknownCurve = errors.Sentinel("unknown curve")

ErrUnknownCurve is returned when TLSSpec.Curve is set to a value other than CurveP256, CurveP384 or CurveP521.

View Source
var ErrUnknownUsage = errors.Sentinel("unknown usage")

ErrUnknownUsage is returned when TLSSpec.Usages or TLSSpec.KeyUsages contains an unknown usage string.

View Source
var ErrUnsupportedKeyAlgorithm = errors.Sentinel("unsupported key algorithm")

ErrUnsupportedKeyAlgorithm is returned when TLSSpec.KeyAlgorithm is set to an unsupported value. It is wrapped with context at each failing site so errors.Cause still finds it.

Functions

func DedupIPs added in v3.0.6

func DedupIPs(in []net.IP) []net.IP

DedupIPs returns a copy of in with duplicate IPs removed, preserving the order of first occurrence. Duplicates are detected by each IP's canonical String() form (so "2001:db8::1" and "2001:db8:0:0:0:0:0:1" dedupe).

func DedupStrings added in v3.0.6

func DedupStrings(in []string) []string

DedupStrings returns a copy of in with duplicate entries removed, preserving the order of first occurrence.

func GetValidCADays added in v3.0.4

func GetValidCADays(spec TLSSpec) int

GetValidCADays returns the CA certificate validity in days, defaulting to 2× GetValidLeafDays when CAValidityDays <= 0. Values above MaxValidityDays are clamped down to it (see MaxValidityDays).

func GetValidLeafDays added in v3.0.4

func GetValidLeafDays(spec TLSSpec) int

GetValidLeafDays returns the leaf certificate validity in days, defaulting to 365 when LeafValidityDays <= 0. Values above MaxValidityDays are clamped down to it to prevent time.Duration overflow in downstream `LeafValidityDays * 24 * time.Hour` math (see MaxValidityDays).

func GetValidRenewalDays added in v3.0.4

func GetValidRenewalDays(spec TLSSpec) int

GetValidRenewalDays returns the renewal window in days, defaulting to DefaultRenewalDays (30) when RenewalDays is not set or invalid (<= 0). Values above MaxRenewalDays are clamped to MaxRenewalDays to prevent time.Duration overflow in downstream `RenewalDays * 24 * time.Hour` math (see MaxRenewalDays).

func RolloutAnnotation added in v3.0.4

func RolloutAnnotation(shouldRollout bool, secret *corev1.Secret, currentHash string) (map[string]string, error)

RolloutAnnotation builds the pod-template annotation map entry.

  • shouldRollout=true: fresh hash of secret (SecretHashAnnotation).
  • shouldRollout=false and currentHash!="": keep currentHash (no restart).
  • shouldRollout=false and currentHash=="" (first run): initialize hash.

func SecretHash

func SecretHash(secret *corev1.Secret) (string, error)

SecretHash computes a stable SHA-256 hash of a Secret's data fields. This hash is used as a pod-template annotation so that any change to the certificate Secret forces a rolling restart of the consuming workload.

This decouples "certificate changed" from "must restart" without bespoke polling of StatefulSet.Status.CurrentReplicas.

func SecretHashAnnotation

func SecretHashAnnotation(secret *corev1.Secret) (map[string]string, error)

SecretHashAnnotation builds the annotation map entry for the certificate hash. The returned map is suitable for merging into a pod template's metadata.annotations.

func ShouldRollout added in v3.0.4

func ShouldRollout(policy RolloutPolicy, sig *LayerSignals) bool

ShouldRollout decides whether a pod rollout is needed for one layer's signals. Forced overrides everything, including RolloutNever.

Types

type CertificateCustomizer added in v3.0.6

type CertificateCustomizer[T object.MultiPhaseObject] interface {
	CustomizeCertificate(o T, base TLSSpec) (TLSSpec, error)
}

CertificateCustomizer lets an operator compute certificate content that cannot be declared statically (e.g., SANs/IPs from cluster state). It receives the reconciled object and the declared base TLSSpec and returns the final content. Return base unchanged to keep declared defaults. It has no ctx by design (matching TLSSpecProvider/NodeSpecProvider); capture a client in a closure if cluster reads are needed.

func DefaultCertificateCustomizer added in v3.0.6

func DefaultCertificateCustomizer[T object.MultiPhaseObject]() CertificateCustomizer[T]

DefaultCertificateCustomizer returns a customizer that only fills CommonName with the object name when empty, and is otherwise the identity. Opt-in: pass it explicitly via rotation.WithCertificateCustomizer (no implicit behavior change for existing operators).

type CertificateCustomizerFunc added in v3.0.6

type CertificateCustomizerFunc[T object.MultiPhaseObject] func(o T, base TLSSpec) (TLSSpec, error)

CertificateCustomizerFunc adapts a func to CertificateCustomizer.

func (CertificateCustomizerFunc[T]) CustomizeCertificate added in v3.0.6

func (f CertificateCustomizerFunc[T]) CustomizeCertificate(o T, base TLSSpec) (TLSSpec, error)

CustomizeCertificate implements CertificateCustomizer.

type CertificateSubject added in v3.0.6

type CertificateSubject struct {
	Organizations       []string `json:"organizations,omitempty"`
	OrganizationalUnits []string `json:"organizationalUnits,omitempty"`
	Countries           []string `json:"countries,omitempty"`
	Localities          []string `json:"localities,omitempty"`
	Provinces           []string `json:"provinces,omitempty"`
	StreetAddresses     []string `json:"streetAddresses,omitempty"`
	PostalCodes         []string `json:"postalCodes,omitempty"`
	SerialNumber        string   `json:"serialNumber,omitempty"`
}

CertificateSubject is the certificate RDN block beyond the legacy single CommonName/Organization fields. All fields are optional; empty = unset.

type ContentIgnoringBackend added in v3.0.6

type ContentIgnoringBackend interface {
	// IgnoresCertificateContent reports whether TLSSpec content fields
	// (subject, SANs, key algorithm/size, usages, validity) are ignored by
	// this backend.
	IgnoresCertificateContent() bool
}

ContentIgnoringBackend is an optional capability for backends that perform no certificate generation and therefore ignore TLSSpec content fields (e.g. the BYO backend, which only references an existing Secret). The rotation saga skips ValidateContent for such backends while still enforcing each backend's own structural validation (e.g. a non-empty SecretName).

type LayerSignals added in v3.0.4

type LayerSignals struct {
	CARotated       bool        // a CA-saga started this cycle
	LeafRegenerated bool        // leaf regenerated (CA saga OR leaf-only)
	LeafChange      *LeafChange // drift/expiry detail (SAN/IP/node deltas)
	Forced          bool        // a force annotation was honored this cycle
}

LayerSignals is the per-layer change-delta signal a TLS step publishes under data["tls.<phaseName>"] for the STS step to gate rollout.

type LeafChange added in v3.0.4

type LeafChange struct {
	Reason       LeafChangeReason
	SANsAdded    []string
	SANsRemoved  []string
	IPsAdded     []string
	IPsRemoved   []string
	NodesAdded   []string
	NodesRemoved []string
}

LeafChange describes leaf drift vs spec at `now`. Delta slices are always populated (independent of Reason) so consumers can compute additive-only rollout decisions.

func (LeafChange) IsZero added in v3.0.4

func (c LeafChange) IsZero() bool

IsZero reports whether no regeneration is needed.

type LeafChangeReason added in v3.0.4

type LeafChangeReason string

LeafChangeReason is the dominant reason a leaf needs regeneration.

const (
	LeafNone           LeafChangeReason = ""
	LeafMissing        LeafChangeReason = "Missing"
	LeafExpiring       LeafChangeReason = "Expiring"
	LeafCNChanged      LeafChangeReason = "CNChanged"
	LeafOrgChanged     LeafChangeReason = "OrgChanged"
	LeafSubjectChanged LeafChangeReason = "SubjectChanged"
	LeafKeyChanged     LeafChangeReason = "KeyChanged"
	LeafUsagesChanged  LeafChangeReason = "UsagesChanged"
	LeafSANsChanged    LeafChangeReason = "SANsChanged"
	LeafIPsChanged     LeafChangeReason = "IPsChanged"
	LeafNodesChanged   LeafChangeReason = "NodesChanged"
	LeafForceRegen     LeafChangeReason = "Forced"
)

type LeafManager added in v3.0.4

type LeafManager[T object.MultiPhaseObject] interface {
	// DesiredLeafWithCA re-issues the leaf signed by the CA in caSecret,
	// reusing that CA's key/cert (no new CA). The returned Secret's ca.crt
	// equals caSecret's ca.crt (single CA, no bundle).
	DesiredLeafWithCA(ctx context.Context, o T, spec TLSSpec, caSecret *corev1.Secret) (*corev1.Secret, error)

	// LeafNeedsChange reports whether the leaf Secret needs regeneration vs
	// spec at now. leafSecret may be nil (treated as LeafMissing).
	LeafNeedsChange(ctx context.Context, o T, leafSecret *corev1.Secret, spec TLSSpec, now time.Time) (LeafChange, error)
}

LeafManager is the optional capability for saga backends that own their leaf Secret and can (a) regenerate it against an existing CA and (b) report drift. Implemented by selfmanaged (single-leaf) and selfmanaged/pernode. The rotation step type-asserts this; when absent (cert-manager/BYO) leaf-only regen is skipped and drift falls back to the full CA saga.

type NodeSetTLSBackend added in v3.0.4

type NodeSetTLSBackend[T object.MultiPhaseObject] interface {
	TLSBackend[T]
	// ExpectedNodeNames returns the node names that should have a certificate.
	ExpectedNodeNames(o T) ([]string, error)
	// NodeSecretKeys returns the Data-key suffixes for a node's cert and key
	// (e.g. ".crt", ".key"); a node's Data keys are name+certSuffix and
	// name+keySuffix.
	NodeSecretKeys() (certSuffix, keySuffix string)
}

NodeSetTLSBackend marks per-node (multi-cert) saga backends. Its presence changes rotation data publishing (omit tlsSecret/leafCert).

type RolloutPolicy added in v3.0.4

type RolloutPolicy string

RolloutPolicy selects which change-delta signals trigger a rollout.

const (
	RolloutAlways     RolloutPolicy = "Always"
	RolloutOnCAChange RolloutPolicy = "OnCAChange"
	RolloutOnAdditive RolloutPolicy = "OnAdditive" // recommended default
	RolloutNever      RolloutPolicy = "Never"
)

type TLSBackend

type TLSBackend[T object.MultiPhaseObject] interface {
	// DesiredObjects returns the child objects that the reconciler should
	// manage via SSA. These may be Secrets (self-managed) or cert-manager
	// CRs (cert-manager backend).
	DesiredObjects(ctx context.Context, o T, spec TLSSpec) ([]client.Object, error)

	// CertificateSecretName returns the name of the Secret that consumers
	// should mount or hash for rolling restart.
	CertificateSecretName(o T, spec TLSSpec) string

	// RequiresRotationSaga reports whether the multi-cycle CA-rotation
	// workflow is needed. Only the self-managed mutual-TLS backend returns
	// true; cert-manager and BYO backends return false.
	RequiresRotationSaga() bool
}

TLSBackend defines the interface for pluggable certificate providers.

Each backend returns the child objects it wants the reconciler to manage via SSA, the name of the resulting certificate Secret, and whether a multi-cycle CA-rotation saga is needed.

  • selfmanaged backend: returns Secret objects (CA + leaf), needs saga.
  • cert-manager backend: returns Issuer/Certificate CRs, no saga needed.
  • BYO backend: returns nothing, no saga needed.

type TLSSpec

type TLSSpec struct {
	// SecretName is the name of the Secret that will hold the certificate
	// and key. Consumers mount this Secret or hash it for rollout.
	SecretName string `json:"secretName,omitempty"`

	// IssuerRef references an existing Issuer or ClusterIssuer for the
	// cert-manager backend (existing-CA mode). When empty, a self-signed
	// Issuer is created (dedicated-CA mode).
	IssuerRef string `json:"issuerRef,omitempty"`

	// CommonName is the CN for the generated certificate.
	CommonName string `json:"commonName,omitempty"`

	// DNSNames lists the SAN DNS names for the certificate.
	DNSNames []string `json:"dnsNames,omitempty"`

	// Organization is the O field for the certificate.
	Organization string `json:"organization,omitempty"`

	// LeafValidityDays is the leaf certificate validity in days.
	// Defaults to 365 if not set (see GetValidLeafDays).
	LeafValidityDays int `json:"leafValidityDays,omitempty"`

	// CAValidityDays is the CA certificate validity in days.
	// Defaults to 2× LeafValidityDays when <= 0 (see GetValidCADays).
	CAValidityDays int `json:"caValidityDays,omitempty"`

	// Curve selects the ECDSA curve. One of P-256 (default), P-384, P-521.
	// Unknown values are rejected by ValidateContent. It drives the effective
	// ECDSA key size (EffectiveKeySize) and therefore also cert-manager's
	// privateKey.size.
	Curve string `json:"curve,omitempty"`

	// IPAddresses lists the SAN IPs for the generated certificate.
	// Each entry must parse via net.ParseIP.
	IPAddresses []string `json:"ipAddresses,omitempty"`

	// RenewalDays is the window before expiry during which renewal is
	// triggered. Defaults to 30 (see GetValidRenewalDays).
	RenewalDays int `json:"renewalDays,omitempty"`

	// GenerateCRL, when true, adds a ca.crl key (DER-encoded revocation
	// list) to the CA Secret (selfmanaged only).
	GenerateCRL bool `json:"generateCRL,omitempty"`

	// Subject is the extended RDN block. When Subject.Organizations is empty,
	// the legacy Organization field is used (backward compatible).
	Subject CertificateSubject `json:"subject,omitempty"`

	// KeyAlgorithm is ECDSA (default) or RSA.
	KeyAlgorithm string `json:"keyAlgorithm,omitempty"`

	// KeySize is the key size in bits. 0 = default per algorithm
	// (ECDSA -> 256/P-256; RSA -> 2048). For ECDSA must be 256/384/521.
	KeySize int `json:"keySize,omitempty"`

	// Usages is the leaf ExtKeyUsage list (see Usage* constants).
	// Empty = [serverAuth, clientAuth].
	Usages []string `json:"usages,omitempty"`

	// KeyUsages is the leaf KeyUsage list (see KeyUsage* constants).
	// Empty = [digitalSignature, keyEncipherment].
	KeyUsages []string `json:"keyUsages,omitempty"`

	// CACommonName overrides the CA certificate CN. Default "<CommonName>-ca".
	CACommonName string `json:"caCommonName,omitempty"`

	// CASubject overrides the CA RDN block. Default = leaf subject with
	// CN = CACommonName.
	CASubject *CertificateSubject `json:"caSubject,omitempty"`
}

TLSSpec defines the desired (computed) TLS configuration for a component. Operators embed a SLIMMED version in their CRD spec and pass computed values to the library via a TLSSpecProvider. Backend selection is an operator decision (which backend type to construct), NOT spec data — the old SelfSigned/CertManager booleans are removed.

func (TLSSpec) EffectiveKeyAlgorithm added in v3.0.6

func (s TLSSpec) EffectiveKeyAlgorithm() string

EffectiveKeyAlgorithm returns KeyAlgorithm or KeyAlgorithmECDSA (default).

func (TLSSpec) EffectiveKeySize added in v3.0.6

func (s TLSSpec) EffectiveKeySize() (int, error)

EffectiveKeySize resolves the key size: KeySize if > 0, else the size implied by Curve (P-256->256, P-384->384, P-521->521), else the algorithm default. Returns an error if KeySize and Curve disagree.

func (TLSSpec) EffectiveKeyUsages added in v3.0.6

func (s TLSSpec) EffectiveKeyUsages() []string

EffectiveKeyUsages returns the leaf KeyUsage strings (defaults applied).

func (TLSSpec) EffectiveUsages added in v3.0.6

func (s TLSSpec) EffectiveUsages() []string

EffectiveUsages returns the leaf ExtKeyUsage strings (defaults applied).

func (TLSSpec) LeafSubject added in v3.0.6

func (s TLSSpec) LeafSubject() pkix.Name

LeafSubject returns the resolved leaf pkix.Name (CN + full RDN).

func (TLSSpec) Organizations added in v3.0.6

func (s TLSSpec) Organizations() []string

Organizations returns the effective leaf organizations: Subject.Organizations if non-empty, else []string{Organization} (dropping "").

func (TLSSpec) ResolvedCASubject added in v3.0.6

func (s TLSSpec) ResolvedCASubject() pkix.Name

ResolvedCASubject returns the resolved CA pkix.Name. CN = CACommonName (default "<CommonName>-ca"); other RDN fields = CASubject if set, else leaf subject. It is a method rather than a field because the exported `CASubject *CertificateSubject` field already occupies that identifier.

func (TLSSpec) ValidateContent added in v3.0.6

func (s TLSSpec) ValidateContent() error

ValidateContent validates all customizable content fields. Returns the first error. Called by backends and by the rotation saga before generation.

type TLSSpecProvider added in v3.0.4

type TLSSpecProvider[T object.MultiPhaseObject] interface {
	TLSSpec(o T) TLSSpec
}

TLSSpecProvider supplies the computed TLSSpec for an object. The operator implements this (or uses TLSSpecProviderFunc) instead of passing a bare func to NewTLSStep.

type TLSSpecProviderFunc added in v3.0.4

type TLSSpecProviderFunc[T object.MultiPhaseObject] func(o T) TLSSpec

TLSSpecProviderFunc adapts a func(o T) TLSSpec to TLSSpecProvider.

func (TLSSpecProviderFunc[T]) TLSSpec added in v3.0.4

func (f TLSSpecProviderFunc[T]) TLSSpec(o T) TLSSpec

Directories

Path Synopsis
Package byo provides a TLSBackend that references an existing user-managed Secret.
Package byo provides a TLSBackend that references an existing user-managed Secret.
Package certmanager provides a TLSBackend that emits cert-manager Issuer and Certificate custom resources.
Package certmanager provides a TLSBackend that emits cert-manager Issuer and Certificate custom resources.
Package rotation provides a reusable multi-cycle TLS rotation saga step built on workflow.WorkflowStepReconcilerActionWithDiff.
Package rotation provides a reusable multi-cycle TLS rotation saga step built on workflow.WorkflowStepReconcilerActionWithDiff.
Package selfmanaged provides a TLSBackend that generates and manages CA and leaf certificates using Go's crypto/x509 standard library.
Package selfmanaged provides a TLSBackend that generates and manages CA and leaf certificates using Go's crypto/x509 standard library.
pernode
Package pernode provides a TLSBackend that keeps one certificate per node in a single Secret (multi-cert transport TLS, e.g.
Package pernode provides a TLSBackend that keeps one certificate per node in a single Secret (multi-cert transport TLS, e.g.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL