From 6ac3c7e4efe73e029d19153a88600f1755084cd9 Mon Sep 17 00:00:00 2001 From: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> Date: Wed, 30 Sep 2026 14:35:29 -0400 Subject: [PATCH] docs(fleet-manager): the operator CA is external; migrating from operator_ca_node A new page covers the external operator CA: where the manager takes the trust anchor from and the file-over-database precedence, the refusal of a node's CA, the denylist and CRL revocation, operatorRevocationPolicy soft and hard, the MCP rule that needs a current CRL, the new config keys, and the stored self-signed certificate with its fingerprint check. A second page walks a manager off operator_ca_node. The Helm values table marks operatorCANode as removed, the Operators section of the web UI page describes revoking through the denylist, and the overview says LINK approval no longer sends the operator CA to the node. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com> --- docs/fleet-manager/helm.md | 2 +- .../migrating-from-operator-ca-node.md | 50 ++++++++ docs/fleet-manager/operator-ca.md | 119 ++++++++++++++++++ docs/fleet-manager/overview.md | 6 +- docs/fleet-manager/web-ui.md | 14 +-- sidebars.ts | 2 + 6 files changed, 182 insertions(+), 11 deletions(-) create mode 100644 docs/fleet-manager/migrating-from-operator-ca-node.md create mode 100644 docs/fleet-manager/operator-ca.md diff --git a/docs/fleet-manager/helm.md b/docs/fleet-manager/helm.md index 928cefd..f76ad3d 100644 --- a/docs/fleet-manager/helm.md +++ b/docs/fleet-manager/helm.md @@ -77,7 +77,7 @@ The full list is in [`chart/fleet-manager/values.yaml`](https://github.com/Crypt | `authBypass` | `false` | Development only. Turns off client-certificate login and TLS. | | `tls.certSecret` | `""` | The TLS Secret. | | `operatorCA.configMap` | `""` | The ConfigMap with `operator-ca.pem`. | -| `operatorCANode` | `""` | The node that acts as the operator CA, for issuing and revoking operator certificates. | +| `operatorCANode` | `""` | Removed. A CryptOS node can't be the operator CA; setting it fails the render. See [Migrating from operator_ca_node](./migrating-from-operator-ca-node.md). | | `mcp.enabled` / `mcp.publicURL` | `false` / `""` | The MCP endpoint for AI agents. See the manager's [MCP guide](https://github.com/CryptOS-PKI/manager/blob/main/docs/mcp.md#with-the-helm-chart). | | `database.existingSecret` | `""` | The Secret with the Postgres connection string. | | `database.secretKey` | `database-url` | The key inside that Secret. | diff --git a/docs/fleet-manager/migrating-from-operator-ca-node.md b/docs/fleet-manager/migrating-from-operator-ca-node.md new file mode 100644 index 0000000..87bc3ea --- /dev/null +++ b/docs/fleet-manager/migrating-from-operator-ca-node.md @@ -0,0 +1,50 @@ +--- +title: "🔁 Migrating from operator_ca_node" +--- + +# 🔁 Migrating from operator_ca_node + +`operator_ca_node` named a CryptOS node as the operator CA: the manager had that node issue and revoke operator certificates. A CryptOS node can't be the operator CA any more. The manager now refuses to start when its config sets `operator_ca_node`, and the Helm chart refuses to render when `operatorCANode` is set. Both errors point here. + +This page moves a manager to an external operator CA. How the manager uses that CA is in [The operator CA and revocation](./operator-ca.md). + +:::caution[Before you start] +You need a machine to run the new operator CA on, ideally offline, with OpenSSL. Plan a short window: the certificates the node issued stop working when the manager restarts with the new settings, so every operator needs a certificate from the new CA before that. +::: + +## Steps + +1. **Create the operator CA.** Make an OpenSSL CA with the operator certificate profile, as in the manager's [standalone deployment guide, section 3](https://github.com/CryptOS-PKI/manager/blob/main/docs/deploying-standalone.md#3-the-operator-ca-is-an-external-ca). Give it `keyCertSign` and `cRLSign`, so the manager can check its CRL. + + :::danger[Keep the CA key offline] + Whoever holds the operator CA key can sign themselves an admin certificate for the Fleet Manager. Keep it on an offline or tightly controlled machine, never on the manager host. + ::: + +2. **Issue new operator certificates** from it, one per operator, with the level extension non-critical. Install your own in your browser. + +3. **Point the manager at it.** Set `operatorCAPath` to the new CA's certificate. If the CA publishes a CRL, add it: + + ```yaml + operatorCAPath: /etc/cryptos/fleet/operator-ca/operator-ca.pem + operatorCRL: + - url: http://pki.example.org/fleetos-operator.crl + ``` + + With the Helm chart, put the certificate in the ConfigMap named by `operatorCA.configMap`. + +4. **Remove `operator_ca_node`** from the config, or `operatorCANode` from your Helm values. + + :::warning[The old certificates stop working here] + After the next step, certificates the node issued are refused: the node's CA can't be an operator CA. Make sure your new certificate is installed first. + ::: + +5. **Restart the manager** (or `helm upgrade`) and reload the page. Choose the new certificate when the browser asks. + +6. **Remove the `operator-*` profiles** (`operator-viewer`, `operator-operator`, `operator-admin`) from the node that used to issue operator certificates. + +## What changes after the move + +- **Revoking an operator** in the Fleet Manager puts the certificate on the manager's own denylist. It no longer revokes at a node, and it can't revoke at your CA. Revoke there too and publish a new CRL. +- **Issuing an operator certificate** in the Fleet Manager is gone: your CA signs them. +- **Operator records** the manager kept from before stay listed, marked `legacy_node`. They can't log in. +- **MCP keys** need the operator CA to have a CRL that isn't out of date. See [MCP needs a current CRL](./operator-ca.md#mcp-needs-a-current-crl). diff --git a/docs/fleet-manager/operator-ca.md b/docs/fleet-manager/operator-ca.md new file mode 100644 index 0000000..d279fa5 --- /dev/null +++ b/docs/fleet-manager/operator-ca.md @@ -0,0 +1,119 @@ +--- +title: "🪪 The operator CA and revocation" +--- + +# 🪪 The operator CA and revocation + +Operators log in to the Fleet Manager with a client certificate. The CA that signs those certificates, the **operator CA**, is always **external**: an offline OpenSSL CA, or an enterprise or offline CA that can issue the operator profile. It is **never a CryptOS node**. The manager learns only the CA's certificate, the trust anchor. It never holds the CA's key and never signs an operator credential. + +This page covers where the manager gets the anchor, how revocation works, and the config keys. Moving a manager off `operator_ca_node` is on its own page: [Migrating from operator_ca_node](./migrating-from-operator-ca-node.md). + +## Where the anchor comes from + +The source depends only on what is configured: + +| Source | When | Trusted | Revocation | +|---|---|---|---| +| Config file | `operatorCAPath` is set | The certificates in that PEM file | The denylist (needs Postgres), plus the CRLs in `operatorCRL` | +| Registered | No `operatorCAPath`, `database_url` set, `firstRun` not `disabled` | The operator CAs stored in Postgres | The denylist, plus each CA's own CRL source | +| None | No `operatorCAPath`, and no `database_url` or `firstRun: disabled` | Nothing | Nothing. Every API call is refused. | + +The config file wins: while `operatorCAPath` is set, any operator CA stored in the database is ignored, and the manager logs that at start. + +:::caution[The operator CA can't be a node's CA] +The manager refuses to start when a certificate in `operatorCAPath` is a CryptOS node's CA, or has the same public key as one. Put only your external operator CA in that file, never a node's CA chain. +::: + +Every start logs each trusted operator CA's subject and SHA-256. Compare it with the fingerprint on the CA machine (the command is the same on every OS): + +```sh +openssl x509 -in operator-ca.crt -noout -fingerprint -sha256 +``` + +The manager checks the certificate again on every request, not only when the browser connects. A certificate whose CA is no longer trusted, or which is on the denylist, is refused on its next request, even on a connection that is already open. + +## Revocation + +A certificate is refused if any source lists it. Serial numbers are unique per CA only, so each source is keyed by the CA as well as the serial. + +- **The denylist** is the Fleet Manager's own list, kept in Postgres and always on. Revoking an operator in the manager adds to it. The manager instance that takes the request refuses the certificate at once, and the other instances within about 5 seconds. Without `database_url` there is no denylist, and revoking fails with error 1603 (`DATABASE_REQUIRED`). +- **The CRL** is your CA's own revocation list, optional for each CA. The manager downloads it (or reads the file), checks it against the CA, and refuses a CRL older than the one it holds. It refreshes the CRL every 15 minutes and at the CRL's next update time, and after a failure tries again after a minute, then less often, up to every 15 minutes. + +:::warning[The denylist doesn't revoke at your CA] +The denylist stops a certificate at the Fleet Manager only. Anything else that trusts the same CA still accepts it. Revoke the certificate at your CA as well, and publish a new CRL. +::: + +The CRL must be a complete CRL for end-entity certificates, signed by the operator CA itself, with a next update time. The manager refuses delta CRLs, indirect CRLs, CRLs limited to CA certificates or to some revocation reasons, and CRLs with an unknown critical extension. A certificate on hold counts as revoked while it is listed. + +A CA with no CRL source is checked against the denylist only: revocations made at the CA are not seen. + +### When the CRL is out of date + +`operatorRevocationPolicy` decides what happens when a CA's CRL is past its next update time, or a configured CRL has never been downloaded: + +| | `soft` (default) | `hard` | +|---|---|---| +| Web UI and API | Keep enforcing the last good CRL and the denylist. Log it every hour, and record `operator-crl-expired` in the audit log once. | Refuse certificates from that CA with error 1608 (`STALE_CRL`) until a current CRL is loaded. | +| MCP | Refused with 1608 (`STALE_CRL`) | Refused | + +:::warning[hard can lock everyone out] +With `hard`, a CRL address that stops answering locks every operator from that CA out of the web UI until the CRL is published again. Use `hard` only when the CRL is published automatically from a highly available location. +::: + +### MCP needs a current CRL + +MCP keys live a long time, so they get a stricter rule. When a key is made and on every call, the operator CA of the key's certificate must have a CRL source with a CRL that isn't out of date, and the manager instance must have read the denylist in the last 5 minutes. Otherwise the call is refused with error 1608 and the reason `NO_CRL`, `STALE_CRL` or `STALE_DENYLIST`. + +## Config keys + +```yaml +# Your external operator CA. Put only that CA's certificates in the file. +operatorCAPath: /etc/cryptos/fleet/operator-ca/operator-ca.pem + +# Optional: where the CRLs for those CAs come from. Each entry is a url +# (http or https) or a path. Each CRL is matched to the CA that signed it. +operatorCRL: + - url: http://pki.example.org/fleetos-operator.crl + # - path: /etc/cryptos/fleet/operator-crl/fleetos-operator.crl + +# Optional: soft (default) or hard. +operatorRevocationPolicy: soft + +# Optional: off, aia (default) or url. +operatorOCSP: + mode: aia + +# Optional: auto (default) or disabled. +firstRun: auto +``` + +| Key | Default | Rules | +|---|---|---| +| `operatorCAPath` | not set | A PEM file of operator CA certificates. | +| `operatorCRL` | none | Needs `operatorCAPath`. Each entry sets exactly one of `url` (http or https) and `path`. | +| `operatorRevocationPolicy` | `soft` | `soft` or `hard`. | +| `operatorOCSP.mode` | `aia` | `off`, `aia` or `url`. Needs `operatorCAPath`. | +| `operatorOCSP.url` | not set | Required with `mode: url`, http or https. Refused with any other mode. | +| `firstRun` | `auto` | `auto` or `disabled`. With `disabled` and no `operatorCAPath`, no operator CA is trusted. | + +:::info[OCSP isn't checked yet] +The manager checks `operatorOCSP` when it loads the config, but this version doesn't ask OCSP responders yet. Revocation comes from the denylist and the CRL. +::: + +:::caution[A misspelt key stops the start] +The manager refuses keys it doesn't know, and says which one. Before this change an unknown key was ignored. +::: + +`mcp.enabled` needs `database_url` and `authBypass: false`. It no longer needs `operator_ca_node` or `operatorCAPath`. + +## The self-signed certificate + +Without `tlsCert` and `tlsKey`, the manager serves a self-signed certificate. With `database_url` it keeps that certificate in Postgres, so a restart or a second instance serves the same one, and replaces it 7 days before it expires. It deletes it once `tlsCert` is set. Every start logs its fingerprint: + +``` +manager: WARNING serving a SELF-SIGNED bootstrap certificate, SHA-256 AB:CD:...:EF, valid until 2026-12-29; ... +``` + +:::danger[Check the fingerprint before you trust the page] +Compare the fingerprint in the log with the one your browser shows for the page. If they differ, something between you and the manager is presenting its own certificate. Don't continue. +::: diff --git a/docs/fleet-manager/overview.md b/docs/fleet-manager/overview.md index 38dfdc6..899fde7 100644 --- a/docs/fleet-manager/overview.md +++ b/docs/fleet-manager/overview.md @@ -46,7 +46,7 @@ What it never holds is a CA's private key. Those keys are made and kept on the n ## How operators log in -There are no usernames or passwords. You log in with an **operator certificate** installed in your browser. The manager checks it against the operator CA named by `operatorCAPath`, and reads your access level from an extension in the certificate (OID `1.3.6.1.4.1.59999.1.1`). +There are no usernames or passwords. You log in with an **operator certificate** installed in your browser. The manager checks it on every request against the operator CA, an external CA that is never a CryptOS node (see [The operator CA and revocation](./operator-ca.md)), and reads your access level from an extension in the certificate (OID `1.3.6.1.4.1.59999.1.1`). The web page itself loads without a certificate, so someone who can't get in sees why. Every API call needs one. @@ -56,7 +56,7 @@ There are three levels. Each includes the one before it. |---|---| | `viewer` | See nodes, certificates, profiles, protocols, enrollments and the audit log. | | `operator` | Issue and revoke certificates, re-key a subordinate CA, read a node's config, open enrollments and approve subordinate ones, and list operator certificates. | -| `admin` | Adopt and decommission nodes, approve a node link, edit and apply configs and profiles, turn protocol adapters on or off, switch a node's enrolment protocols, back up and restore CA keys, and issue or revoke operator certificates. | +| `admin` | Adopt and decommission nodes, approve a node link, edit and apply configs and profiles, turn protocol adapters on or off, switch a node's enrolment protocols, back up and restore CA keys, and revoke operator certificates on the manager's denylist. | The manager's [Operator PKI guide](https://github.com/CryptOS-PKI/manager/blob/main/docs/operator-pki.md) shows how to mint the first operator certificate. @@ -133,7 +133,7 @@ A `LINK` enrollment brings in a node that is already installed and running, whic 1. Someone with `operator` opens the request with the node's address, an admin certificate and key the node trusts, and the node's CA chain. 2. The manager sends the node a random challenge. The node signs it with its CA identity key, and the manager records that key's fingerprint. The request is now `PENDING`. 3. Someone with `admin` approves it and supplies the connection details again. The manager runs the challenge again and refuses the approval if the fingerprint has changed. -4. On approval the manager writes a management block into the node's config: the manager's name, the operator CA as a trusted client CA, and a flag that marks the node's own operator surface read-only. +4. On approval the manager writes a management block into the node's config: the manager's name and a flag that marks the node's own operator surface read-only. It doesn't send the operator CA, so operator certificates never log in to a node directly. ### Signing a subordinate diff --git a/docs/fleet-manager/web-ui.md b/docs/fleet-manager/web-ui.md index fad4f89..358b365 100644 --- a/docs/fleet-manager/web-ui.md +++ b/docs/fleet-manager/web-ui.md @@ -192,17 +192,17 @@ This page records intent only. ACME (RFC 8555) and EST (RFC 7030) are served by ## Operators -The operator certificates the manager has issued, with level, serial, expiry and status. It needs an operator CA node, named by `operator_ca_node` in the manager's config. Without one the page says `No operator-CA node is configured.` +The operator certificates the manager knows about, with level, serial, expiry and status. It needs an operator CA (see [The operator CA and revocation](./operator-ca.md)). Without one the page says no operator CA is configured. -:::caution[Without an operator CA node, revocation isn't enforced] -If `operator_ca_node` is not set, the manager can't revoke operator certificates, and a revoked one keeps working until it expires. Set it before you hand out certificates. -::: +**Revoke…** (`admin`) puts a certificate on the manager's denylist: the manager refuses it from its next request. It needs `database_url`. -**Issue operator…** (`admin`) makes the key in your browser, has the operator CA sign it, and downloads `{CN}-operator.p12`. Set the passphrase first and save it: it opens the file and can't be recovered. **Revoke…** (`admin`) revokes one; the manager then refuses that certificate. +:::warning[Revoking here doesn't revoke at your CA] +The denylist stops the certificate at the Fleet Manager only. Revoke it at your operator CA as well and publish a new CRL. +::: -{/* screenshot: fleet-manager/operators-not-configured.png: the Operators page with the No operator-CA node is configured message */} +The manager doesn't issue operator certificates: your operator CA signs them. Certificates the manager recorded before operator CAs became external are listed as `legacy_node` and can't log in. -A certificate made outside the manager, for example with OpenSSL against the operator CA, doesn't appear here and can't be revoked here. +{/* screenshot: fleet-manager/operators-not-configured.png: the Operators page with no operator CA configured */} ## Agent keys and MCP sign-in diff --git a/sidebars.ts b/sidebars.ts index 8af705d..cab9711 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -100,6 +100,8 @@ const sidebars: SidebarsConfig = { items: [ 'fleet-manager/overview', 'fleet-manager/node-trust', + 'fleet-manager/operator-ca', + 'fleet-manager/migrating-from-operator-ca-node', 'fleet-manager/helm', 'fleet-manager/web-ui', 'fleet-manager/approvals',