Certificate Management¶
PKI Operations — Request, Revoke, Import, Export, and CRL Management
Document Version: 1.3.0
Last Updated: 2026-06-21
1. Certificate Lifecycle¶
graph LR
A["📝 Request<br/>(CSR)"] --> B["⏳ Approval"]
B --> C["✅ Issuance"]
C --> D["🔒 Active"]
D --> E["🔄 Renewal"]
E --> C
D --> F["❌ Revocation"]
F --> G["📜 CRL Update"]
D --> H["⏰ Expiration"]
classDef request fill:#EBF5FB,stroke:#2196F3,stroke-width:2px,color:#1565C0
classDef active fill:#E8F5E9,stroke:#4CAF50,stroke-width:2px,color:#2E7D32
classDef warning fill:#FFF8E1,stroke:#FF9800,stroke-width:2px,color:#E65100
classDef danger fill:#FFEBEE,stroke:#F44336,stroke-width:2px,color:#C62828
class A,B request
class C,D active
class E,H warning
class F,G danger
2. Requesting a Certificate¶
Via Web Interface¶
- Navigate to Certificates → Request Certificate
- Choose a Certificate Template.
| Template | Key Usage | Extended Key Usage | Typical Validity |
|---|---|---|---|
| Web Server | Digital Signature, Key Encipherment | Server Authentication | 1 year |
| Client Auth | Digital Signature | Client Authentication | 1 year |
| Code Signing | Digital Signature | Code Signing | 2 years |
| Email (S/MIME) | Digital Signature, Key Encipherment | Email Protection | 1 year |
- Select a request source:
- Upload CSR when your tooling already generated the private key and CSR.
- Generate in MazeVault when MazeVault should create the private key and CSR.
- Generate in Azure Key Vault when the key must remain inside Azure Key Vault and only the CSR is exposed for signing.
- Fill in the request details:
- Common Name (CN): Primary domain or identifier.
- Template subject fields: Review the subject attributes inherited from the template.
- Additional SANs: Add allowed DNS, IP, email, or URI SAN entries as permitted by the template.
- Key custody: For MazeVault-generated requests, keep the key in MazeVault or store it through a configured secret manager integration. For Azure Key Vault generation, select the Azure Key Vault integration that will own the key.
- Submit the request. Depending on approval policy, the request is either issued immediately or waits for approval.
Via API¶
curl -X POST https://vault.example.com/api/v1/certificates/csr \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"template_id": "550e8400-e29b-41d4-a716-446655440010",
"project_id": "550e8400-e29b-41d4-a716-446655440001",
"requested_cn": "api.example.com",
"csr_source_mode": "mazevault_generated",
"requested_san": {
"dns_names": ["api.example.com", "api-internal.example.com"]
},
"requested_subject": {
"common_name": "api.example.com",
"organization": "Example Corp"
},
"requested_key_algorithm": "RSA",
"requested_key_size": 2048,
"key_storage_mode": "local"
}'
For Azure Key Vault-native generation, set csr_source_mode to azure_key_vault_generated and include key_integration_id for the Azure Key Vault integration that should own the key material.
3. Certificate Approval¶
Depending on your organization's policy:
| Policy | Behavior |
|---|---|
| Auto-approve | Certificate issued immediately after request |
| Admin approval | Certificate enters pending_approval state; an administrator must approve |
| Manager approval | Project manager can approve certificates for their project |
Approving a Request¶
- Navigate to Certificates → Pending Requests
- Review the certificate details
- Click Approve or Reject
4. Revoking a Certificate¶
When to Revoke¶
- Private key has been compromised
- Certificate holder's affiliation has changed
- Certificate has been superseded by a new one
- Service using the certificate has been decommissioned
Revocation Procedure¶
- Navigate to Certificates → Active and find the certificate
- Click Revoke
- Select a Revocation Reason (RFC 5280):
keyCompromise— Private key is compromisedaffiliationChanged— Subject's affiliation changedsuperseded— Replaced by a new certificatecessationOfOperation— Service decommissionedcertificateHold— Temporary hold (can be unreleased)- Add an optional comment
- Click Confirm Revocation
The CRL is automatically regenerated after revocation.
Unrevoking a Certificate¶
Certificates revoked with reason certificateHold can be unreleased:
- Navigate to Certificates → Revoked and find the certificate
- Click Unrevoke
- Confirm the action
5. Importing Certificates¶
Single Certificate Import¶
- Navigate to Certificates → Import
- Upload or paste:
- Certificate (PEM format, required)
- Private Key (PEM format, optional)
- CA Chain (PEM format, optional)
- Select the target project
- Click Import
Bulk Import (PEM Bundle)¶
- Navigate to Certificates → Import → Bulk Import
- Upload a PEM bundle containing multiple certificates
- Select the target project
- Click Import
The system parses the bundle, identifies individual certificates, and imports them with automatic chain detection.
Renewal Preparation After Import¶
- If an imported certificate is linked to a managed CA account and meets rotation prerequisites, MazeVault can prepare renewal metadata automatically.
- Automatically prepared rotation configurations stay disabled until an operator explicitly enables them. This prevents silent private-key rotation immediately after import.
- The initial renewal lead time is resolved from the effective certificate, template, and CA-account policy hierarchy, so imported certificates inherit the intended renewal window instead of a hidden default.
Per-Certificate Renewal Key Policy¶
For renewable certificates, you can override key handling on a per-certificate basis:
Regenerateissues a new private key during renewal.Reusekeeps the current key material when the provider, custody mode, and downstream deployment path support reuse.- Leaving the value unset defers to the template or higher-level policy.
Use Reuse only when downstream systems require key continuity or re-enrollment overhead is unacceptable.
Readiness Review Before Enabling Automation¶
- Review the Project Rotations view before enabling certificate automation.
- Renewal and deployment resources now expose inline readiness and preflight state.
Manual Review Requiredmeans MazeVault cannot validate a target automatically and an operator should confirm the deployment path before production rollout.
6. Exporting Certificates¶
Export Formats¶
| Format | Extension | Includes Key | Use Case |
|---|---|---|---|
| PEM | .pem, .crt |
Optional | Linux/Unix servers, Apache, Nginx |
| DER | .der, .cer |
No | Java applications, Windows |
| PKCS#12 | .p12, .pfx |
Yes (password-protected) | Windows, Java KeyStore import |
Export Procedure¶
- Navigate to the certificate detail page
- Click Export
- Select format and options:
- Include CA chain
- Include private key (requires
certificates.export_keypermission) - Set password (for PKCS#12)
- Download the file
Private Key Export
Exporting a certificate with its private key is a security-sensitive operation recorded in the audit log. Ensure the private key is transferred securely.
7. ACME Certificate Automation¶
MazeVault includes a built-in ACME server (RFC 8555) that enables automated certificate issuance directly from Kubernetes using cert-manager.
Quick Overview¶
- Generate EAB credentials in MazeVault (Organization Settings → ACME Access)
- Create a Kubernetes Secret with the HMAC key
- Deploy a ClusterIssuer pointing to your MazeVault ACME directory
- Annotate Ingresses or create
Certificateresources — cert-manager handles the rest
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: mazevault-issuer
spec:
acme:
server: https://vault.example.com/api/acme/directory
externalAccountBinding:
keyID: "YOUR_EAB_KEY_ID"
keySecretRef:
name: mazevault-eab-secret
key: secret
privateKeySecretRef:
name: mazevault-acme-account-key
solvers:
- http01:
ingress:
ingressClassName: nginx
Internal domains (.local, .internal, .lan, .corp) are auto-approved without HTTP-01 challenge.
Full ACME Guide
For step-by-step instructions, YAML examples, ACME profiles, and troubleshooting, see the dedicated ACME Certificate Automation Guide.
8. CRL Management¶
Certificate Revocation List (CRL)¶
MazeVault generates CRLs automatically when certificates are revoked. CRLs are available at:
| Format | URL |
|---|---|
| DER | https://vault.example.com/api/v1/crl |
| PEM | https://vault.example.com/api/v1/crl/pem |
CRL Distribution Point¶
Configure the CRL Distribution Point in your certificates to point to your MazeVault CRL URL. This is automatically included in certificates issued by MazeVault CAs.
Force CRL Regeneration¶
If needed, regenerate the CRL manually:
- Navigate to Certificates → CRL
- Click Regenerate CRL
- Select type: Full or Delta
8. OCSP Validation¶
OCSP provides real-time certificate status checking as an alternative to CRL:
| Feature | CRL | OCSP |
|---|---|---|
| Real-time | ❌ (periodic generation) | ✅ |
| Bandwidth | Higher (full list) | Lower (per-certificate) |
| Privacy | Client downloads all | Client queries specific cert |
| Offline support | ✅ (cached CRL) | ❌ (requires connectivity) |
OCSP URL¶
Testing OCSP¶
Related¶
- Certificates API — API reference
- Secrets Management — Managing secrets
- TLS Configuration — TLS setup
Certificate Issuance Pipeline — Architecture¶
The following diagram shows the complete end-to-end flow from a browser request to a signed certificate, covering all supported CA types.
flowchart TD
subgraph Frontend["🖥️ Frontend (React)"]
FE_MODAL["RequestCertificateModal\n(template select, CN, SANs,\nkey algorithm, subject DN)"]
FE_POLL["Status Polling\nafter submission"]
end
subgraph API["🔌 API Layer (Go/Gin)"]
API_SUBMIT["POST /projects/:id/csr\nSubmitProjectCSR\n+ OrganizationID from JWT"]
API_ISSUE["IssueCertificate"]
end
subgraph Template["📋 Template Engine"]
TPL_RESOLVE["ResolveTemplateForProject\n(inheritance merge)"]
TPL_MERGE["mergeTemplates\nValidityDays + ValidDays sync\n(BUG-01 fixed)"]
TPL_OVERRIDE["OverrideValidityDays\nfrom project_template_assignments\n(BUG-05 fixed)"]
end
subgraph CA_Resolution["🔀 CA Resolution"]
CA_DUAL["Dual-probe:\n1. org CA accounts\n2. certificate_authorities\n(BUG FP-1 fixed)"]
end
subgraph CA_Providers["🏛️ CA Providers"]
CA_INT["Internal CA\n(crypto/x509)\nvalidDays = ValidityDays || ValidDays || 365"]
CA_SMALL["Smallstep (step-ca REST)\nnotAfter from ValidityDays\n(1-day cert bug fixed)"]
CA_DIGI["DigiCert CertCentral v2\nValidityYears from ValidityDays"]
CA_SSL["SSLMarket.cz REST\nvalidityMonths from ValidityDays"]
CA_VENAFI["Venafi (vcert v5)"]
CA_ACME_OUT["ACME Client (outbound)\nDNS-01 + HTTP-01"]
CA_ADCS["ADCS (Web/WCCE/DCOM)\nStatus=5 → ErrADCSPendingApproval\n(type-safe check)"]
CA_VAULT["HashiCorp Vault"]
end
subgraph ACME_Server["🔐 ACME Server (MazeVault as CA)"]
ACME_NEW["NewOrder\nhttp-01 + dns-01\ntls-alpn-01 → explicit 400"]
ACME_VALIDATE["ValidateChallenge\nHTTP probe / DNS TXT lookup"]
ACME_FINALIZE["FinalizeOrder\nSignCSR → CA provider"]
ACME_ADCS_POLL["pollPendingADCSOrders\n(5 min ticker)\nadcs_request_id → resume"]
end
subgraph Pending["⏳ ADCS Pending Approval"]
ADCS_FIELD["ACMEServerOrder.adcs_request_id\n(migration 000165)"]
ADCS_POLLER["ADCSApprovalPoller\nRenewalQueue path"]
end
subgraph Key_Offload["🔑 Key Offload (Orchestrator Mode)"]
KO_CTX["WithKeyOffloadOrgID(ctx)\ntenant scope (BUG-07a fixed)"]
KO_AZURE["Azure Key Vault\nbase64 decode on read\n(BUG-07b fixed)"]
KO_AWS["AWS Secrets Manager"]
KO_HC["HashiCorp Vault"]
end
FE_MODAL --> API_SUBMIT
API_SUBMIT --> TPL_RESOLVE
TPL_RESOLVE --> TPL_MERGE
TPL_MERGE --> TPL_OVERRIDE
TPL_OVERRIDE --> CA_DUAL
CA_DUAL --> CA_INT & CA_SMALL & CA_DIGI & CA_SSL & CA_VENAFI & CA_ACME_OUT & CA_ADCS & CA_VAULT
CA_INT & CA_SMALL & CA_DIGI & CA_SSL --> API_ISSUE
CA_ADCS -->|"Status=5 pending"| ADCS_FIELD
ADCS_FIELD --> ACME_ADCS_POLL & ADCS_POLLER
ACME_ADCS_POLL -->|"approved → valid"| ACME_FINALIZE
ADCS_POLLER -->|"approved → completed"| API_ISSUE
API_ISSUE --> KO_CTX
KO_CTX --> KO_AZURE & KO_AWS & KO_HC
API_ISSUE --> FE_POLL
FE_POLL -.->|"poll GET /projects/:id/csr/:id"| API_SUBMIT
subgraph cert_mgr["🤖 cert-manager (K8s)"]
CM_ACME["ACME NewOrder\nhttp-01 / dns-01 challenges"]
end
cert_mgr --> ACME_NEW
ACME_NEW --> ACME_VALIDATE --> ACME_FINALIZE
ACME_FINALIZE --> CA_INT & CA_ADCS
classDef frontend fill:#EBF5FB,stroke:#2196F3,color:#1565C0
classDef api fill:#E8F5E9,stroke:#4CAF50,color:#2E7D32
classDef template fill:#F3E5F5,stroke:#9C27B0,color:#4A148C
classDef ca fill:#FFF3E0,stroke:#FF9800,color:#E65100
classDef acme fill:#E0F2F1,stroke:#009688,color:#00695C
classDef pending fill:#FFEBEE,stroke:#F44336,color:#C62828
classDef offload fill:#E8EAF6,stroke:#3F51B5,color:#1A237E
class FE_MODAL,FE_POLL frontend
class API_SUBMIT,API_ISSUE api
class TPL_RESOLVE,TPL_MERGE,TPL_OVERRIDE template
class CA_INT,CA_SMALL,CA_DIGI,CA_SSL,CA_VENAFI,CA_ACME_OUT,CA_ADCS,CA_VAULT ca
class ACME_NEW,ACME_VALIDATE,ACME_FINALIZE,ACME_ADCS_POLL acme
class ADCS_FIELD,ADCS_POLLER pending
class KO_CTX,KO_AZURE,KO_AWS,KO_HC offload
Key Architectural Decisions (2026-06-21)¶
| Component | Issue | Fix | Migration |
|---|---|---|---|
mergeTemplates |
ValidDays alias not synced after inheritance merge → 1-day certs on Smallstep |
Add merged.ValidDays = child.ValidityDays |
— |
| All CA providers | Read ValidDays (alias) not ValidityDays (DB col) |
effectiveDays := template.ValidityDays; if == 0 { effectiveDays = template.ValidDays } |
— |
ResolveTemplateForProject |
OverrideValidityDays in project_template_assignments was never applied |
New ResolveTemplateForProject(ctx, id, projectID) reads override |
— |
IssueCertificate CA lookup |
CAAccountID UUID probed only in organization_ca_accounts → internal CA requests fail |
Dual-probe: try organization_ca_accounts → fallback certificate_authorities |
— |
SubmitProjectCSR |
OrganizationID not set → cert request missing tenant scope |
Read org_id from JWT context |
— |
ACME FinalizeOrder |
strings.Contains for ADCS pending — fragile, dead-end on approval |
errors.As(err, &adcsPending) + ADCSRequestID field + 5-min poller |
000165 |
ACME findOrderByCert |
LIMIT 500 full scan — revocation silently fails for large tenants |
Serial-number JOIN on certificates table |
— |
ACME ValidateChallenge |
tls-alpn-01 silently falls through to http-01 |
Explicit unsupportedIdentifier ACME error |
— |
getIntegrationByProvider |
System-wide first-match → cross-tenant key vault leakage | Scope by organization_id via WithKeyOffloadOrgID(ctx, orgID) |
— |
| Azure KV reader path | GetExternalSecretValue returns raw bytes, base64.Decode was skipped → double-base64 |
Decode base64 in retrieval path | — |