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
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.Namespaceis not set on the Command Center; the CC dials each agent on itsPort(default5100) directly. - Dashed grey — heartbeat freshness check. CC consults
FleetStateStore; if the agent's last snapshot is older thanMaxAgentStateAge(default 15 min), the request fails fast with503 Service Unavailablebefore 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-Keyheader (matchingCommandCenter.ApiKeyon the bmo side) or an Entra ID bearer token ifAzureAdis configured. - The agent does not authenticate the SB-mode request as a user — it trusts that whatever published on
agent.commands.v1came 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 port5100to the CC's IP if you keep HTTP-mode enabled.
Platform notes#
- Windows only. All four operations short-circuit on non-Windows:
listreturns an empty array,upload/download/deletereturn400 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 whoseNotAfter.Year < Today.Year - 1to 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 listshows them withinMaxAgentStateAge) - [ ] Operating against Windows agents (cert operations are Windows-only)
- [ ] PFX uploads include
--passwordif the file is encrypted - [ ] Downloads with
--include-private-keyalways specify--password(the resulting PFX is encrypted with it) - [ ] If using HTTP fallback (no
ServiceBus.Namespaceset), agent port5100is open from the CC host
See also#
- Command Center setup and operations —
MaxAgentStateAge,AgentTimeouts.CertificateBinary, ApiKey/Entra config - Agent setup and operations — agent host firewall rules, SB-only topology
- Orchestrator setup and operations — bmo authentication and
CommandCenter.BaseUrl