cert-manager Code Walkthrough

cert-manager Codebase Walkthrough

Welcome to the cert-manager codebase walkthrough. This guide helps experienced Go developers navigate the internals of cert-manager, a Kubernetes controller that automates X.509 certificate management. We’ll cover execution entry points, core abstractions, the certificate issuance lifecycle, and common patterns used throughout the codebase.

1. Where Execution Starts

cert-manager consists of four main binaries, each with its own entry point in the cmd/ directory:

Key Entry Points

  • cert-manager controller: The main controller binary that runs all certificate lifecycle controllers.

    • File: cmd/controller/main.go
    • Function: main() calls into app.NewServerCommand() which configures the controller-manager, registers all controllers, and starts the leader election loop.
    • Initialization: Registers controller constructors via controllerpkg.Known map, sets up informer factories for cert-manager CRDs and core Kubernetes resources, then starts the selected controllers.
  • cert-manager webhook: The admission webhook server for validation and defaulting.

    • File: cmd/webhook/main.go
    • Function: main() sets up the webhook server with TLS, registers validation and mutation handlers for all cert-manager API types.
    • Initialization: Configures serving certificates (can bootstrap its own TLS via a self-signed CA), registers conversion webhooks for API version migration.
  • cert-manager cainjector: The CA bundle injection controller.

    • File: cmd/cainjector/main.go
    • Function: main() starts a controller that watches annotated resources and injects CA certificates from referenced Secrets.
  • ACME solver: A temporary HTTP server for ACME HTTP-01 challenge validation.

    • File: cmd/acmesolver/main.go
    • Function: main() starts a minimal HTTP server that responds to /.well-known/acme-challenge/<token> requests with the computed challenge key.

Startup Process Overview

Each binary follows the standard Kubernetes controller-manager pattern:

  1. Parse command-line flags and build a configuration object.
  2. Create a Kubernetes client and informer factories.
  3. Register controllers and start informer caches.
  4. Begin leader election (for the main controller) and start reconciliation loops.

The controller binary is the most complex, as it registers and manages dozens of individual controllers that each handle a specific aspect of certificate management.

2. Core Abstractions

cert-manager’s codebase is built around Kubernetes custom resources and the controller-runtime pattern. Understanding these abstractions is essential for navigating the code.

Key Types and Interfaces

  • Certificate: Declared in pkg/apis/certmanager/v1/types_certificate.go. The primary user-facing resource that defines desired certificate properties (DNS names, duration, issuer reference, private key algorithm).
  • CertificateRequest: Declared in pkg/apis/certmanager/v1/types_certificate_request.go. Represents a one-shot CSR submission to an issuer. Immutable after creation.
  • Issuer / ClusterIssuer: Declared in pkg/apis/certmanager/v1/types_issuer.go. Configuration resources that define how to connect to a certificate authority.
  • Order: Declared in pkg/apis/acme/v1/types_order.go. ACME-specific resource tracking a certificate order with an ACME server.
  • Challenge: Declared in pkg/apis/acme/v1/types_challenge.go. ACME-specific resource for individual domain validation challenges.
  • Issuer Interface: Defined in pkg/issuer/issuer.go. The core interface that all issuer implementations must satisfy, with a Sign(ctx, CertificateRequest) ([]byte, error) method.

Component Diagram

graph TD
    subgraph API Types
        CERT[Certificate]
        CR[CertificateRequest]
        ISS[Issuer / ClusterIssuer]
        ORD[Order]
        CH[Challenge]
    end

    subgraph Controllers
        CT[certificates-trigger]
        CK[certificates-key-manager]
        CI[certificates-issuing]
        CRR[certificates-readiness]
        CRI[certificaterequests-issuer-*]
        OC[orders controller]
        CC[challenges controller]
        IS[ingress-shim]
    end

    subgraph Issuer Implementations
        ACME[ACME Issuer]
        CA[CA Issuer]
        SS[SelfSigned Issuer]
        VLT[Vault Issuer]
        VEN[Venafi Issuer]
    end

    CERT --> CT
    CT --> CR
    CK --> CERT
    CI --> CR
    CRR --> CERT
    CR --> CRI
    CRI --> ACME
    CRI --> CA
    CRI --> SS
    CRI --> VLT
    CRI --> VEN
    ACME --> ORD
    ORD --> OC
    OC --> CH
    CH --> CC
    IS -->|Creates| CERT
    ISS -->|Configures| CRI

This diagram shows how the API types flow through the controller hierarchy. Certificate resources trigger CertificateRequests, which are processed by issuer-specific controllers. For ACME, Orders and Challenges add additional layers.

3. Request/Operation Lifecycle

Let’s trace the most common operation: creating a Certificate and obtaining a signed certificate from Let’s Encrypt via ACME.

Step-by-Step Flow

  1. User creates a Certificate resource:

    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: my-cert
    spec:
      secretName: my-cert-tls
      issuerRef:
        name: letsencrypt-prod
        kind: ClusterIssuer
      dnsNames:
        - example.com
  2. certificates-key-manager detects the new Certificate, generates an RSA/ECDSA private key, and stores it in the target Secret (my-cert-tls).

    • File: pkg/controller/certificates/keymanager/keymanager.go
  3. certificates-trigger evaluates the Certificate and determines issuance is needed (no existing certificate or certificate approaching expiry). It creates a CertificateRequest containing the CSR signed with the private key.

    • File: pkg/controller/certificates/trigger/trigger.go
  4. certificaterequests-issuer-acme controller picks up the CertificateRequest, reads the ClusterIssuer configuration, and creates an Order resource with the ACME server.

    • File: pkg/controller/certificaterequests/acme/acme.go
  5. orders controller reconciles the Order, communicating with the ACME server to determine required challenges. It creates Challenge resources for each domain.

    • File: pkg/controller/acmeorders/controller.go
  6. challenges controller solves each Challenge. For HTTP-01, it creates a temporary Pod (running acmesolver), a Service, and an Ingress to serve the challenge token. For DNS-01, it creates a TXT record via the configured DNS provider.

    • File: pkg/controller/acmechallenges/controller.go
  7. ACME server validates the challenge by checking the HTTP endpoint or DNS record.

  8. orders controller detects all challenges are valid, finalizes the Order with the ACME server, and receives the signed certificate chain.

  9. certificaterequests-issuer-acme updates the CertificateRequest with the signed certificate.

  10. certificates-issuing watches for completed CertificateRequests, updates the target Secret with the signed certificate and CA chain, and marks the Certificate as Ready.

    • File: pkg/controller/certificates/issuing/issuing.go
  11. certificates-readiness continuously monitors the certificate in the Secret and updates the Certificate’s status conditions, including the NotAfter time and renewal time.

    • File: pkg/controller/certificates/readiness/readiness.go

4. Reading Order

For a developer new to the cert-manager codebase, here is a recommended path through the code:

  1. Start with API types:

    • File: pkg/apis/certmanager/v1/types_certificate.go
    • Why: Understand the Certificate spec and status fields. This is the primary resource users interact with.
  2. Certificate trigger controller:

    • File: pkg/controller/certificates/trigger/trigger.go
    • Why: See how cert-manager decides when to issue or renew a certificate. This is the entry point for the issuance workflow.
  3. CertificateRequest processing:

    • File: pkg/controller/certificaterequests/acme/acme.go
    • Why: Understand how CertificateRequests are handled for the most common issuer type (ACME).
  4. ACME Order and Challenge flow:

    • Files: pkg/controller/acmeorders/controller.go, pkg/controller/acmechallenges/controller.go
    • Why: Trace the ACME-specific flow from order creation through challenge solving.
  5. Certificate issuing controller:

    • File: pkg/controller/certificates/issuing/issuing.go
    • Why: See how the signed certificate is stored in the Kubernetes Secret and the Certificate status is finalized.
  6. Ingress shim:

    • File: pkg/controller/ingress-shim/controller.go
    • Why: Understand how cert-manager integrates with Kubernetes Ingress resources for automatic certificate provisioning.
  7. Issuer implementations:

    • Directory: pkg/issuer/
    • Why: Explore the different CA backends (ACME, CA, SelfSigned, Vault, Venafi) and how they implement the issuer interface.
  8. Webhook validation:

    • Directory: internal/webhook/
    • Why: Understand how cert-manager validates and defaults resources before they are persisted.

5. Common Patterns

cert-manager employs several recurring design patterns that are important to recognize:

  • Controller Registration: Controllers are registered via a Known map in the controller package. Each controller provides a constructor function and metadata (informer requirements, feature gates). This allows controllers to be selectively enabled/disabled via CLI flags.

  • Status Conditions: cert-manager uses Kubernetes-style status conditions extensively. The Ready condition on Certificate and CertificateRequest resources is the primary signal for completion. Controllers set, update, and watch conditions to coordinate lifecycle phases.

  • Annotation-Based Coordination: Controllers communicate through annotations on shared resources. For example, the cert-manager.io/certificate-revision annotation tracks the issuance revision, allowing the issuing controller to detect stale CertificateRequests.

  • Informer-Based Watches: All controllers use SharedInformerFactories to watch resources efficiently. The controller binary starts informer factories for both cert-manager CRDs (cmclient) and core Kubernetes types (kubeclient), sharing caches across controllers.

  • External Issuer Pattern: cert-manager defines a clear interface for external issuers. Third-party projects implement their own CertificateRequest controllers that watch for CertificateRequests referencing their issuer type, sign them, and update the status. This pattern enables projects like google-cas-issuer and aws-privateca-issuer to exist independently.

  • Feature Gates: cert-manager uses feature gates (pkg/feature/) to control experimental or optional functionality. Controllers check feature gates at startup to decide whether to register themselves.

  • ACME DNS Provider Plugins: DNS-01 challenge solvers are implemented via a webhook interface (pkg/acme/webhook/), allowing third-party DNS providers to be added without modifying the core codebase.

Design Trade-offs

  • Kubernetes-Native vs. Performance: cert-manager stores all state in the Kubernetes API (etcd), which provides durability and observability but introduces API server load for high certificate volume deployments.
  • CRD Granularity: Using separate CRDs for each lifecycle phase (Certificate, CertificateRequest, Order, Challenge) provides visibility but means a single certificate issuance can create 4+ resources.
  • Leader Election: Only one controller pod is active at a time via leader election, trading horizontal scalability for simplicity in state management.

By recognizing these patterns and trade-offs, you will be well-equipped to navigate, debug, and contribute to the cert-manager codebase effectively.