Policies & approvals
Two governance gates: a project-scoped certificate policy that constrains every issue / renew (key types, allowed issuers, SAN globs, naming regex, auto-renew requirement), and a request / approve / reject workflow that interposes a human review on sensitive operations.
01Certificate policy
One policy per project (singleton — you don't stack policies). Enforcement is synchronous: the issuance / renewal request fails with HTTP 422 and a structured violation list before any work is queued. The policy is cached for 60 seconds; saves take up to that long to propagate to all workers.
An empty policy (or no policy) means no enforcement.
02Rules
| Rule | Purpose | Empty value |
|---|---|---|
allowed_key_types | Whitelist of key types: ECDSA-P256, ECDSA-P384, RSA-2048, RSA-3072, RSA-4096. Up to 10 entries. | All allowed. |
allowed_issuer_types | Whitelist of issuer types (acme / msca). | All allowed. |
allowed_issuer_ids | Whitelist of specific ACME-account / MSCA-connection IDs (subject to the type list). | All allowed. |
san_rules.allow | Globs — a SAN matching any allow glob passes. | Allow-by-default. |
san_rules.deny | Globs — a SAN matching any deny glob is rejected. Evaluated before allow. | Deny none. |
san_rules.max_san_count | Cap on number of SANs per cert. | No cap. |
naming_pattern | Go regex; the certificate's human-assigned name must match. | No check. |
require_auto_renew | Rejects certs with auto_renew: false. | Manual renewals allowed. |
allow_key_export | Whether the Download flow may return the private key (PEM / PKCS#12 / zip). | Export allowed. |
03Evaluation order
- Resolve the policy for the project (cached, 60 s TTL).
- Key type — reject on mismatch.
- Issuer type, then issuer ID — reject on mismatch.
- SAN rules: max_san_count, then deny globs, then allow globs (if an allow list is set, a SAN not matching any allow is rejected).
- Naming regex — reject if the certificate name doesn't match.
require_auto_renew.- If any step produced a violation, return HTTP 422 with the full list. The UI surfaces violations inline on the issuance form, with the offending rule named.
04Example policy
allowed_key_types: ["ECDSA-P256", "ECDSA-P384"]
allowed_issuer_types: ["acme", "msca"]
san_rules:
allow: ["*.example.com", "*.internal.example.com"]
deny: ["*.admin.example.com"]
max_san_count: 10
naming_pattern: "^(prod|stage)-[a-z0-9-]+$"
require_auto_renew: true
allow_key_export: false
Reads: ECDSA only, any configured ACME or MSCA issuer, SANs must live under example.com or its internal subtree but never under admin.example.com, up to 10 SANs per cert, the human-readable name must start with prod- or stage-, auto-renew must be on, private-key download is forbidden.
05Configure & live test
- Settings → Certificate Policy (project admin role required).
- Toggle Enable policy.
- Fill the rules. The form has live-test inputs — type a candidate cert name to verify the naming regex and a candidate domain to verify the SAN allow / deny globs before you save. Regex compile errors surface next to the input.
- Save. Cache TTL means changes take up to 60 s to propagate.
Bypass: there is no in-UI bypass. If a policy blocks a legitimate request at 2 a.m., either temporarily relax the specific rule (admin-only, audit-logged), disable the policy + issue + re-enable, or use a different project. Every disable / relax is audit-logged.
06Approval workflow
An optional gate that interposes a human review on top of RBAC. RBAC controls who can submit; the approval policy controls whether they execute directly or queue a request that must be approved by a reviewer with the right role.
Per-project settings enable or disable each gate independently. The default deployment leaves all gates off.
Actions that can be gated:
- Issue — a new cert request.
- Renew — manual renewal outside the scheduler.
- Reissue — key rotation.
- Revoke — revocation at the CA.
- Download (key export) — pulling the private key.
- Distribution execute — running a distribution (optional).
07Request states
| State | Meaning |
|---|---|
pending | Awaiting review. |
approved | Reviewer accepted; the queued action runs immediately. |
executed | Action ran to completion (success / failure captured separately on the resulting job). |
rejected | Reviewer declined (comment required). |
expired | Not reviewed within the TTL (default 7 days). |
cancelled | Requester cancelled before review. |
08Submit a request
- Attempt the gated action on the UI (e.g. click Revoke on a cert).
- If the gate is on, the dialog shifts from Confirm to Request approval. Fill the reason (required) and acknowledge any preflight warnings.
- Submit. The action is queued; the cert detail surface shows the pending request.
Preflight warnings surface conditions like "this cert has 45 days left — revoking it breaks three live distributions" or "this cert is in a policy-required auto-renew state — revoking breaks policy". Acknowledging isn't optional; approvers see the acknowledgement in the audit trail.
09Review a request
- Reviewer opens My Requests → Reviews — every pending request they are empowered to act on.
- Click a row; the full context appears (requester, reason, preflight warnings, target cert, action).
- Approve with an optional comment, or Reject with a required comment.
- On approve, the queued action runs immediately. The requester is notified via their configured channels.
10Who can approve what
- Reviewers must have a role at least one tier above the requester on the same scope (org or project). An operator request needs an admin+ reviewer; an admin request needs an owner.
- Self-approval is blocked unconditionally — the approver cannot be the requester even if their role qualifies.
- If no eligible reviewer exists (single-admin org), the gate auto-rejects with an explanatory message so you don't deadlock.
11TTL & expiry
Requests time out after a configurable TTL (default 7 days, set under Settings → General → Approval TTL). An expired request terminates in expired; the action does NOT execute. The requester can re-submit. TTL changes are audit-logged.
12Notifications
approval.requested— fires when a new request enterspending. Route this to a reviewer channel (shared inbox, Slack group).approval.approved/approval.rejected— fires on state transition. Route back to the requester.approval.expired— catches stale requests; useful for metrics.
Configure under Notifications with the same channel + rule pattern as cert events.
13Troubleshooting
"Policy violation" but no rule seems to apply
Check the cached age — a recent save may not have propagated yet (60 s TTL). The 422 response names the exact rule.
Naming regex silently rejects everything
Usually a Go-regex-syntax issue (PCRE features like lookaheads aren't supported). The live-test panel surfaces compile errors inline.
"No eligible reviewer" on submit
The org has no user with a role one tier above you on this scope. Either lower the request, add a reviewer, or temporarily disable that approval gate.
Request stuck in approved, action didn't fire
The executing worker is down or the action failed with a system error. Check Jobs — the approval-triggered job carries the approval ID in its payload.