Skip to content

feat(operators): credential requests, complete, deny and a client-side request page - #145

Merged
Bugs5382 merged 1 commit into
mainfrom
feat/144-operator-credential-requests
Oct 1, 2026
Merged

Bugs5382 merged 1 commit into
mainfrom
feat/144-operator-credential-requests

Conversation

@Bugs5382

@Bugs5382 Bugs5382 commented Oct 1, 2026

Copy link
Copy Markdown
Contributor

What and why

The manager no longer signs operator credentials: the operator CA is external, and the api contract (CryptOS-PKI/api#132) replaced IssueOperatorCredential with credential requests and a denylist revoke. The Operators page still called the removed RPC, so it could not issue anything against a current manager. This brings it onto the request, sign out of band, record model.

Refs #144

  • Request credential (admin): holder name, email and level, then either a P-384 key and CSR made in the browser (CN=email, with the level profile as an extensionRequest) or a CSR the holder made. Browser keys go through a mandatory key backup step: a 24-character passphrase from a 64-symbol alphabet is shown once with a copy button, and Continue stays disabled until the encrypted PKCS#8 backup (fleetos-<level>-<email>.key.pem) is downloaded. Only then is the request filed and the CSR shown, with the extension section and the openssl ca command the manager returns. An uploaded CSR whose CN isn't the holder's email is refused in the browser.
  • Pending requests tab with Cancel, and Complete…: paste the signed certificate, pick the key source (the encrypted backup this tab still holds, a backup file, or the holder keeps the key), optionally add the operator CA as the chain. The browser opens the backup and checks the certificate carries that key before recording, then builds the PKCS#12 sealed with the backup's own passphrase and a FleetOS <level> (<email>) friendly name. Record certificate… imports one made entirely at the CA.
  • Deny at the Fleet Manager replaces revoke: RFC 5280 reasons 0-10 except 7, a note, and the reminder (before, and after with the manager's warnings) to revoke at the CA too.
  • Columns: kind, issuer (fingerprint prefix), denylisted, CRL-revoked and last seen. Legacy and already denylisted rows have no Deny. The 1400 view now says no operator CA is configured (operatorCAPath or first run) instead of naming operator_ca_node.
  • Make a credential request at /request-credential, outside the login gate and linked from it: makes the key, key backup and CSR, and later builds the PKCS#12 from the backup and the signed certificate. It makes no network calls and doesn't import the Fleet Manager client.
  • Reason codes: errorReason() reads x-cryptos-error-reason, and src/lib/fleet/error-copy.ts maps every 1600-1611 code and sub-reason (including STALE_OCSP, NO_CRL, REVOKED_OCSP, OCSP_UNKNOWN, OCSP_UNREACHABLE, OCSP_INVALID) to a message. The new screens show those messages with the code to quote.
  • assemblePkcs12 takes an optional friendly name (PKCS#9 bag attribute on the key and leaf bags).
  • Removed: operator-issue-dialog.tsx (and its test) and operator-revoke-dialog.tsx.
  • Stubs: src/gen/fleet/ is refreshed to api main (29b56ec), which adds bootstrap_pb.ts, errors_pb.ts and operator_ca_pb.ts. wire-fields.test.ts pins the new credential, request and operator CA fields.

Tests removed or changed (the behaviour they covered is gone or intentionally changed):

  • src/components/operator-issue-dialog.test.tsx, all four tests: the issue dialog is removed.
  • src/lib/operators.test.ts: the issueOperatorCredential tests (mock issue, unknown level, empty CSR, live issue) and the revokeOperatorCredential tests (mock revoke, live revoke error). Replaced by tests for create, list, cancel, record and deny; the level and empty-CSR checks moved to request creation.
  • src/pages/operators.test.tsx: the Issue action became Request credential, the empty state no longer says the manager only lists what it issued, and the 1400 view no longer names operator_ca_node.

Merge order: with or after CryptOS-PKI/cryptos-manager#152 (credential requests, record, denylist revoke); against a manager without it the new RPCs are refused. Either order with #143: both refresh src/gen/fleet/. Whichever merges second keeps this PR's copy, generated from api main, which is a superset of #143's (it already carries ConfirmAdoptionFingerprint and the new AdoptNodeResponse fields), so both keep compiling. AGENTS.md and wire-fields.test.ts may need a trivial rebase too.

Docs: companion CryptOS-PKI/docs#74 (two new task pages and the web UI page). README and AGENTS.md are updated here. Next PRs for #144: the Operator CAs page and banners, then the first-run wizard.

Verification

  • Lint clean
  • Tests pass
  • Build succeeds
  • Documentation updated (if behavior or API changed)

How this was verified

  • Tests written first, failing before the change: src/lib/fleet/error-copy.test.ts (every code and each code/sub-reason pair has its own message), src/lib/crypto/key-backup.test.ts (64-symbol 24-character passphrase, PEM and DER round trip, wrong passphrase, key/certificate match, PKCS#12 opens with the backup's passphrase), src/lib/crypto/pkcs12.test.ts (friendly name), src/lib/operators.test.ts, src/components/key-backup-step.test.tsx (no Continue before the download, passphrase gone on remount, nothing in storage), src/components/credential-request-wizard.test.tsx, src/components/credential-complete-dialog.test.tsx, src/components/operator-deny-dialog.test.tsx, src/pages/operators.test.tsx, src/pages/credential-request.test.tsx, plus the App and auth gate route tests.
  • Key never in a request body: in live mode the wizard and Complete tests stub fetch, capture every Connect request body, and assert that neither the PKCS#8 key (base64, base64url, a slice of it), the JWK d, the passphrase nor the backup appears.
  • No network on the anonymous page: fetch, XMLHttpRequest.open, sendBeacon and the Fleet Manager client are spied and never called across the whole flow.
  • The key backup and the PKCS#12 the browser builds were opened with OpenSSL 3 (openssl pkey, openssl pkcs12 -info), and the CSR shows the requested extensions with the level extension non-critical.
  • npm run lint, npm test (85 files, 488 tests), npm run build and task license pass locally.

…e request page

The manager no longer signs operator credentials (IssueOperatorCredential
is gone), so the Operators page could not issue anything. It now follows
the external operator CA model:

- Request credential: a P-384 key and CSR made in the browser with a
  mandatory encrypted key backup and a passphrase shown once, or a CSR
  the holder made. Shows the CSR, the extension section and the
  openssl ca command the manager returns.
- Pending requests tab with cancel, and Complete: record the signed
  certificate and, when the browser can open the key backup, build the
  PKCS#12 with the same passphrase.
- Deny at the Fleet Manager replaces revoke: RFC 5280 reasons 0-10
  except 7, a note, and the reminder to revoke at the CA as well.
- Kind, issuer, denylisted, CRL-revoked and last-seen columns.
- An anonymous /request-credential page that makes the key, backup and
  CSR, and later the PKCS#12, with no network calls.
- Messages for every 1600-1611 code and sub-reason.

The TypeScript stubs are refreshed to api main.

Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
@Bugs5382 Bugs5382 self-assigned this Oct 1, 2026
@Bugs5382
Bugs5382 marked this pull request as ready for review October 1, 2026 00:07
@github-actions github-actions Bot added the enhancement New feature (feat). Minor version bump. label Oct 1, 2026
@Bugs5382

Bugs5382 commented Oct 1, 2026

Copy link
Copy Markdown
Contributor Author

Merging for the v0.1.0 test run: CI green and mergeable at merge time.

@Bugs5382
Bugs5382 merged commit e8099d6 into main Oct 1, 2026
16 checks passed
@Bugs5382
Bugs5382 deleted the feat/144-operator-credential-requests branch October 1, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature (feat). Minor version bump.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant