Repository navigation
feat(operators): credential requests, recording and denylist revoke - #152
Merged
Merged
Conversation
Credential requests, recording against a request and observed credentials get their own store interface. Postgres keeps them in the v7 tables: recording and completing a request happen in one transaction, a pending request past its expiry reads as expired with its CSR dropped, and a row only observed in use is upgraded when the certificate is recorded. The in-memory store refuses them with ErrDatabaseRequired. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
An observed-credential recorder wraps the peer authorizer and queues one observed row per certificate it accepts. An LRU keeps a certificate from being written more than hourly, and a bounded queue drops a sighting when full so a request never waits; a dropped or failed sighting is retried on the certificate's next request. Also adds the op_<level> OpenSSL section and signing command built from the level encoding the manager reads, and the full-name check. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
CreateOperatorCredentialRequest checks the CSR, stores it pending for 30 days and returns the CSR, the level's extension section and the openssl command. RecordOperatorCredential checks a signed certificate against the request (level, CN and key) or imports one made out of band; it must come from the active operator CA and not be recorded already. Listing and cancelling requests are served too. RevokeOperatorCredential now refuses a serial that isn't hex, a reason that isn't an RFC 5280 CRL reason and an issuer that isn't a SHA-256 fingerprint, and ListOperatorCredentials returns first and last seen. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
With Postgres configured, the API's peer authorizer records every operator certificate it accepts through the observed-credential recorder. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
A new page covers requesting a credential, signing it at the operator CA, recording it, credentials seen in use, and denying one, with the warning that a Fleet Manager denial doesn't revoke at the CA. operator-pki.md, which described the node-hosted operator CA, is removed; its level extension table moves to the new page. Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
2 tasks done
Bugs5382
marked this pull request as ready for review
September 30, 2026 23:35
4 tasks done
Signed-off-by: Bugs5382 <12115015+Bugs5382@users.noreply.github.com>
Contributor
Author
|
Merging for the v0.1.0 test run: CI green and mergeable at merge time. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What and why
The manager never signs an operator credential, so the old issue path is replaced by request, sign at the external CA, record, and deny. This serves the RPCs the contract already had as stubs.
Closes #147
CreateOperatorCredentialRequest(admin) checks the CSR withCheckCSR(subject exactlyCN=<email>, P-384 or RSA 3072+, the request's email), stores itpendingfor 30 days, and returns the CSR PEM, theop_<level>OpenSSL section built fromauthz.MarshalLevelExtension, and theopenssl cacommand.ListOperatorCredentialRequests(operator, optional state filter) andCancelOperatorCredentialRequest(admin) are served. A pending request past its expiry reads asexpired; the CSR is dropped on completion, cancellation or expiry.RecordOperatorCredential(admin) runsCheckOperatorCert. Withrequest_idthe level, CN and key must match the pending request, which is completed in the same transaction as the insert (kindrequested); without it an out-of-band certificate is imported (kindrecorded). It must be signed by the active operator CA (NOT_ACTIVE_ANCHORfor the retiring one) and not already recorded (DUPLICATE). A row only observed in use is upgraded in place.RevokeOperatorCredentialalready wrote the denylist with issuer and note. It now refuses a serial that isn't hex, a reason that isn't an RFC 5280 CRL reason (0-10 except 7) and an issuer that isn't a SHA-256 fingerprint, instead of writing them. The audit summary says the credential is not revoked at the CA.ListOperatorCredentialsalso returnsfirst_seen_atandlast_seen_at.observedrow. An LRU keeps a certificate from being written more than hourly, and a bounded queue drops a sighting when full without blocking the request; a dropped or failed sighting is retried on the next request.operator-credential-requested,operator-credential-request-cancelled,operator-credential-recorded,operator-revoked). The new RPCs stay excluded from MCP, as the catalog already marked them.DATABASE_REQUIRED.Docs:
docs/operator-credentials.mdis new;docs/operator-pki.md(the node-hosted operator CA) is removed and its level extension table moved to the new page; links in the README,operator-ca.mdanddeploying-standalone.mdare updated. Docs site companion: CryptOS-PKI/docs#73.No new dependencies.
Merge order: independent of #150, #151 and #149 (trial merges with each are clean). Merge together with the docs companion.
Verification
How this was verified
task ci(gofmt, vet, golangci-lint,go test ./...with a fresh test cache) passes withMANAGER_TEST_DATABASE_URLpointing at a throwaway Postgres 18, so the store integration tests ran.task licensepasses. 36 new tests, each seen failing first: the Postgres store suite, the handler tests (admin gating, CSR and name refusals, request matching, 1611 states, active-anchor and duplicate refusals, the next request refused after a denial), the recorder tests (one write per certificate, hourly refresh, non-blocking overflow, retry after a failed write) and the wiring tests.An OpenSSL 3 run of the documented flow (a P-384 CSR on the holder side,
openssl cawith the returnedop_operatorsection,openssl pkcs12 -export,openssl ca -revokeand-gencrl) produced a certificate with the level extension non-critical, andCheckCSRplusCheckOperatorCertwith the CSR accepted it at leveloperator.