|
| 1 | +# Certificate Signing |
| 2 | + |
| 3 | +This guide explains how the operator bootstraps a Certificate Authority, signs certificates, and distributes CRLs. |
| 4 | + |
| 5 | +## CA Bootstrap |
| 6 | + |
| 7 | +When a CertificateAuthority resource is created, the operator runs a setup Job that initializes the CA on a PVC: |
| 8 | + |
| 9 | +```mermaid |
| 10 | +sequenceDiagram |
| 11 | + participant User |
| 12 | + participant Operator |
| 13 | + participant Job as CA Setup Job |
| 14 | + participant PVC as CA PVC |
| 15 | + participant K8s as Kubernetes API |
| 16 | +
|
| 17 | + User->>Operator: Create CertificateAuthority |
| 18 | + Operator->>K8s: Create PVC ({ca}-data) |
| 19 | + Operator->>K8s: Create ServiceAccount + RBAC |
| 20 | + Operator->>K8s: Create Job ({ca}-setup) |
| 21 | + Job->>PVC: Run puppetserver ca setup |
| 22 | + Job->>K8s: Create Secret {ca}-ca (public cert) |
| 23 | + Job->>K8s: Create Secret {ca}-ca-key (private key) |
| 24 | + Job->>K8s: Create Secret {ca}-ca-crl (CRL) |
| 25 | + Job-->>Operator: Job completed |
| 26 | + Operator->>Operator: Phase -> Ready |
| 27 | +``` |
| 28 | + |
| 29 | +The setup Job: |
| 30 | + |
| 31 | +1. Runs `puppetserver ca setup` on the PVC to generate the CA key pair and self-signed certificate |
| 32 | +2. Exports three Secrets via the Kubernetes API: |
| 33 | + - **`{ca}-ca`** - public CA certificate (`ca_crt.pem`), mounted in all pods |
| 34 | + - **`{ca}-ca-key`** - CA private key (`ca_key.pem`), never mounted in pods |
| 35 | + - **`{ca}-ca-crl`** - certificate revocation list, mounted in non-CA pods |
| 36 | +3. If a Certificate resource already exists for the CA server, the Job also signs and exports its TLS Secret |
| 37 | + |
| 38 | +The Job is idempotent: if the CA is already initialized on the PVC, it skips setup and only ensures the Secrets exist. |
| 39 | + |
| 40 | +## Certificate Signing Strategies |
| 41 | + |
| 42 | +The operator uses two strategies depending on when the Certificate is created relative to the CA: |
| 43 | + |
| 44 | +### Strategy 1: CA Setup Export |
| 45 | + |
| 46 | +**When:** The Certificate exists before or at the same time as the CA setup Job runs. |
| 47 | + |
| 48 | +The CA setup Job signs the certificate as part of the initial `puppetserver ca setup` and exports the cert+key directly as a Kubernetes Secret. The Certificate controller detects the existing Secret, adopts it (sets ownerReference), and marks the Certificate as `Signed`. |
| 49 | + |
| 50 | +This is the typical path for the **CA server's own certificate**. |
| 51 | + |
| 52 | +### Strategy 2: HTTP Signing |
| 53 | + |
| 54 | +**When:** The Certificate is created after the CA is already `Ready`. |
| 55 | + |
| 56 | +This is the typical path for **non-CA compile servers**: |
| 57 | + |
| 58 | +```mermaid |
| 59 | +sequenceDiagram |
| 60 | + participant Cert as Certificate Controller |
| 61 | + participant K8s as Kubernetes API |
| 62 | + participant CA as CA Server (Puppetserver) |
| 63 | +
|
| 64 | + Cert->>K8s: Generate RSA 4096 key |
| 65 | + Cert->>K8s: Store key in {cert}-tls-pending Secret |
| 66 | + Cert->>CA: PUT /puppet-ca/v1/certificate_request/{certname} |
| 67 | + CA-->>Cert: 200 OK (CSR accepted) |
| 68 | +
|
| 69 | + loop Poll every 5s |
| 70 | + Cert->>CA: GET /puppet-ca/v1/certificate/{certname} |
| 71 | + CA-->>Cert: Signed certificate (when ready) |
| 72 | + end |
| 73 | +
|
| 74 | + Cert->>K8s: Create {cert}-tls Secret (cert.pem + key.pem) |
| 75 | + Cert->>K8s: Delete {cert}-tls-pending Secret |
| 76 | + Cert->>Cert: Phase -> Signed |
| 77 | +``` |
| 78 | + |
| 79 | +The controller: |
| 80 | + |
| 81 | +1. Generates an RSA 4096-bit private key and stores it in a temporary `{cert}-tls-pending` Secret |
| 82 | +2. Creates a CSR with the configured `certname` and `dnsAltNames` |
| 83 | +3. Submits the CSR via HTTP PUT to the CA server's Puppetserver API |
| 84 | +4. Polls for the signed certificate via HTTP GET (every 5 seconds) |
| 85 | +5. Once signed, creates the final `{cert}-tls` Secret and deletes the pending Secret |
| 86 | + |
| 87 | +The pending Secret ensures idempotency: if the controller restarts mid-signing, it reuses the same key instead of generating a new one. |
| 88 | + |
| 89 | +### Service Discovery |
| 90 | + |
| 91 | +The Certificate controller discovers the CA server endpoint automatically: |
| 92 | + |
| 93 | +1. Find all Configs with `authorityRef` pointing to the CA |
| 94 | +2. Find a Server with `ca: true` referencing one of those Configs |
| 95 | +3. Use the first `poolRef` as the Kubernetes Service name |
| 96 | +4. Endpoint: `https://{pool-name}.{namespace}.svc:8140` |
| 97 | + |
| 98 | +No manual URL configuration is needed. |
| 99 | + |
| 100 | +## CRL Distribution |
| 101 | + |
| 102 | +The operator periodically fetches the CRL from the CA server and stores it as a Secret: |
| 103 | + |
| 104 | +1. Fetches CRL from `https://{ca-service}:8140/puppet-ca/v1/certificate_revocation_list/ca` |
| 105 | +2. Updates the `{ca}-ca-crl` Secret with the fresh CRL |
| 106 | +3. Requeues after `spec.crlRefreshInterval` (default: 5 minutes) |
| 107 | + |
| 108 | +Non-CA pods mount the CRL Secret as a **directory volume** (without SubPath), which allows kubelet to auto-sync the content without pod restarts. CA pods read the CRL directly from their PVC. |
| 109 | + |
| 110 | +## Secrets Overview |
| 111 | + |
| 112 | +| Secret | Contents | Created By | Mounted In | |
| 113 | +|--------|----------|------------|------------| |
| 114 | +| `{ca}-ca` | `ca_crt.pem` | CA setup Job | All pods (trust chain) | |
| 115 | +| `{ca}-ca-key` | `ca_key.pem` | CA setup Job | Never (API access only) | |
| 116 | +| `{ca}-ca-crl` | `ca_crl.pem` | CA setup Job, then operator refresh | Non-CA pods (directory mount) | |
| 117 | +| `{cert}-tls` | `cert.pem`, `key.pem` | CA setup Job or Certificate controller | Server pods (SSL) | |
| 118 | +| `{cert}-tls-pending` | `key.pem` | Certificate controller | Never (temporary, deleted after signing) | |
| 119 | + |
| 120 | +## Phase Lifecycle |
| 121 | + |
| 122 | +### CertificateAuthority |
| 123 | + |
| 124 | +``` |
| 125 | +Pending -> Initializing -> Ready |
| 126 | + | |
| 127 | + v |
| 128 | + Error |
| 129 | +``` |
| 130 | + |
| 131 | +| Phase | Description | |
| 132 | +|-------|-------------| |
| 133 | +| `Pending` | Waiting for Config with `authorityRef` pointing to this CA | |
| 134 | +| `Initializing` | CA setup Job is running | |
| 135 | +| `Ready` | CA Secrets created, certificates can be signed | |
| 136 | +| `Error` | Setup Job failed (retried up to 3 times) | |
| 137 | + |
| 138 | +### Certificate |
| 139 | + |
| 140 | +``` |
| 141 | +Pending -> Requesting -> Signed |
| 142 | + | |
| 143 | + v |
| 144 | + Error |
| 145 | +``` |
| 146 | + |
| 147 | +| Phase | Description | |
| 148 | +|-------|-------------| |
| 149 | +| `Pending` | Waiting for CertificateAuthority to reach `Ready` | |
| 150 | +| `Requesting` | CSR submitted, polling for signed certificate | |
| 151 | +| `Signed` | TLS Secret created, Server can mount it | |
| 152 | +| `Error` | Signing failed | |
0 commit comments