Skip to content

Managing Certificates with bmo manage certificate#

bmo manage certificate operates on the Personal store of LocalMachine (Cert:\LocalMachine\My) on each agent host. The CLI never talks to an agent directly — every command goes through the Command Center, which then routes the operation to the agent over Service Bus (default in production) or HTTP (fallback when SB is not configured).


Sub-commands#

Command Purpose Fan-out
bmo manage certificate list Enumerate certificates in LocalMachine\My All agents (or --server filter)
bmo manage certificate upload <FILE_PATH> Install a certificate (PEM / DER / PFX) All agents (or --server filter)
bmo manage certificate download <THUMBPRINT> [OUTPUT_PATH] Export a certificate to disk Single agent (--server, defaults to first configured)
bmo manage certificate delete <THUMBPRINT> Remove a certificate from the store All agents (or --server filter)

list#

# All agents
bmo manage certificate list

# Specific agents
bmo manage certificate list --server APP01 --server APP02

# Machine-readable
bmo manage certificate list -o json

Returns: friendly name, truncated thumbprint, subject, issuer, validity dates (with red/yellow expiry warnings), and whether a private key is present.

upload#

# Plain certificate (.cer / .pem)
bmo manage certificate upload .\contoso.cer

# PFX with password (private key)
bmo manage certificate upload .\contoso.pfx --password "S3cr3t!"

# Single host
bmo manage certificate upload .\contoso.pfx --password "S3cr3t!" --server APP01

Imported with X509KeyStorageFlags.MachineKeySet | PersistKeySet. PFX bytes are loaded with X509CertificateLoader.LoadPkcs12; everything else with LoadCertificate.

download#

# Public part only (DER → .cer)
bmo manage certificate download 1A2B3C4D... .\contoso.cer

# With private key (PFX → .pfx). Password is required.
bmo manage certificate download 1A2B3C4D... .\contoso.pfx `
    --include-private-key --password "S3cr3t!"

Without --server, the first configured agent is used. Output filename defaults to certificate_<8chars>.{cer|pfx} if not given.

delete#

# Interactive
bmo manage certificate delete 1A2B3C4D...

# Skip confirmation
bmo manage certificate delete 1A2B3C4D... --force

# Targeted
bmo manage certificate delete 1A2B3C4D... --server APP01 --force

A confirmation prompt is shown unless --force is set. The CLI prints the thumbprint and the target server count before asking.


Transport: how the bytes get there#

flowchart LR
    Op["👤 Operator"]
    CertStore[("🗄️ LocalMachine\\My")]

    subgraph BMO["bmo CLI"]
        Cmds(("CertificateList/Upload/<br/>Download/DeleteCommand"))
        Client(("CommandCenterClient"))
        Cmds --> Client
    end

    subgraph CC["Command Center"]
        EP(("CertificateEndpoints<br/>/api/agents/{server}/certificates*"))
        Router(("AgentTransportRouter"))
        SBReq(("ServiceBusAgentRequester"))
        AgentHttpClient(("AgentClient (HTTP fallback)"))
        Fleet[("FleetStateStore<br/>(heartbeats)")]

        EP --> Router
        Router -. "fresh?" .-> Fleet
        Router -- "SB configured & online" --> SBReq
        Router -- "HTTP-only mode" --> AgentHttpClient
    end

    subgraph SB["Azure Service Bus"]
        TCmds[("agent.commands.v1")]
        TReply[("agent.replies.v1")]
    end

    subgraph AGENT["Agent"]
        Consumer(("AgentCommandsConsumer"))
        AgentHttp(("CertificateEndpoints<br/>/api/certificates*"))
        X509(("X509Store ops"))
        Consumer --> X509
        AgentHttp --> X509
    end

    Op -->|"list / upload / download / delete"| Cmds
    Client ==>|"HTTPS · ApiKey | Entra"| EP

    SBReq -->|"AgentCommand<br/>Cert*: base64 bytes inline"| TCmds
    TCmds -->|"filter targetServer={agent}"| Consumer
    Consumer -->|"AgentReply<br/>Download body: base64 bytes"| TReply
    TReply -->|"ephemeral sub by correlationId"| SBReq

    AgentHttpClient ==>|"HTTPS direct to agent:5100<br/>multipart / octet-stream"| AgentHttp

    X509 <-->|"X509Certificate2 (DER/PFX)"| CertStore

    classDef ext fill:#fef3c7,stroke:#92400e,stroke-width:1px
    classDef store fill:#e0e7ff,stroke:#3730a3,stroke-width:1px
    class Op,CertStore ext
    class Fleet,TCmds,TReply store

    linkStyle 0,1 stroke:#0ea5e9,stroke-width:2px
    linkStyle 2 stroke:#94a3b8,stroke-dasharray:3 3
    linkStyle 3 stroke:#16a34a,stroke-width:1.5px
    linkStyle 4 stroke:#dc2626,stroke-width:1.5px
    linkStyle 5,6,7,8 stroke:#16a34a,stroke-width:1.5px
    linkStyle 9 stroke:#dc2626,stroke-width:1.5px
    linkStyle 10 stroke:#7c3aed,stroke-width:1.5px
Hold "Alt" / "Option" to enable pan & zoom

Reading the diagram:

  • Cyan — operator → bmo → CC. Always HTTPS, authenticated by ApiKey or Entra ID bearer.
  • Green — Service Bus path. Default in production. Cert payloads travel inline as base64 in the SB message body (no blob staging for certs).
  • Red — HTTP fallback. Used only when ServiceBus.Namespace is not set on the Command Center; the CC dials each agent on its Port (default 5100) directly.
  • Dashed grey — heartbeat freshness check. CC consults FleetStateStore; if the agent's last snapshot is older than MaxAgentStateAge (default 15 min), the request fails fast with 503 Service Unavailable before anything is published to Service Bus.

Per-operation payloads#

Op Request data into agent Reply data
list (none) CertificateInfo[]
upload SbCertUploadPayload { fileName, password?, base64(bytes) } (SB) — or multipart/form-data { file, password } (HTTP) success / 503
download SbCertDownloadPayload { thumbprint, includePrivateKey, password? } SbCertDownloadBody { fileName, contentType, base64(bytes) } (SB) — or application/octet-stream (HTTP)
delete CertificateDeleteRequest { thumbprint } success / 503

Size limits#

Service Bus standard messages are capped at 1 MB. A typical PFX with a single RSA-2048 key is well under that, but bundles with intermediate chains plus large keys can approach the limit. If you hit it, fall back to HTTP-only mode for that one upload by temporarily setting --server against an agent reachable on TCP/5100, or split the bundle.


Authentication and operator identity#

  • The CC requires the X-Api-Key header (matching CommandCenter.ApiKey on the bmo side) or an Entra ID bearer token if AzureAd is configured.
  • The agent does not authenticate the SB-mode request as a user — it trusts that whatever published on agent.commands.v1 came through the CC. This means CC's API key / Entra config is the gatekeeper for certificate operations.
  • The agent's HTTP endpoints (/api/certificates*) likewise rely on CC's identity; lock the agent's port 5100 to the CC's IP if you keep HTTP-mode enabled.

Platform notes#

  • Windows only. All four operations short-circuit on non-Windows: list returns an empty array, upload / download / delete return 400 BadRequest. Production agents are always on Windows Server, so this is mostly relevant when running the agent locally on macOS / Linux for development.
  • Store filter on list. The agent skips certificates whose NotAfter.Year < Today.Year - 1 to keep output focused on currently-relevant certs. Long-expired certs are still in the store; they are simply hidden.

Troubleshooting#

Symptom Likely cause Fix
503 Service Unavailable from CC immediately Agent heartbeat stale (older than MaxAgentStateAge) Check bmo state list or the CC fleet endpoint; restart the agent service if it is unreachable.
400 BadRequest: Certificate operations are only supported on Windows Targeting a Linux/macOS agent Run against a Windows agent, or switch host.
404 Not Found on download/delete Thumbprint not in LocalMachine\My on that agent Confirm with bmo manage certificate list --server <name>.
PFX upload fails with "The specified network password is not correct" Wrong --password, or the PFX is unencrypted Verify the password, or omit --password for an unencrypted PFX.
Upload succeeds but the app does not see the cert Reading the wrong store / location The agent always writes to LocalMachine\My. If the consuming service expects CurrentUser or a different store, install via that service's mechanism instead.
SB upload of a large PFX fails Message exceeds the 1 MB Service Bus body limit Use HTTP-only mode for that upload, or split the bundle.
delete aborted at the prompt Confirmation declined Re-run with --force to skip the prompt, or answer y.

Operator checklist#

  • [ ] CC is reachable from the bmo workstation (HTTPS, correct ApiKey / Entra config)
  • [ ] Target agents have a fresh heartbeat (bmo state list shows them within MaxAgentStateAge)
  • [ ] Operating against Windows agents (cert operations are Windows-only)
  • [ ] PFX uploads include --password if the file is encrypted
  • [ ] Downloads with --include-private-key always specify --password (the resulting PFX is encrypted with it)
  • [ ] If using HTTP fallback (no ServiceBus.Namespace set), agent port 5100 is open from the CC host

See also#