Certificates#
Customer stages are reached on their own domains (acc.acme.nl, *.acme.nl), and those need TLS
certificates from a CA. /config/certificates requests them through the platform's Key Vault:
Key Vault generates the private key and keeps it, CommandCenter hands you the certificate signing
request (CSR) to take to the CA, and the signed certificate is merged back into Key Vault. The key
never passes through CommandCenter — not the browser, not a log, not a database.
Installing a certificate on an IIS HTTPS binding is not part of this page yet; that will be an agent command. Automated issuance by a CA connected to Key Vault is not either.
Context#
flowchart LR
Operator(Operator)
CA(["Certificate authority<br/>(outside CommandCenter)"])
subgraph cc [CommandCenter]
SPA["/config/certificates"]
Platform["WebApi serving the platform<br/>/api/certificates/requests"]
Env["Environment WebApi<br/>/api/bmo/stages"]
end
KV[("Platform Key Vault<br/>certificates tls-…<br/>keys stay here")]
Operator --> SPA
SPA -- "/platform/api/certificates/**" --> Platform
SPA -- "stages of the open environment" --> Env
Platform -- "Key Vault Certificates Officer" --> KV
Operator -- "CSR (PEM)" --> CA
CA -- "signed certificate" --> Operator
- Where: the vault at
Certificates:KeyVault:Uri, else the host's own vault (Configuration:KeyVault:Uri). Without either, the page says so (503not-configured). - Who may: reading is
config.read; requesting, merging and cancelling iscertificates.request(config-admin and security-admin by default,docs/authorization.md). - Which objects: only certificates this page made. Each is named
tls-{common name}-{date}-{4 hex}and taggedcc-purpose=tls. The vault holds other secrets too (signing keys, Key Vault references), and nothing without that prefix and tag is ever listed, changed or deleted.
Requesting and completing a certificate#
sequenceDiagram
autonumber
actor Op as Operator
participant SPA as /config/certificates
participant API as WebApi (platform)
participant KV as Key Vault
participant CA as Certificate authority
Op->>SPA: common name, DNS names (or pick stages), subject, key type
SPA->>API: POST /api/certificates/requests
API->>API: validate; new name tls-acc-acme-nl-20261001-3f2a
API->>KV: start certificate, issuer Unknown, exportable key, tags
KV-->>API: pending operation + CSR (the key stays in Key Vault)
API-->>SPA: 201 pending certificate + CSR (PEM)
Op->>CA: CSR
CA-->>Op: signed certificate (PEM, CER or P7B)
Op->>SPA: upload or paste it
SPA->>API: POST /api/certificates/requests/{name}/merge
API->>API: does one certificate carry the CSR's public key? (422 if none)
API->>KV: merge, leaf first, then the intermediates
KV-->>API: issued
API-->>SPA: 200 certificate (valid until …)
- The subject. Common name, organizational unit, organization, locality, state and country. Key Vault takes the subject as one string, so a comma, plus, quote, backslash, angle bracket, semicolon, equals or hash in a field is refused rather than escaped: it would silently change the name. The common name is always one of the DNS names, and listed first.
- DNS names. Up to 100, each a DNS name or a
*.wildcard. Picking stages adds the host of each stage's URL; the stages are also written to the request'scc-stagestag (whole names, at most 256 characters). - Key. RSA 4096 (default), RSA 2048 or EC P-256. Always exportable, decided at creation and never changeable: the IIS hosts will later take the PFX from Key Vault.
- Validity in months (default 12) is what the request asks for; the CA decides.
- The upload is PEM text (several certificates, or one
PKCS7block), or a CER (DER) or P7B file, at most 96 KB. One of its certificates must carry the request's public key, or nothing reaches Key Vault (422 "not this request's certificate"). That one is merged first, followed by the others.
Pending, issued, expiring#
| Status | Meaning |
|---|---|
| Pending | The CSR is with the CA; download it again, upload the signed certificate, or cancel. |
| Valid | Issued, more than 30 days left. |
| Expiring | 30 days or less left. |
| Expires soon | 14 days or less left. |
| Expired | Past its end date. |
Coverage lists the stages of the environment that is open, each with the issued, unexpired
certificates whose DNS names cover its URL's host. A wildcard covers exactly one label: *.acme.nl
covers acc.acme.nl, not acme.nl and not a.acc.acme.nl.
Cancelling a pending request cancels and deletes it. An issued certificate is never deleted from this page. Key Vault keeps a deleted certificate for its retention period (soft delete), and with purge protection its name stays taken until then; that is why every request gets a new name. Key Vault answers a reused name with 409, shown as "a deleted certificate of that name is still recoverable".
User journey#
journey
title An operator gets a certificate for a new customer domain
section Request
Pick the stages: 5: Operator
Check names and subject: 4: Operator
Copy the CSR: 5: Operator
section At the CA
Submit the CSR: 3: Operator
Receive the certificate: 3: Operator
section Complete
Upload it: 5: Operator
See it valid and covering the stages: 5: Operator
section Months later
Expiring warning: 4: Operator
Request the renewal: 4: Operator
Operator and developer notes#
- Roles. The identity of the host serving the platform needs Key Vault Certificates Officer
on the vault (create, read the CSR, merge, cancel and delete). The dev Azure stack assigns it in
infra/dev/developer.bicep. Without it the page shows Key Vault's refusal (503vault-forbidden) and names the role. Confirm on the first live run that it is sufficient; purging needs Key Vault Purge Operator, which this page never does. - Permissions.
certificates.requestis in the release defaults. A deployment whose/config/accesspermissions were already saved has none of it until it is added to the roles there. Until then nobody can request, and the page says so. - Audit. 3200 requested (name, subject, number of DNS names), 3201 completed (name, thumbprint, valid until), 3202 cancelled. Never key material.
- Code.
backend/CommandCenter.WebApi/Certificates/(ICertificateVaultoverCertificateClient— every assumption about Key Vault's behaviour is inKeyVaultCertificateVault;CertificateRules;CertificateFiles), handlers inDomain/Certificates/, endpoints inEndpoints/Certificates/, the page inClientApps/command-center/src/pages/certificates/. - Tests.
CommandCenter.Test/Certificates/CertificateRequestTests.csruns against a fake vault that makes real keys and real CSRs, signed in the test by a throwaway CA. The round trip against a real vault,KeyVaultCertificateVaultTests(Integration), runs whenCC_TEST_KEYVAULT_URIis set, with an identity that has Certificates Officer there.