Certificate Controller

cert-manager Certificate Controller Deep Dive

The Certificate controller is the core component of cert-manager responsible for managing the lifecycle of X.509 certificates in Kubernetes. Rather than a single monolithic controller, cert-manager splits certificate management across four specialized sub-controllers that coordinate through status conditions and annotations on the Certificate resource.

1. What This Component Does

The Certificate controller ensemble manages the complete lifecycle of a Certificate resource, from initial key generation through issuance, renewal, and secret management. Its responsibilities include:

  • Private Key Management: Generating and rotating cryptographic private keys (RSA, ECDSA, Ed25519) according to the Certificate spec.
  • Issuance Triggering: Determining when a certificate needs to be issued or renewed and creating the appropriate CertificateRequest.
  • Secret Management: Storing the signed certificate, private key, and CA chain in the target Kubernetes Secret in the correct format.
  • Status Reporting: Maintaining accurate status conditions on the Certificate resource, including readiness, expiration time, and renewal time.
  • Automatic Renewal: Proactively triggering certificate renewal before expiry based on the renewBefore configuration.

When/Why Use It?

The Certificate controller is invoked whenever a user creates, updates, or needs renewal of a Certificate resource. It is the primary workflow that cert-manager users interact with, either directly by creating Certificate resources or indirectly through the ingress-shim or gateway-shim.

2. Sub-Controller Architecture

The Certificate lifecycle is split across four controllers, each responsible for a distinct phase:

graph TD
    CERT[Certificate Resource] --> KM[certificates-key-manager]
    CERT --> TR[certificates-trigger]
    CERT --> IS[certificates-issuing]
    CERT --> RD[certificates-readiness]

    KM -->|Ensures private key<br>exists in Secret| SEC[Target Secret]
    TR -->|Creates when<br>issuance needed| CR[CertificateRequest]
    IS -->|Copies signed cert<br>to Secret on completion| SEC
    RD -->|Updates status<br>conditions| CERT

    CR -->|Processed by| ISSUER[Issuer Controller]
    ISSUER -->|Signs and returns cert| CR

certificates-key-manager

  • File: pkg/controller/certificates/keymanager/keymanager.go
  • Responsibility: Ensures a private key exists in the target Secret before any CertificateRequest is created. Generates a new key if one does not exist, or if the key specification has changed (e.g., switching from RSA to ECDSA).
  • Key Logic: Reads the Certificate’s spec.privateKey configuration (algorithm, size, rotation policy) and ensures the Secret contains a matching key. If rotationPolicy: Always is set, generates a new key for each issuance.

certificates-trigger

  • File: pkg/controller/certificates/trigger/trigger.go
  • Responsibility: Evaluates whether a Certificate needs a new CertificateRequest. This is the decision-maker for initial issuance, renewal, and re-issuance after spec changes.
  • Trigger Conditions:
    • No signed certificate exists in the target Secret.
    • The current certificate does not match the Certificate spec (e.g., DNS names changed).
    • The renewal time has been reached (calculated from NotAfter - renewBefore).
    • The Certificate has been manually triggered via the cert-manager.io/issuing annotation.
  • Key Logic: Compares the desired state (Certificate spec) with the actual state (contents of the Secret) and creates a CertificateRequest with the CSR when issuance is needed.

certificates-issuing

  • File: pkg/controller/certificates/issuing/issuing.go
  • Responsibility: Watches for completed CertificateRequests and copies the signed certificate into the target Secret. Handles the transition from “issuing” to “ready” state.
  • Key Logic:
    • Watches CertificateRequests owned by a Certificate.
    • When a CertificateRequest reaches a Ready=True condition, extracts the signed certificate chain.
    • Updates the target Secret with tls.crt (certificate + chain), tls.key (private key), and ca.crt (CA certificate).
    • Sets the cert-manager.io/certificate-name, cert-manager.io/issuer-name, and other metadata annotations on the Secret.
    • Cleans up old CertificateRequests based on the revisionHistoryLimit setting.

certificates-readiness

  • File: pkg/controller/certificates/readiness/readiness.go
  • Responsibility: Continuously evaluates the state of the Certificate and its associated Secret, updating status conditions.
  • Key Logic:
    • Reads the certificate from the target Secret and parses its X.509 properties.
    • Sets the Ready condition based on whether a valid, non-expired certificate exists.
    • Calculates and reports status.notBefore, status.notAfter, and status.renewalTime.
    • Emits Kubernetes Events for certificate expiration warnings.

3. Certificate Issuance Flow

Here is the detailed internal flow for a certificate issuance:

sequenceDiagram
    participant User
    participant K8s as API Server
    participant KM as key-manager
    participant TR as trigger
    participant IS as issuing
    participant RD as readiness

    User->>K8s: Create Certificate
    KM->>K8s: Read Certificate spec
    KM->>K8s: Generate private key
    KM->>K8s: Store key in Secret

    TR->>K8s: Read Certificate + Secret
    TR->>TR: Determine issuance needed
    TR->>K8s: Create CSR from private key
    TR->>K8s: Create CertificateRequest
    TR->>K8s: Set Certificate Issuing=True

    Note over K8s: CertificateRequest processed by issuer controller...

    IS->>K8s: Watch CertificateRequest
    IS->>IS: CertificateRequest Ready=True
    IS->>K8s: Update Secret (tls.crt, ca.crt)
    IS->>K8s: Set Certificate Issuing=False
    IS->>K8s: Increment revision annotation

    RD->>K8s: Read Certificate + Secret
    RD->>RD: Parse X.509 certificate
    RD->>K8s: Set Ready=True condition
    RD->>K8s: Set notAfter, renewalTime status

4. Private Key Management

cert-manager supports three private key algorithms:

AlgorithmSpec FieldDefault SizeUsage
RSAspec.privateKey.algorithm: RSA2048 bitsMost compatible, widely supported
ECDSAspec.privateKey.algorithm: ECDSAP-256Smaller keys, faster TLS handshakes
Ed25519spec.privateKey.algorithm: Ed25519N/AModern, high performance

Rotation Policies

  • Never (default): The private key is generated once and reused across renewals. The same key is used for all certificate re-issuances.
  • Always: A new private key is generated for every issuance. Provides forward secrecy but means the Secret contents change on each renewal.

Key generation is handled in pkg/util/pki/generate.go, which provides functions for each algorithm type.

5. Secret Format

The target Secret created by the issuing controller follows a standard format:

apiVersion: v1
kind: Secret
metadata:
  name: my-cert-tls
  annotations:
    cert-manager.io/certificate-name: my-cert
    cert-manager.io/issuer-name: letsencrypt-prod
    cert-manager.io/issuer-kind: ClusterIssuer
    cert-manager.io/issuer-group: cert-manager.io
    cert-manager.io/common-name: example.com
    cert-manager.io/alt-names: example.com,www.example.com
    cert-manager.io/ip-sans: ""
    cert-manager.io/uri-sans: ""
type: kubernetes.io/tls
data:
  tls.crt: <base64 certificate + chain>
  tls.key: <base64 private key>
  ca.crt: <base64 CA certificate>

The type: kubernetes.io/tls ensures compatibility with Kubernetes Ingress controllers and other TLS consumers. Additional output formats can be configured via spec.keystores for JKS and PKCS#12 formats.

6. Renewal Logic

The renewal mechanism is one of cert-manager’s most important features:

  1. Renewal Time Calculation: renewalTime = notAfter - renewBefore. If renewBefore is not set, it defaults to 2/3 of the certificate’s total duration.
  2. Trigger Check: The certificates-trigger controller compares time.Now() against renewalTime on each reconciliation.
  3. Renewal Execution: When renewal time is reached, a new CertificateRequest is created. The old certificate remains in the Secret and continues serving traffic until the new one is issued.
  4. Backoff on Failure: If issuance fails, cert-manager applies exponential backoff before retrying, preventing excessive load on the certificate authority.

Example Timeline (90-day Let’s Encrypt Certificate)

Day 0:   Certificate issued (NotAfter = Day 90)
Day 60:  Renewal triggered (default: 2/3 of 90 = 60 days)
Day 60:  New CertificateRequest created
Day 60:  New certificate issued, Secret updated
Day 150: Next renewal triggered

7. Configuration

Certificate Spec Fields

Key configuration options on the Certificate resource:

  • spec.secretName: Name of the Secret to store the certificate in (required).
  • spec.issuerRef: Reference to the Issuer or ClusterIssuer (required).
  • spec.dnsNames: List of DNS Subject Alternative Names (most common).
  • spec.ipAddresses: List of IP SANs.
  • spec.uris: List of URI SANs.
  • spec.duration: Requested certificate duration (default: 2160h / 90 days).
  • spec.renewBefore: How long before expiry to trigger renewal (default: 720h / 30 days).
  • spec.privateKey.algorithm: Key algorithm (RSA, ECDSA, Ed25519).
  • spec.privateKey.size: Key size for RSA (2048, 4096, 8192) or ECDSA curve (256, 384).
  • spec.privateKey.rotationPolicy: Key rotation policy (Never, Always).
  • spec.keystores: Additional output formats (JKS, PKCS#12).
  • spec.revisionHistoryLimit: Number of old CertificateRequests to retain.

Common Patterns

Basic TLS certificate:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: web-tls
spec:
  secretName: web-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - example.com
    - www.example.com

mTLS client certificate with ECDSA:

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: client-cert
spec:
  secretName: client-cert-tls
  issuerRef:
    name: internal-ca
    kind: Issuer
  duration: 720h
  renewBefore: 240h
  privateKey:
    algorithm: ECDSA
    size: 256
    rotationPolicy: Always
  usages:
    - client auth
  dnsNames:
    - service.internal

8. Extension Points

  • External Issuers: Third-party projects can implement their own CertificateRequest controllers to handle signing for custom CA types. The Certificate controller does not need modification; it creates CertificateRequests that external issuers pick up and process.
  • Keystores: The spec.keystores field allows configuring additional output formats. Custom keystore types could be added by extending the issuing controller’s Secret encoding logic.
  • Post-Issuance Hooks: While not natively supported, external controllers can watch for Certificate Ready=True events to trigger downstream actions (e.g., restarting deployments, updating external systems).

By understanding the Certificate controller’s sub-controller architecture and coordination mechanism, developers can effectively debug issuance issues, contribute to the codebase, and build extensions that integrate with the certificate lifecycle.