Skip to content

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
Hold "Alt" / "Option" to enable pan & zoom
  • Where: the vault at Certificates:KeyVault:Uri, else the host's own vault (Configuration:KeyVault:Uri). Without either, the page says so (503 not-configured).
  • Who may: reading is config.read; requesting, merging and cancelling is certificates.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 tagged cc-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 …)
Hold "Alt" / "Option" to enable pan & zoom
  • 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's cc-stages tag (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 PKCS7 block), 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
Hold "Alt" / "Option" to enable pan & zoom

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 (503 vault-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.request is in the release defaults. A deployment whose /config/access permissions 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/ (ICertificateVault over CertificateClient — every assumption about Key Vault's behaviour is in KeyVaultCertificateVault; CertificateRules; CertificateFiles), handlers in Domain/Certificates/, endpoints in Endpoints/Certificates/, the page in ClientApps/command-center/src/pages/certificates/.
  • Tests. CommandCenter.Test/Certificates/CertificateRequestTests.cs runs 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 when CC_TEST_KEYVAULT_URI is set, with an identity that has Certificates Officer there.