PKI Governance
Require M-of-N approval for sensitive PKI operations, and restrict who may issue or revoke under a CA, from where and when.
Overview
PKI governance reuses two Cockpit modules and binds them to PKI resources:
- Quorum policies (Administration > Quorum policies): M-of-N approval requests, the same flow that gates key and vault deletion.
- Access policies: rules on user, IP address, country and time, the same evaluator used by other DuoKey services.
Governance bindings
A binding attaches policies to one PKI resource. It is managed from the Governance card on the CA detail page, the certificate template page and the CA crypto token page.
| Resource | Quorum policy | Access policy | High-value issuance rule |
|---|---|---|---|
| X.509 CA | Yes | Yes | Yes |
| Certificate template (profile) | Yes | No (bind it to the CA) | Yes |
| CA crypto token (signing keys of external CA software) | Yes | No | No |
When a binding is saved, the quorum policy must exist in the tenant and enforce dual control (a threshold of 2 or more, or the requester barred from approving), and the access policy must exist in the tenant.
While a quorum policy is bound, changing or removing the binding needs that policy's approval. One administrator cannot remove the quorum and then act alone. If the bound policy has been deleted or disabled, the binding can be repaired without approval.
M-of-N quorum
Gated operations
| Operation | What it gates |
|---|---|
revoke | CA revocation (single and cascade, console, API and MCP); CA crypto token signing-key revocation |
delete | CA archive; certificate template deletion |
update | CA changes (name, CRL distribution URL, OCSP URL); certificate template changes; the governance binding itself (always) |
issue | High-value issuance (see below) |
rekey | CA crypto token signing-key rotation |
The quorum policy's own list of gated operations narrows this set; an empty list gates every operation of the resource. The REST API and API keys reach the same handlers as the console and are gated the same way.
Approval flow
Request
The operator calls the operation. If it is gated and not yet approved, Cockpit answers HTTP 202 with quorum_required: true, status: "pending_approval" and a request_id, and does nothing else. MCP tools return the same answer.
Vote
Approvers vote under Administration > Quorum requests (permission Quorum.Approve). The request shows exactly what is being approved: CA, subject names, validity, reason, template change.
Execute
Once the threshold is reached, the same operator repeats the same call. The approval is consumed and the operation runs.
Enforced in every case:
- An approval is bound to the exact request: a different subject, reason or template body opens a new approval request.
- Only the operator who opened the request can use its approval, and only once (a replay or concurrent call gets HTTP 409).
- The policy must be dual control when it is bound and when it is used.
- A binding to a quorum policy that no longer exists refuses the operation (fail closed). A disabled policy is not enforced.
Requests, votes, approvals used and binding changes are recorded in the activity and audit logs.
High-value issuance
A high-value rule on the CA (and optionally on a template) marks some issuances as needing quorum approval. Any matching clause makes the issuance high value:
| Clause | Matches |
|---|---|
| CA certificates | A CA certificate: subordinate CA creation under this CA, or a request for a CA certificate |
| Wildcard | A wildcard DNS name (*.example.com) in the CN or a SAN. When the names cannot be read (for example inside a SCEP envelope), the clause matches: fail closed |
| Validity over N days | A validity strictly above N days |
| Always (templates) | Every issuance with the template |
When a template's rule matches and the template has its own quorum policy, that policy governs; otherwise the CA's policy does.
| Issuance path | High-value issuance |
|---|---|
| Console and REST API (issue under a CA, intermediate CA, internal-CA issuer issue and renew, timestamping unit certificate) | 202, approval flow |
| MCP tools (issue, sign CSR, renew, create issuing CA, issue via issuer, deploy PQC certificate) | Pending answer, approval flow |
| ACME, EST, SCEP, CMP, cert-manager, Windows autoenrollment | Refused: these protocols cannot carry an approval, so issue high-value certificates interactively |
| Automatic renewal, Managed S/MIME issuance | Refused |
Access policies on CAs
An access policy bound to a CA is evaluated on:
- every issuance under the CA, on every path above, except the platform's own jobs (automatic renewal and S/MIME automation; an operator-triggered S/MIME issuance is evaluated);
- every revocation under the CA: console and API, internal-CA issuer, S/MIME, MCP, ACME
revoke-certand CMPrr; - CA revocation (single and cascade).
Attributes per channel
| Channel | Source IP | Country | Time | User |
|---|---|---|---|---|
| Console, REST API, API key | Yes | Yes | Yes | The user's e-mail |
| MCP | No | No | Yes | The MCP key owner's e-mail |
| ACME | Yes | Yes | Yes | acme-account:<id> |
| Windows autoenrollment | Yes | Yes | Yes | The authenticated principal, else ms-enroll-endpoint:<id> |
| EST | Yes | Yes | Yes | The HTTP Basic user or client-certificate identity |
| SCEP | Yes | Yes | Yes | scep-profile:<id> |
| CMP | Yes | Yes | Yes | cmp-alias:<name> |
| cert-manager | Yes | Yes | Yes | cert-manager-issuer:<id> |
Forwarded client addresses are only trusted from configured trusted proxies; otherwise the TCP peer is used. Country rules need a trusted Cloudflare edge or a local GeoIP database; without either, they cannot match.
Group rules cannot match on PKI paths: no PKI channel carries identity-provider group membership. When all rule categories must match, a policy with group rules therefore denies every PKI request.
Fail-closed behaviour
- A rule whose attribute is missing cannot match; with no matching allow rule the request is denied.
- A binding to an access policy that no longer exists denies.
- An evaluator or database error denies.
- A disabled policy is not evaluated.
Every decision is recorded in the access-policy audit trail with the channel, caller IP and country, and CA.
API
GET /api/pki/governance/<resource_type>/<id> (Operations.Pki.Governance.Read)
PUT /api/pki/governance/<resource_type>/<id> (Operations.Pki.Governance.Manage)
{
"quorum_policy_id": "uuid or null",
"access_policy_id": "uuid or null",
"high_value": { "ca_certificates": true, "wildcard": true, "validity_days_over": 397, "always": false },
"reason": "optional, shown to approvers"
}resource_type is ca, template or ca_crypto_token. An access policy can only be bound to a CA, a high-value rule only to a CA or a template, and validity_days_over must be between 1 and 36,500.
Not covered
- There is no in-place re-key of an X.509 CA (a CA's key is fixed at creation); re-key is gated where it exists, on CA crypto token signing keys.
- CA suspend, reactivate and unarchive are not gated (they are reversible).
- Generating a CRL with an explicit list of serial numbers is not gated by quorum or access policies.
- Merkle Tree CAs, SSH CAs and external issuers (public CAs reached through connectors) are not covered by these bindings.
- Editing or disabling a quorum policy or an access policy is a single-administrator action in its own module, as for keys and vaults.
- For CMP requests in CRMF format, only the subject CN is read for the wildcard rule; SANs in the CRMF template are not.