certificate

package
v3.0.4 Latest Latest
Warning

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

Go to latest
Published: Aug 20, 2026 License: MIT Imports: 8 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
)
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

This section is empty.

Functions

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 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"
	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 for the selfmanaged backend.
	// One of P-256 (default), P-384, P-521. Unknown values are rejected by
	// the selfmanaged backend. Ignored by cert-manager.
	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"`
}

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.

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