diff --git a/.github/workflows/sync.yml b/.github/workflows/sync.yml index a96d645..e99c270 100644 --- a/.github/workflows/sync.yml +++ b/.github/workflows/sync.yml @@ -124,12 +124,17 @@ jobs: # Routes and guides are different, and get a pull request. A new endpoint # usually needs a sentence of prose that a generator cannot write, and # opening a pull request is what creates the moment to write it. + # Pinned the same way as the pages above, and for the same reason: only + # the repository that dispatched is pinned. These two read certpilot + # alone, and used to take the dispatched SHA whoever sent it, so an agent + # merge asked for certpilot's routes.json at an agent commit and got a + # 404 (run 36064618794). - run: npm run sync env: - CERTPILOT_REF: ${{ github.event.client_payload.sha }} + CERTPILOT_REF: ${{ github.event.client_payload.repo == 'certpilot-agent' && 'main' || github.event.client_payload.sha }} - run: npm run sync:guides env: - CERTPILOT_REF: ${{ github.event.client_payload.sha }} + CERTPILOT_REF: ${{ github.event.client_payload.repo == 'certpilot-agent' && 'main' || github.event.client_payload.sha }} - uses: peter-evans/create-pull-request@v7 with: diff --git a/guides/agents.md b/guides/agents.md index 0f2cbed..844e3bb 100644 --- a/guides/agents.md +++ b/guides/agents.md @@ -1,4 +1,4 @@ - Two APIs, kept completely apart. @@ -300,6 +300,13 @@ grant's `renew_before_days`. A host that picked its own moment could decide to renew hourly, and four hundred of them would be a denial of service against the CA. +`renew_after` always falls inside the certificate's own lifetime, and never in +its first third. A lead time longer than two thirds of the lifetime is replaced +by renewing when a third remains, so a six-day certificate under the default 30 +days renews at day four. Without that bound, a certificate shorter than its lead +time was handed a `renew_after` that had already passed, and the host renewed it +on every cycle ([#109](https://github.com/certpilot/certpilot/issues/109)). + ## Installing, and reloading diff --git a/guides/ca-accounts-gateways.md b/guides/ca-accounts-gateways.md index cfbb7df..79866f8 100644 --- a/guides/ca-accounts-gateways.md +++ b/guides/ca-accounts-gateways.md @@ -1,4 +1,4 @@ - ## Registering diff --git a/guides/certificate-transparency.md b/guides/certificate-transparency.md index b1b82a9..e30a01a 100644 --- a/guides/certificate-transparency.md +++ b/guides/certificate-transparency.md @@ -1,4 +1,4 @@ - This is the half of discovery a network scan cannot reach. A scan answers "what diff --git a/guides/certificates.md b/guides/certificates.md index 45436e4..108c3d0 100644 --- a/guides/certificates.md +++ b/guides/certificates.md @@ -1,9 +1,15 @@ - `GET /certificates` filters on `status`, `environment`, `common_name`, `ca_account_id`. +Despite its name, `common_name` matches **any** of a certificate's names, the +subject common name or any SAN, by substring and ignoring case. Conforming CAs +are dropping the common name (Pebble already issues without one), and a +certificate whose only name is in its SANs must still be findable by it. A +certificate matches once however many of its names do. + ## Issuing ```http @@ -48,6 +54,9 @@ a policy that finds something must say so even when it does not block. `POST /certificates/:id/renew` runs the same path. The key is rotated and the new one persisted; a renewal that produced a certificate without storing its matching key would leave a record that looks healthy and cannot terminate TLS. +That is only possible for a key CertPilot already holds: a certificate whose +`key_custody` is `AGENT` or `EXTERNAL` is refused with `400`, and renewed by +whoever holds its key (see [Asking for one](/api/reference/renewal-queue#asking-for-one)). On failure the record is marked `RENEWAL_FAILED` with `renewal_error` set, and the previous certificate is left intact. diff --git a/guides/cloud-inventory.md b/guides/cloud-inventory.md index 800e1d0..580d736 100644 --- a/guides/cloud-inventory.md +++ b/guides/cloud-inventory.md @@ -1,4 +1,4 @@ - The third place certificates hide. A scan finds what is **served**; Certificate diff --git a/guides/dashboard.md b/guides/dashboard.md index 28d4e31..d11b631 100644 --- a/guides/dashboard.md +++ b/guides/dashboard.md @@ -1,4 +1,4 @@ - ## Statistics diff --git a/guides/deployment.md b/guides/deployment.md index b9d7734..4ab7b6a 100644 --- a/guides/deployment.md +++ b/guides/deployment.md @@ -1,4 +1,4 @@ - Everything above deployment in this document observes. This changes something diff --git a/guides/discovery.md b/guides/discovery.md index 7ad3d86..af67aee 100644 --- a/guides/discovery.md +++ b/guides/discovery.md @@ -1,4 +1,4 @@ - Scanning is operator-gated because it opens connections to third-party diff --git a/guides/display-tokens.md b/guides/display-tokens.md index bd9c48b..735db42 100644 --- a/guides/display-tokens.md +++ b/guides/display-tokens.md @@ -1,4 +1,4 @@ - Create: diff --git a/guides/live-event-stream.md b/guides/live-event-stream.md index f8ee693..034325d 100644 --- a/guides/live-event-stream.md +++ b/guides/live-event-stream.md @@ -1,4 +1,4 @@ - Opens with a `retry:` directive and a `snapshot` event carrying current diff --git a/guides/notification-channels.md b/guides/notification-channels.md index 134ea9c..8e75896 100644 --- a/guides/notification-channels.md +++ b/guides/notification-channels.md @@ -1,4 +1,4 @@ - Three channel types have a notifier: `slack`, `webhook`, `email`. Migration 001 diff --git a/guides/pki-ca-management.md b/guides/pki-ca-management.md index 266bb3b..a741132 100644 --- a/guides/pki-ca-management.md +++ b/guides/pki-ca-management.md @@ -1,4 +1,4 @@ - ## Importing the CAs behind a CA account diff --git a/guides/policies.md b/guides/policies.md index 10b41ef..9d805f5 100644 --- a/guides/policies.md +++ b/guides/policies.md @@ -1,4 +1,4 @@ - Rule types, all of which are evaluated: diff --git a/guides/renewal-queue.md b/guides/renewal-queue.md index 3dc3c1d..16ce6ee 100644 --- a/guides/renewal-queue.md +++ b/guides/renewal-queue.md @@ -1,4 +1,4 @@ - Renewal is the only part of this system that changes the world. Everything else @@ -34,6 +34,15 @@ weekly rate limit. A certificate with no CA account — anything discovered rath than issued — is refused with **400** at this point rather than becoming a job that fails forever. +So is a certificate whose private key CertPilot does not hold, again with +**400**. When `key_custody` is `AGENT`, the refusal names the agent and says how +to renew it on that host (`certpilot-agent request --name …`). When it is +`EXTERNAL`, it asks for a new signing request from whoever holds the key. A +renewal here always ends with the gateway generating a fresh keypair. For these +certificates, CertPilot would then hold a key it promised never to hold, and +the record would describe a certificate the key holder is not serving. A job +for one that was queued some other way is cancelled, not retried. + ## Where a job stands ```json @@ -137,7 +146,9 @@ true because the next call failed. The advice overrides the configured lead time in both directions — it can bring a renewal forward and hold one back — but **never past a seven-day safety -floor**. Inside that window a certificate renews regardless of what the CA +floor**, or past a third of the certificate's lifetime when that is shorter. A +six-day certificate is inside seven days from the moment it is issued, so an +unbounded floor would renew it on every sweep whatever the CA advised. Inside that window a certificate renews regardless of what the CA suggested, so a bad window, or a stale one left by a poller that stopped running, cannot defer something about to expire. diff --git a/scripts/sync-guides.mjs b/scripts/sync-guides.mjs index 9287135..a74371c 100644 --- a/scripts/sync-guides.mjs +++ b/scripts/sync-guides.mjs @@ -122,6 +122,9 @@ const CODE_TREE = 'https://github.com/certpilot/certpilot/blob/main' */ const ANCHOR_TO_PAGE = { 'authenticating-an-unattended-screen': '/api/display-tokens', + // Certificates → renewing: a key CertPilot does not hold is refused, and + // the rule is written up where the renewal queue is (certpilot#114). + 'asking-for-one': '/api/reference/renewal-queue#asking-for-one', } /**