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",
"dcv_method": "dns",
"order_years": 2,
"tech_contact": {"firstname": "Jana", "lastname": "Nováková", "email": "pki@example.com"}
}'
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.
dcv_method, ca_product_id, order_years, requested_validity_days, tech_contact and voucher are optional per-request overrides for commercial CAs; omitted values inherit the template, then the CA account (see Commercial CA Orders).
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.
Multi-File Import (Certificate + Separate Key File)¶
When you upload several files at once (for example server.pem and server.key), MazeVault pairs each standalone .key file with the certificate whose public key it matches during the preview step. The paired certificate shows a Private Key badge and a source label such as server.pem + server.key. On import, the key is sent together with its certificate in one request, so it is stored with that certificate — including certificates that already exist in MazeVault without key custody (see below). Keys that match none of the uploaded certificates are reported as an error and never imported on their own.
Adding a Private Key to an Existing Certificate¶
Certificates signed from an uploaded CSR are stored with key custody none, because MazeVault never saw the private key. The same applies to certificates imported without a key. To bring the key under MazeVault custody later, use either path — both end in the same backend logic that verifies the key against the certificate's public key before storing it:
- Certificate detail → Overview → Import private key (recommended). Upload a PEM key file, an encrypted PEM key, or a PFX/P12 bundle, or paste the PEM key. The request is pinned to that certificate (
expected_certificate_id), so material that belongs to a different certificate is rejected instead of creating a new record. - Certificates → Import: import a PFX/P12, a PEM containing both the certificate and the key, or a multi-file upload with the certificate file plus its
.keyfile into the same project. MazeVault detects the existing record by SHA-256 thumbprint and attaches the key.
Rules that apply in both paths:
- The key must match the certificate; a mismatch returns
private key does not match certificate. - A certificate that already has a private key (local or external custody) is never overwritten; the import reports
already has a private key. - Importing into a different project than the one owning the certificate is rejected.
- In Orchestrator Mode the key is offloaded to the selected secret manager integration; local storage is not offered.
- Re-importing a certificate without a key when it already exists returns a hint explaining how to attach the key.
Completing a Certificate That Has No Body¶
A managed certificate adopted from discovery or synchronised from a CA listing may hold metadata only (no certificate body) if it was created before MazeVault started persisting bodies, or if it was discovered only by an agent on the legacy protocol. Such a certificate cannot be exported, deployed or renewed, and a private key cannot be verified against it. It is completed by any of these paths — the result is identical:
- Automatically — the next agent scan or CA synchronisation that sees the certificate attaches the body (matched by SHA-256 thumbprint).
- Certificate detail → Complete certificate — upload the public certificate (CRT/PEM/DER), optionally together with its private key, or a PFX/P12. The upload must match the stored thumbprint.
- Certificates → Import — importing the public certificate alone completes the record instead of reporting "already exists"; the import result item carries
message: "existing certificate record completed with the uploaded certificate body".
The body is stored together with the identity derived from it (serial, thumbprints, subject, issuer, validity, algorithms, key usage, SAN, type). An active status is refreshed from the certificate's validity; a revoked or pending record keeps its status. If a private key is supplied with the certificate: it is attached when the record has no custody, accepted silently when it matches an existing custody, and rejected with an explicit error after the body was stored when it does not match. A locally stored key that could not be verified earlier is verified when the body arrives; a mismatch is flagged in the certificate metadata and the audit log but the key is never deleted — review and replace it deliberately.
Uploaded CSR Validation Against the Template¶
Uploaded CSRs are checked against the selected template before anything is sent to the CA, so policy violations fail fast:
- The CSR must be well-formed and its self-signature must verify.
- If the template defines
allowed_key_sizes, the CSR key size must be listed (for example a 4096-bit RSA CSR is rejected by a template that only allows 2048/3072). - If the template has an explicit SAN policy in
san_config(allowed_san_types,san_validation_pattern,max_san_count), the CSR's SAN entries must satisfy it. The top-levelallow_additional_sans/max_san_countcolumns are not applied to uploaded CSRs. - Set
san_config.require_san = trueon a template to reject CN-only CSRs, which modern TLS clients do not accept. - System templates (agent and gateway mTLS) are exempt from these checks.
If the CA requires manual approval (for example an ADCS CA with manager approval), the request moves to submitted_to_ca and the certificate is finalized automatically once the CA approves it. If automatic issuance fails for another reason, an auto-approved request is marked failed with the error recorded, while a manually approved request is returned to pending so the approver can retry.
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.
How the CA Chain Is Determined¶
With Include CA chain enabled, MazeVault exports the certificate's effective issuer chain. The same chain is shown on the certificate's Chain tab, returned by the API and installed by agents, so every consumer sees identical material.
- Stored chain first. A chain captured when the certificate was imported or issued (for example from a PFX or from the CA at issuance) is exported exactly as stored. MazeVault never appends anything to it — if your issuer delivered
leaf + intermediatewithout the root, that is what you get. - Inventory only when nothing is stored. If the certificate has no stored chain (typical for an intermediate CA imported on its own), MazeVault searches the CA certificates managed in the same organization — including the internal CA — for the issuer. A candidate is used only when it cryptographically verifies the certificate's signature; a matching name alone is never enough. The chain is followed up to the self-signed root.
- Transparency. The Chain tab labels the source (Stored with certificate / Resolved from inventory / No chain), shows whether the chain is complete to the root, and lists warnings such as a revoked issuer. Nothing is written back to the certificate by viewing or exporting it.
Intermediate CA export
To export an intermediate CA together with its root, import the root CA certificate into any project of the same organization once. From then on, the root is included automatically in exports, in the Chain tab and in agent deployments of that intermediate.
Deployments and trust stores
Agents split the exported bundle the same way on every target: the certificate and key go to the keystore (JKS, PKCS#12, Windows My), the issuer chain goes to the trust store (Windows Root/CA, Linux CA directory) when the agent's install_chain_to_trust_store option is enabled. Windows Root/CA targets receive public certificates only — never a private key.
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¶
9. Commercial CA Orders (SSLmarket)¶
Public TLS certificates bought through a commercial CA behave differently from internal or ACME issuance: an order is paid, it cannot be cancelled through the API, it is processed asynchronously, and it is bound to a multi-year subscription that outlives any single certificate. MazeVault treats such an order as a long-running process rather than a request/response call.
Where settings come from (request → template → CA account)¶
| Setting | Request (per certificate) | Certificate template | CA account |
|---|---|---|---|
| Product (validation level, wildcard/SAN limits) | ca_product_id |
ca_product_id |
product_code default |
| Domain validation method | dcv_method (dns, email, file) |
dcv_method |
dv_auth_method |
| Subscription length (years) | order_years |
order_years |
1 year |
| Technical contact | tech_contact |
tech_contact |
tech_* defaults |
| Voucher | voucher |
— | voucher |
| Owner, authorised contact, invoice | — | — | owner_*, auth_*, invoice_* |
The request wins, the template fills in what the request leaves empty, and the CA account supplies the business data. The Request Certificate dialog shows a CA order settings section with exactly the fields the issuing CA allows to be overridden; every field displays the template value as its default, so leaving it empty always means "inherit".
Before anything is ordered, the resolved settings are validated against the product catalog synced from the CA (unsupported validation method, too many SANs, wildcard on a non-wildcard product, subscription longer than the product allows). A request the CA would reject therefore fails when it is submitted, with an actionable message — never as a paid order. The resolved settings and their sources are stored with the request (issuance_spec) and shown in the request detail.
Subscription years are not certificate validity
Since March 2026 public TLS certificates are valid for at most 200 days. order_years is the length of the paid order at SSLmarket (1–6 years, limited by the product). Within that subscription the CA issues follow-up certificates itself — see below.
Asynchronous issuance and attentive waiting¶
- Submission — the request enters
submitting_to_cawhile the order is placed. If the CA does not answer clearly (timeout, 5xx, restart), MazeVault does not retry blindly: it looks the order up by the exact CSR and either continues with it or, after a grace period, returns the request to approved. An order is never placed twice for one request. - Waiting — the request is
submitted_to_ca; MazeVault polls the CA with increasing intervals (2 → 5 → 15 → 60 minutes) while the CA-side state does not change and resets the interval when it does. - Escalation — when an order has not moved for longer than the CA account's Alert when an order has not progressed for (hours) setting (default 24 h), MazeVault opens one incident, comments the DCV ticket if there is one and keeps waiting. It never gives up on a paid order by itself.
- Issuance — the certificate is downloaded, stored with its
external_order_id, and deployment continues as for any other certificate.
Domain control validation (DCV)¶
The CA proves domain control before issuing. How the proof is placed is decided per order:
| Situation | What MazeVault does | What you do |
|---|---|---|
DNS validation and the CA account has a DNS provider configured (dns_solver_type + dns_solver_config) |
Creates the _dnsauth.<domain> TXT record itself, waits for the CA, removes the record afterwards |
Nothing |
| DNS/file validation and the organization has a ticketing integration (Jira) | Opens one ticket with the exact record (name, type, value) and the order id; continues automatically once the CA validates; closes the ticket with a comment | Add the record from the ticket to your DNS or web server |
| Neither | Opens an incident dcv_manual_action_required and shows the record in the DCV panel |
Add the record; MazeVault keeps polling |
| E-mail validation | Waits for the approval e-mail; Resend E-mail asks the CA to send it again | Click the link in the CA's e-mail |
Nothing needs to be confirmed in MazeVault after the record is in place — the CA validates, MazeVault notices and continues.
Renewal of a subscription certificate (follow-up)¶
When a certificate issued under an SSLmarket order is due for renewal, MazeVault first inspects the order:
- The subscription is still running (or the CA renewed the order into a child order, or a reissue is pending): the renewal enters
awaiting_ca_followup. No new order is placed. When the CA issues the follow-up certificate, MazeVault downloads it, verifies that it uses the same public key as the managed certificate (the private key stays, custody is inherited), runs the deployment preflight, deploys, runs post-install steps and only then marks the previous certificate as superseded. If the predecessor is about to expire and the follow-up has not appeared, one incidentsubscription_followup_overdueis opened. - The follow-up was issued with a different key (e.g. a manual reissue in the SSLmarket portal with a new CSR): MazeVault cannot deploy it — the renewal goes to
manual_reviewwith an incidentsubscription_followup_key_mismatch. Either reissue the order with the managed key, import the new key, or trigger a renewal with Place a new order at the CA. - The subscription ends with this certificate: a new order is placed as described above.
Certificates discovered from the CA account carry their order id as well, so adopted certificates take part in this logic too.
Revocation
The SSLmarket API revokes DigiCert Client Premium (S/MIME) certificates only. TLS certificates ordered via SSLmarket are revoked in the SSLmarket customer portal; MazeVault shows this note on the template and request forms.
Renewal states and what they mean¶
| State | Meaning | Your action |
|---|---|---|
submitting_to_ca |
Order is being placed; outcome reconciled by CSR if unclear | None |
submitted_to_ca |
Order accepted; waiting for the CA | None |
pending_dcv |
Domain validation outstanding (see table above) | Depends on placement mode |
awaiting_ca_followup |
Subscription running; waiting for the CA's follow-up certificate | None |
manual_review |
Automatic continuation is not safe; see the incident | Decide: reissue / import key / new order |
deploying |
Certificate received; deploying — the previous certificate stays active | None |
failed with resume |
Deployment / post-install / hand-off failed on some targets; nothing was handed over. While this item exists no new renewal is started automatically or manually (a second paid order would be placed) | Fix the target, then Resume renewal — only the failed targets are redeployed; or Cancel the item explicitly |
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 | — |