docs: v2.1.0 release polish — strip internal bundle/phase tags, update status for OIDC ship

README:
- Rewrite Status block: drop the stale 'federated identity not yet
  shipped' line; flag v2.1.0 OIDC + sessions + back-channel logout
  + break-glass as early-access; encourage GitHub issues for IdP
  rough edges. (A1 framing — keep early-access umbrella, no
  SAML/WebAuthn/JIT roadmap teaser.)
- Add OIDC SSO bullet to 'What it does' covering per-IdP runbooks,
  group-claim → role mapping, AES-256-GCM client_secret encryption,
  JWKS auto-refresh, PKCE-S256, RFC 9700 §4.7.1 pre-login binding,
  RFC 9207 iss check, __Host- cookies, CSRF rotation, idle+absolute
  expiry, BCL, break-glass admin.
- Update Security paragraph: three auth paths (API keys / OIDC /
  break-glass), HMAC-signed sessions, CSRF rotation, RFC OIDC BCL.
- Correct CI coverage thresholds against
  .github/coverage-thresholds.yml (service 70%, handler 75%,
  crypto 88%, auth packages 85-95%); 'static analysis' replaces
  the inflated '11 linters' claim (actual count is 4 active).

Docs B3 sweep — strip operator-facing 'Bundle N' / 'Phase N' tags:
- docs/operator/auth-threat-model.md — rewrite intro; rename 5 H2
  sections (API-key + RBAC defenses / OIDC + sessions + break-glass
  defenses / OIDC + sessions threat catalogue / Closed federated-
  identity threats / Future-work threats); clean ~12 H3/prose hits.
- docs/operator/rbac.md — strip Bundle 1 framing from intro,
  scope_id deferral note, MCP tools section, day-0 bootstrap, and
  'Where to look next'.
- docs/operator/auth-benchmarks.md — drop 'Phase 14' framing from
  title intro, hardware floor caption, result table caption,
  methodology, and pre-merge audit section.
- docs/operator/security.md — already cleaned earlier this session
  (RBAC / day-0 / approval-bypass / OIDC federation / sessions /
  OIDC first-admin / break-glass H3s).
- docs/operator/oidc-runbooks/{index,keycloak,authentik,okta,
  azure-ad}.md — strip Auth Bundle 2 framing + Phase 10/3/4
  references; replace with feature-name prose.
- docs/operator/legacy-clients-tls-1.2.md — drop Bundle F / M-023
  audit-reference framing; keep CWE-326.
- docs/operator/database-tls.md — drop Bundle B / M-018 framing
  from intro + Helm section.
- docs/operator/runbooks/disaster-recovery.md — drop 'Production
  hardening II Phase 10' status callout.
- docs/migration/oidc-enable.md — retitle 'Enable OIDC SSO';
  strip Bundle 1/2 framing from prereqs, troubleshooting, related
  docs; update __Host- cookie callout from 'audit MED-14' to
  v2.1.0-BREAKING.
- docs/migration/api-keys-to-rbac.md — strip Bundle 1 framing from
  intro, migration table, IsAdmin section, and cross-references.
- docs/migration/acme-from-cert-manager.md — strip residual
  'Phase 5' tags from cert-manager integration test references.
- docs/reference/configuration.md — retitle Auth section.
- docs/reference/profiles.md — strip Bundle 1 Phase 9 framing
  from RequiresApproval section + Related list.
- docs/reference/auth-standards-implemented.md — rewrite intro
  (API-key + RBAC + OIDC + sessions + back-channel logout +
  break-glass); rename 'Bundle 1 (RBAC) standards covered
  separately' H2; clean per-row Phase references.
- docs/README.md — rewrite nav-table entries to drop Bundle 1/2
  parentheticals; retitle 'Enable OIDC SSO' migration entry.

No code or test changes; pure operator-facing prose polish for
the v2.1.0 tag.
This commit is contained in:
shankar0123
2026-05-11 16:54:07 +00:00
parent 1b03d0c594
commit 56e2ea1ad7
20 changed files with 260 additions and 292 deletions
+6 -6
View File
@@ -16,7 +16,7 @@ through cert-manager 1.15+. Target audience: Kubernetes operator who
has never deployed certctl before and wants a working
`Certificate``Secret` flow on their cluster in under 30 minutes.
The Phase 5 integration test (`make acme-cert-manager-test`) automates
The cert-manager integration test (`make acme-cert-manager-test`) automates
exactly the recipe below. The YAML snippets in this doc are byte-equal
to the files under `deploy/test/acme-integration/` — re-running the
test from a fresh clone produces the same results documented here.
@@ -24,7 +24,7 @@ test from a fresh clone produces the same results documented here.
## Prereqs
- A Kubernetes cluster (kind / k3d / EKS / GKE / AKS / on-prem). For
local trial, `kind v0.20+` works exactly the way the Phase 5 test
local trial, `kind v0.20+` works exactly the way the integration test
uses it. The kind config lives at
[`deploy/test/acme-integration/kind-config.yaml`](../deploy/test/acme-integration/kind-config.yaml).
- `kubectl` v1.27+, `helm` v3.13+.
@@ -37,7 +37,7 @@ test from a fresh clone produces the same results documented here.
which is the same idempotent installer the integration test uses.
- A certctl Helm chart published to a registry your cluster can pull
from. The Phase 5 test uses an `image.tag=test` placeholder; production
from. The integration test uses an `image.tag=test` placeholder; production
deployments use the actual image tag for your release line.
## Step 1 — Deploy certctl-server
@@ -99,7 +99,7 @@ recipe lives in
## Step 4 — Apply the ClusterIssuer
```yaml
# Phase 5 — sample ClusterIssuer for the certctl trust_authenticated
# sample ClusterIssuer for the certctl trust_authenticated
# auth mode (RFC 8555 §6 + certctl auth_mode=trust_authenticated, where
# the JWS-authenticated ACME account is trusted to issue any identifier
# the profile policy permits — no per-identifier ownership challenges).
@@ -169,7 +169,7 @@ HTTP-01 to work.
## Step 5 — Apply the Certificate
```yaml
# Phase 5 — Certificate resource the integration test applies and
# Certificate resource the integration test applies and
# waits for. The certctl-test-trust ClusterIssuer (trust_authenticated
# mode) issues the cert without any solver round-trip; the resulting
# Secret 'test-com-tls' is asserted to carry tls.crt + tls.key.
@@ -262,4 +262,4 @@ helm uninstall certctl-test
- [`docs/acme-traefik-walkthrough.md`](./acme-from-traefik.md) —
Traefik-side recipe.
- [`deploy/test/acme-integration/`](../deploy/test/acme-integration/) —
Phase 5 integration test (the same recipe, automated).
cert-manager integration test (the same recipe, automated).
+13 -15
View File
@@ -5,7 +5,7 @@
This is the upgrade guide for an existing certctl deployment moving
from v2.0.x's "every API key is admin or not" model to v2.1.0's
RBAC primitive. Everything keeps working through the upgrade - the
Bundle 1 migration backfills every existing API key to the
migration backfills every existing API key to the
`r-admin` role on first boot, so the pre-existing automation that
was using those keys does not change behavior. **However**, most
keys do not need full admin power; this guide walks the operator
@@ -13,7 +13,7 @@ through the post-upgrade scope-down flow.
## ⚠️ SECURITY: AUDIT YOUR API KEYS
Bundle 1 maps **every** existing `CERTCTL_API_KEYS_NAMED` entry
v2.1.0 maps **every** existing `CERTCTL_API_KEYS_NAMED` entry
(and every legacy `CERTCTL_AUTH_SECRET`-synthesized key) to the
`r-admin` role on the first boot after migration 000029 applies.
This is the safe-for-back-compat default - your CI / agents / scripts
@@ -29,18 +29,18 @@ release notes for v2.1.0 lead with this callout for a reason.
### 1. Apply the migration
The migration runner is idempotent. Re-applying is a no-op if the
schema is already at the target version. Migrations that ship in
the Bundle 1 slice of v2.1.0:
schema is already at the target version. The five RBAC migrations
that ship in v2.1.0:
| Migration | What it does |
|---|---|
| `000029_rbac.up.sql` | Creates `tenants`, `roles`, `permissions`, `role_permissions`, `actor_roles`. Seeds 7 default roles + 33-permission catalogue + the synthetic `actor-demo-anon` admin grant. Backfills every named API key into `actor_roles` with the `r-admin` role. |
| `000030_rbac_admin_perms.up.sql` | Seeds 5 admin-only fine-grained permissions (`cert.bulk_revoke`, `crl.admin`, `scep.admin`, `est.admin`, `ca.hierarchy.manage`) into `r-admin` only. |
| `000031_api_keys.up.sql` | Creates the `api_keys` table for runtime-minted keys (Bundle 1 Phase 6 bootstrap). |
| `000031_api_keys.up.sql` | Creates the `api_keys` table for runtime-minted keys (day-0 bootstrap path). |
| `000032_audit_category.up.sql` | Adds `event_category` column to `audit_events` with the closed enum (`cert_lifecycle` / `auth` / `config`). |
| `000033_approval_kinds.up.sql` | Adds `approval_kind` + `payload` to `issuance_approval_requests` for the Phase 9 approval-bypass closure. |
| `000033_approval_kinds.up.sql` | Adds `approval_kind` + `payload` to `issuance_approval_requests` for the approval-bypass closure. |
The Bundle 1 server applies these on first boot. No operator
The v2.1.0 server applies these on first boot. No operator
action is required other than running the upgrade.
### 2. Verify the backfill landed
@@ -147,8 +147,8 @@ bootstrap flow + the threat model.
## What changes for code that called `IsAdmin`
Pre-Bundle-1, the five admin handlers checked `auth.IsAdmin(ctx)`
directly in the body. Bundle 1 Phase 3.5 moved those checks to
In v2.0.x, the five admin handlers checked `auth.IsAdmin(ctx)`
directly in the body. v2.1.0 moved those checks to
the router via the `auth.RequirePermission` middleware (wrapped
through the `rbacGate` helper in
`internal/api/router/router.go`). The behavior contract is
@@ -164,9 +164,9 @@ the helper is internal), the new convention is:
(or `migrations/000029_rbac.up.sql`'s catalogue).
3. Grant the perm to the right default roles.
The five admin-only fine-grained perms shipped in Phase 3.5 stay
on `r-admin` only by default. Operators delegate by creating
custom roles with the specific perm.
The five admin-only fine-grained perms stay on `r-admin` only by
default. Operators delegate by creating custom roles with the
specific perm.
## Helm-specific upgrade
@@ -288,9 +288,7 @@ boot regardless of schema version).
- [`docs/operator/auth-threat-model.md`](../operator/auth-threat-model.md) -
what the new controls defend against
- [`docs/reference/profiles.md`](../reference/profiles.md) - the
Phase 9 approval-bypass closure
approval-bypass closure on `RequiresApproval` profile edits
- [`docs/operator/security.md`](../operator/security.md) - the
full security posture
- `cowork/auth-bundle-1-prompt.md` - the design + phase plan
- `cowork/auth-bundles-index.md` - the per-phase status tracker
- `CHANGELOG.md` - the v2.1.0 release notes lead with this guide
+14 -14
View File
@@ -1,10 +1,10 @@
# Enable OIDC SSO on a Bundle-1-merged deployment
# Enable OIDC SSO
> Last reviewed: 2026-05-10
This guide walks an operator already running certctl with Bundle 1 (RBAC primitive on top of API-key auth) through enabling OIDC SSO from Bundle 2. The path is additive: API-key auth keeps working unchanged; OIDC sits alongside as a second authentication surface for human users.
This guide walks an operator already running certctl with API-key auth + RBAC through enabling OIDC SSO. The path is additive: API-key auth keeps working unchanged; OIDC sits alongside as a second authentication surface for human users.
If you are upgrading from a pre-Bundle-1 deployment, finish [`api-keys-to-rbac.md`](api-keys-to-rbac.md) first. If you have not deployed certctl at all, start with [`getting-started/quickstart.md`](../getting-started/quickstart.md). For the canonical mental model + per-flow threat coverage, see [`security.md`](../operator/security.md) and [`auth-threat-model.md`](../operator/auth-threat-model.md).
If you are upgrading from a pre-RBAC (v2.0.x) deployment, finish [`api-keys-to-rbac.md`](api-keys-to-rbac.md) first. If you have not deployed certctl at all, start with [`getting-started/quickstart.md`](../getting-started/quickstart.md). For the canonical mental model + per-flow threat coverage, see [`security.md`](../operator/security.md) and [`auth-threat-model.md`](../operator/auth-threat-model.md).
## What "enable OIDC" gives you
@@ -19,15 +19,15 @@ After this migration:
What does NOT change:
- API keys keep working. Existing automation continues to authenticate via `Authorization: Bearer` exactly as before.
- The break-glass admin path (Phase 7.5) stays default-OFF.
- The break-glass admin path stays default-OFF.
- The auditor split + approval workflow + RBAC primitive are unchanged.
## Pre-requisites
**On certctl side:**
- Server build ≥ v2.1.0 (the post-Bundle-2 master). Confirm via `curl https://<your-host>:8443/api/v1/version`.
- `CERTCTL_CONFIG_ENCRYPTION_KEY` set in the server environment. This is the passphrase that encrypts the OIDC `client_secret` at rest. Use a stable, secrets-manager-stored value at least 32 random bytes long. **The server refuses to start if the key is missing AND any source='database' rows already exist** (per Bundle B / M-001 / CWE-311 closure). Set this before doing anything else.
- Server build ≥ v2.1.0. Confirm via `curl https://<your-host>:8443/api/v1/version`.
- `CERTCTL_CONFIG_ENCRYPTION_KEY` set in the server environment. This is the passphrase that encrypts the OIDC `client_secret` at rest. Use a stable, secrets-manager-stored value at least 32 random bytes long. **The server refuses to start if the key is missing AND any source='database' rows already exist** (CWE-311 fail-closed gate). Set this before doing anything else.
- An admin actor available to drive the configuration. The actor needs the `auth.oidc.create` + `auth.oidc.edit` permissions; `r-admin` carries both by default. Get one via the day-0 bootstrap path if you don't have one yet.
- HTTPS-only control plane (post-v2.2 milestone — this is the default). The OIDC redirect URI MUST be `https://`.
@@ -40,7 +40,7 @@ What does NOT change:
### 1. Pin `CERTCTL_CONFIG_ENCRYPTION_KEY`
If your deployment already has it set (the Bundle B M-001 fail-closed gate enforces this for any source='database' issuer/target row), skip this step. If you don't:
If your deployment already has it set (the CWE-311 fail-closed gate enforces this for any source='database' issuer/target row), skip this step. If you don't:
```bash
# Generate a 32-byte random key + base64-encode it.
@@ -55,7 +55,7 @@ Then make the server consume it at boot:
export CERTCTL_CONFIG_ENCRYPTION_KEY="$(cat /etc/certctl/config-encryption-key)"
```
Restart the server. Confirm the boot log does NOT show the `ErrEncryptionKeyRequired` warning. If it does, the server refuses to start because there's pre-existing source='database' material that needs to be re-sealed; see the pre-Bundle-B migration notes for re-encryption flow.
Restart the server. Confirm the boot log does NOT show the `ErrEncryptionKeyRequired` warning. If it does, the server refuses to start because there's pre-existing source='database' material that needs to be re-sealed; see [`docs/operator/security.md`](../operator/security.md) for the re-encryption flow.
### 2. Pick an IdP runbook + complete the IdP-side configuration
@@ -211,10 +211,10 @@ The user clicked the OIDC login button, then the browser tab idled past the 10-m
Either the user double-submitted a callback URL (clicked it twice from email or browser history), or a CSRF attempt. The pre-login row is single-use; second consumption returns `ErrPreLoginNotFound`. Have them retry from the login page.
**`Sessions revoked but the user can still hit the API.`**
Check the Phase 4 session contract: the cookie is HMAC-validated on every request, but the actual database row is what `Revoke` deletes. If your reverse proxy is caching the response or the `certctl_session` cookie wasn't actually cleared on the client, the cookie hits the server's session middleware which returns 401 on the missing-row lookup. The middleware never serves stale data; the issue is upstream of certctl in this case.
Check the session contract: the cookie is HMAC-validated on every request, but the actual database row is what `Revoke` deletes. If your reverse proxy is caching the response or the `__Host-certctl_session` cookie wasn't actually cleared on the client, the cookie hits the server's session middleware which returns 401 on the missing-row lookup. The middleware never serves stale data; the issue is upstream of certctl in this case.
**JWKS rotation: an IdP rotated its signing key and existing users start failing login.**
Click **Refresh discovery cache** on the OIDC provider detail page (or `POST /api/v1/auth/oidc/providers/<id>/refresh`). The certctl service re-fetches discovery + JWKS. New tokens validate immediately. The Phase 10 integration test exercises this drill end to end.
Click **Refresh discovery cache** on the OIDC provider detail page (or `POST /api/v1/auth/oidc/providers/<id>/refresh`). The certctl service re-fetches discovery + JWKS. New tokens validate immediately. The Keycloak integration test exercises this drill end to end.
**Database row count drift.**
After OIDC is live, expect to see new rows under:
@@ -231,12 +231,12 @@ All ten of these tables are tenant-scoped (`tenant_id` column); single-tenant de
- Run [`docs/operator/oidc-runbooks/<your-idp>.md`](../operator/oidc-runbooks/index.md) end to end to fill in the validation checklist + sign-off line.
- Read [`docs/operator/auth-benchmarks.md`](../operator/auth-benchmarks.md) for the steady-state + cold-cache performance baselines.
- Review the [`auth-threat-model.md`](../operator/auth-threat-model.md) Bundle 2 sections to understand the failure modes the OIDC + sessions surface defends against.
- Review the [`auth-threat-model.md`](../operator/auth-threat-model.md) OIDC + sessions + break-glass sections to understand the failure modes the federated-identity surface defends against.
- Schedule a rotation reminder for the OIDC `client_secret` (typically 6-12 months; the IdP doesn't auto-rotate it). Edit the provider via the GUI when the time comes; leaving `client_secret` blank in the edit form preserves the existing ciphertext, providing a value rotates.
## `__Host-` cookie rename (Audit 2026-05-10 MED-14, BREAKING)
## `__Host-` cookie rename (BREAKING)
Post-Bundle-2 deploys carrying the 2026-05-10 audit-fix wave include a wire-format change to the three auth cookies: they now carry the `__Host-` prefix. The cookie names are:
v2.1.0 carries a wire-format change to the three auth cookies: they now carry the `__Host-` prefix. The cookie names are:
- `__Host-certctl_session` (was `certctl_session`)
- `__Host-certctl_csrf` (was `certctl_csrf`)
@@ -253,7 +253,7 @@ If you have GUI customizations that read `document.cookie` directly, update them
## Cross-references
- [`docs/operator/oidc-runbooks/index.md`](../operator/oidc-runbooks/index.md) — per-IdP setup guides.
- [`docs/operator/security.md`](../operator/security.md) — overall auth surface incl. this Bundle 2 OIDC layer.
- [`docs/operator/security.md`](../operator/security.md) — overall auth surface including this OIDC layer.
- [`docs/operator/auth-threat-model.md`](../operator/auth-threat-model.md) — threat model.
- [`docs/operator/auth-benchmarks.md`](../operator/auth-benchmarks.md) — performance baselines.
- [`docs/reference/auth-standards-implemented.md`](../reference/auth-standards-implemented.md) — RFC + CWE evidence list.