Command-line interface for the PKI Manager API — X.509 certificate authorities and certificates, CRLs, the SSH Certificate Manager, bulk operations, the audit log and the machine-facing issuer APIs.
Coverage: every operation in the server's OpenAPI document (/api/v1/openapi.json)
has a corresponding command, plus the unauthenticated trust-material routes served at
the server root. See Command ↔ endpoint map.
| pki-cli | PKI Manager | REST API |
|---|---|---|
| 0.5.0 | 3.11.0 | v1.0.0 |
Against an older server, commands for endpoints it does not have return 404; everything else works unchanged. SSH zones need 3.11.0+, and the 22 SSH lifecycle commands need the SSH REST/tRPC parity work that followed 3.9.5.
# Using uv (recommended)
uv sync
uv run pki --help
# Or install globally with pipx
pipx install .
pki --helpOnly PKI_API_URL is mandatory. For authentication, pick one of three modes:
- OIDC client credentials (normal) — set
PKI_OIDC_URL,PKI_CLIENT_IDandPKI_CLIENT_SECRETtogether. The CLI exchanges them for a token and caches it. - Pre-obtained bearer token — set
PKI_TOKEN(or--token) to skip the OIDC exchange, e.g. when a CI step already minted a token. - Anonymous — leave all of the above unset. This only works against a deployment
running with
OIDC_ENABLED=false, typically a local dev stack.
Settings come from environment variables, a .env file, or CLI options (in increasing
order of precedence). Run pki config to see which mode is active.
cp .env.example .env
# Edit .env with your server URLs and credentialsThe CLI looks for .env files in:
- Current working directory
~/.config/pki-cli/.env(global configuration)
export PKI_API_URL=https://your-pki-server.example.com/api/v1
export PKI_OIDC_URL=https://your-iam-server.example.com/realms/realm/protocol/openid-connect/token
export PKI_CLIENT_ID=your-client-id
export PKI_CLIENT_SECRET=your-client-secret
# ...or a pre-obtained token instead of the three OIDC values
export PKI_TOKEN=eyJhbGciOi...
# ...or nothing at all, against a dev server with OIDC disabled
export PKI_API_URL=http://localhost:52081/api/v1pki --api-url https://... --oidc-url https://... \
--client-id your-id --client-secret your-secret \
ca listpki config # Show current configuration
pki login # Test authentication
pki logout # Clear cached token
pki health # Check API healthpki ca list # List all CAs
pki ca list --status active --search acme # Filter and search
pki ca list --sort-by expiryDate --sort-order asc --limit 20 --offset 20
pki ca get <CA_ID> # Get CA details
pki ca create --cn "My CA" --org "MyOrg" --validity-years 10
pki ca certs <CA_ID> --status active # Certificates issued by this CA
pki ca revoke <CA_ID> --reason keyCompromise # Revoke CA
pki ca delete <CA_ID> --destroy-key --force # Delete CA (and its KMS key)
# Download the CA certificate / a truststore bundle
pki ca download <CA_ID> --format pem -o ca.pem
pki ca download <CA_ID> --format p12-truststore --password pass -o truststore.p12
pki ca pem <CA_ID> # Public AIA URL, no credentials neededRevocation reasons are the RFC 5280 camelCase codes: unspecified, keyCompromise,
caCompromise, affiliationChanged, superseded, cessationOfOperation,
certificateHold, removeFromCRL.
pki cert list # List all certificates
pki cert list --ca <CA_ID> # Filter by CA
pki cert list --type server --status active # Filter by type and status
pki cert list --search example.com --sort-by notAfter --sort-order asc
pki cert get <CERT_ID> # Get certificate details
# Issue a new certificate (the CN is added to the DNS SANs automatically)
pki cert issue --ca <CA_ID> --cn "example.com" --type server \
--dns "www.example.com" --dns "api.example.com" \
--ip 10.0.0.1 --email admin@example.com --ou Platform --tag prod
pki cert renew <CERT_ID> --validity 365 # Renew certificate
pki cert renew <CERT_ID> --same-key --revoke-original
pki cert renew <CERT_ID> --cn new.example.com # Renew with an updated subject
pki cert revoke <CERT_ID> --reason keyCompromise --generate-crl
pki cert delete <CERT_ID> --destroy-key --force
# Download certificate
pki cert download <CERT_ID> # PEM (default)
pki cert download <CERT_ID> -f chain-pem # Certificate + issuing chain
pki cert download <CERT_ID> -f key-pem # Private key
pki cert download <CERT_ID> -f full-pem # Certificate + key + chain
pki cert download <CERT_ID> -f p12 -p secret # PKCS#12 keystore
pki cert download <CERT_ID> -f jks-keystore -p secretDownload formats: pem, der, chain-pem, full-pem, full-der, csr-pem,
key-pem, key-der, pkcs8-pem, pkcs8-der, pkcs8-encrypted, p12/pfx,
jks-keystore, jks-truststore.
Up to 100 certificates per call. IDs come from repeatable --id flags, a
comma-separated list, or --ids-file (one per line, - for stdin).
# Issue from a headerless CSV: certificateType,CN,O,C,SANs,validityDays
# (SANs are ';'-separated and auto-classified into DNS / IP / email)
printf 'server,web1.acme.io,Acme,ES,web1.acme.io;10.0.0.1,365\n' > certs.csv
pki cert bulk issue --ca <CA_ID> --csv certs.csv
pki cert list --status active -o json | jq -r '.items[].id' > ids.txt
pki cert bulk renew --ids-file ids.txt --validity 90
pki cert bulk revoke --ids-file ids.txt --reason superseded --force
pki cert bulk delete --ids-file ids.txt --destroy-key --force
pki cert bulk download --ids-file ids.txt --format pem --output-file bundle.zipbulk commands exit non-zero if any item failed, and print the per-item result.
pki crl list <CA_ID> # CRLs this CA has published
pki crl generate <CA_ID> --next-update-days 14 # Publish a new CRL
pki crl download <CA_ID> -o ca.crl # Public CDP download (PEM)
pki crl download <CA_ID> --format der -o ca.derpki domain list --search acme.io # Domain inventory from cert SANs
pki domain list --ca <CA_ID> --limit 100
pki audit list --limit 50 # Tamper-evident operation log
pki audit list --operation ca.create --status failure
pki audit list --entity-type certificate --entity-id <CERT_ID>
pki audit list --start 2026-01-01 --end 2026-06-30 # ISO date or Unix timestamp
pki report generate --type certificate_inventory --output-file inventory.csv
pki report generate --type revocation --ca <CA_ID> --start 2026-01-01
pki report generate --type ca_operations -o jsonManage SSH CAs, host/user certificates, principals, fleet tokens and per-host
access blocks. These use the same OIDC credentials as the X.509 commands.
--pubkey/--csr flags accept an inline value or an @path/to/file reference.
# CAs
pki ssh ca list # List user + host CAs
pki ssh ca create --type user --label "acme-users" # Create a CA
pki ssh ca get <CA_ID> # Inspect one CA
pki ssh ca import --type host --kms-key-id <K> --kms-public-key-id <P>
pki ssh ca pub <CA_ID> # Print the CA's OpenSSH public key
pki ssh ca rotate <CA_ID> # New keypair, predecessor kept for trust
pki ssh ca retire <CA_ID> # Stop issuance, keep existing certs trusted
pki ssh ca revoke <CA_ID> --reason "compromise" # Revoke the CA itself
pki ssh ca krl-generate <CA_ID> # Rebuild the CA's KRL
pki ssh ca revocations <CA_ID> # List revocations
pki ssh ca revoke-serial <CA_ID> --serial 42 # Revoke a serial with no cert row
pki ssh ca revoke-key <CA_ID> --fingerprint SHA256:... # Deny a key this PKI never issued
# Hosts
pki ssh host register --fqdn web1.acme.internal --pubkey @/etc/ssh/ssh_host_ecdsa_key.pub
pki ssh host issue --host-id <HOST_ID> --ttl 2592000
pki ssh host list --fqdn web1.acme.internal
pki ssh host get <HOST_ID> # Inspect one host
pki ssh host access <HOST_ID> # Who can reach this host
pki ssh host deploy-bundle <HOST_ID> # sshd drop-in + CA keys + on-host paths
pki ssh host ecies-key <HOST_ID> # Register its ECIES/KRL recipient key
pki ssh host cert <HOST_ID> # Print the host certificate
pki ssh host sshd-config <HOST_ID> # Print the sshd_config drop-in
pki ssh host auth-principals <HOST_ID> # Render AuthorizedPrincipalsFile contents
pki ssh host mark-pushed <HOST_ID> # Clear Stale after pushing those files
pki ssh host blocks <HOST_ID> # Block history (active + lifted)
pki ssh host revoke <HOST_ID> --reason "rebuilt" # Revoke its current certificate
pki ssh host offboard <HOST_ID> # Terminal: retires its per-host KRL lineage
# Identities & user certificates
pki ssh identity list # List identities (subject is UNIQUE)
pki ssh identity create --subject alice
pki ssh identity blocks <ID> # Active blocks + distribution state
pki ssh identity collisions <ID> # Identities sharing a certified key
pki ssh identity disable <ID> # No new certs; existing ones stay valid
pki ssh identity offboard <ID> --reason "left" # Disable + revoke its live certificates
pki ssh user issue --identity-id <ID> --pubkey @~/.ssh/id_ed25519.pub \
-p admins -p developers --ttl 3600 -e permit-pty
pki ssh user certificates --identity-id <ID> # List issued user certificates
# Principals (roles)
pki ssh principal create --name admins
pki ssh principal map --host-id <HOST_ID> --principal-id <PID> --local-account root
pki ssh principal grant --identity-id <ID> --principal-id <PID>
pki ssh principal mappings # principal -> (host, local account), fleet-wide
pki ssh principal stale-hosts # Maps changed since the last push
pki ssh principal delete <PID> # Refused while anything still references it
# Bulk certificate lifecycle
pki ssh bulk expiring --within-seconds 86400 # Expiring within a window
pki ssh bulk renew -i <CERT_ID> -i <CERT_ID> # ...or --ids-file ids.txt
pki ssh bulk revoke --ids-file ids.txt --reason "rotation"
# Fleet tokens (automation credentials for the external SSH API)
pki ssh token mint --name ci --op sign-host --op sign-user --host-ca-id <CA_ID>
pki ssh token revoke <TOKEN_ID>
# Per-host access blocks + revocation
pki ssh block create --host-id <HOST_ID> --identity-id <ID> --reason "offboarded"
pki ssh block unblock --host-id <HOST_ID> --identity-id <ID>
pki ssh block fleet # Fleet-wide block + KRL distribution
pki ssh cert revoke <CERT_ID> --reason "key compromise"
# Zones (multi-tenant scoping)
pki ssh zone list # Active zones
pki ssh zone list --include-archived
pki ssh zone get tenant-b # By ID or name
pki ssh zone create --name tenant-b --display-name "Tenant B"
pki ssh zone update tenant-b --display-name "Tenant B Ltd"
pki ssh zone archive tenant-b # Blocks new entities, keeps trust material served
pki ssh zone unarchive tenant-b
# Trust material & health
pki ssh trust-anchors --zone default # TrustedUserCAKeys / @cert-authority
pki ssh host-ca-keys # Host CA public key(s)
pki ssh cert-authority -p '*.acme.internal' # known_hosts @cert-authority lines
pki ssh metrics # KRL / expiry health metrics
pki ssh trusted-user-ca-keys # TrustedUserCAKeys file contents
pki ssh krl <CA_ID> -o revoked_keys # Download a CA's public KRL bytes
pki ssh ca krl <CA_ID> -o revoked_keys # Same, authenticated
pki ssh krl-host <HOST_ID> # A host's composed public KRLZones and
--zone. A zone scopes SSH CAs, hosts, identities, principals and fleet tokens so one installation can serve several tenants, and entities may only reference CAs in their own zone.Zone resolution is fail-closed: the server resolves the zone implicitly only while exactly one is active, and errors once several are. Two groups of commands take
--zone <id-or-name>:
- Required once a second zone exists — the commands that create something, or that resolve exactly one zone:
ssh ca create,ssh ca import,ssh host register,ssh identity create,ssh principal create,ssh token mint,ssh trust-anchorsandexternal ssh krl. Without--zonethese fail with "zone is ambiguous — N zones exist".- Optional filter —
ssh ca list,ssh host list,ssh identity list,ssh principal listandssh metricsspan every zone by default and narrow to one when--zoneis given.Note the server documents the zone selector on the read endpoints only in their OpenAPI summary text (
?zoneId=), not as a declared parameter — likewise?includeArchived=onGET /ssh/zones. The CLI sends them anyway; a client generated purely from the spec could not.
The public trust-material routes (
ca pub,host cert,sshd-config,host-ca-keys,trusted-user-ca-keys,cert-authority,krl) are served at the server root, not under/api/v1. On deployments where a web frontend is mounted at the root, these may be shadowed — the CLI reports this clearly rather than printing the HTML page.
Machine-facing endpoints authenticated with their own bearer token (not OIDC):
- Cluster token (
pkimg_..., one per CA) for the X.509 issuer API —--tokenorPKI_CLUSTER_TOKEN. - Fleet token (minted via
pki ssh token mint) for the SSH automation API —--tokenorPKI_FLEET_TOKEN.
# X.509 issuer API (cluster token)
pki external health --token pkimg_xxx
pki external ca-bundle --token pkimg_xxx --output-file ca-chain.pem
pki external sign --token pkimg_xxx --csr @request.csr --request-uid $(uuidgen)
pki external revoke --token pkimg_xxx --serial 0A1B2C
# SSH automation API (fleet token)
pki external ssh sign-host --fqdn web1.acme.internal --pubkey @/etc/ssh/ssh_host_ecdsa_key.pub
pki external ssh sign-user --subject alice --pubkey @~/.ssh/id_ed25519.pub -p admins
pki external ssh register-host-pubkey --fqdn web1.acme.internal
pki external ssh auth-principals web1.acme.internal
pki external ssh krl --host-id web1.acme.internal -o krl.enc # ECIES envelope, no tokenpki health # API health + version
pki stats # Show statistics
pki expiring --limit 20 # Show expiring certificates
pki search "example" # Search CAs, certificates and domainsAll commands support -o json for JSON output. Status lines (✓/✗/notes) go to
stderr, so JSON output pipes cleanly:
pki ca list -o json | jq -r '.items[].id'# Create a CA and issue a server certificate
CA_ID=$(pki ca create --cn "Internal CA" -o json | jq -r '.id')
pki cert issue --ca "$CA_ID" --cn "web.internal" --type server
# Renew every certificate expiring soon, in one call
pki expiring --limit 20 -o json | jq -r '.[] | select(.type=="Server") | .id' > ids.txt
pki cert bulk renew --ids-file ids.txt --validity 365
# Rotate a CA's CRL and publish it to a distribution point
pki crl generate "$CA_ID" --next-update-days 7
pki crl download "$CA_ID" -o /var/www/crl/internal.crl
# Bootstrap trust on a new host without credentials
pki ca pem "$CA_ID" > /usr/local/share/ca-certificates/internal.crt
# Who touched this certificate?
pki audit list --entity-type certificate --entity-id "$CERT_ID"
# Quarterly compliance evidence
pki report generate --type certificate_inventory --output-file q3-inventory.csv| Variable | Description | Required |
|---|---|---|
PKI_API_URL |
PKI Manager API URL | Yes |
PKI_OIDC_URL |
OIDC token endpoint | With OIDC auth |
PKI_CLIENT_ID |
OIDC client ID | With OIDC auth |
PKI_CLIENT_SECRET |
OIDC client secret | With OIDC auth |
PKI_TOKEN |
Pre-obtained bearer token (alternative to the three OIDC values) | No |
PKI_CLUSTER_TOKEN |
Cluster bearer token for pki external (X.509 issuer API) |
For external |
PKI_FLEET_TOKEN |
Fleet bearer token for pki external ssh (SSH automation API) |
For external ssh |
Every operation in the server's OpenAPI document is reachable from the CLI. Regenerate this table (and verify coverage) after a server upgrade — see Keeping up with the server.
Dashboard
| Endpoint | Command |
|---|---|
GET /dashboard/expiring |
pki expiring |
GET /dashboard/stats |
pki stats |
GET /health |
pki health |
Search
| Endpoint | Command |
|---|---|
GET /search |
pki search |
Certificate Authorities
| Endpoint | Command |
|---|---|
GET /cas/ |
pki ca list |
POST /cas/ |
pki ca create |
DELETE /cas/{id} |
pki ca delete |
GET /cas/{id} |
pki ca get |
GET /cas/{id}/certificates |
pki ca certs |
GET /cas/{id}/crls |
pki crl list |
POST /cas/{id}/crls |
pki crl generate |
GET /cas/{id}/download |
pki ca download |
POST /cas/{id}/revoke |
pki ca revoke |
Certificates
| Endpoint | Command |
|---|---|
GET /certificates/ |
pki cert list |
POST /certificates/ |
pki cert issue |
DELETE /certificates/{id} |
pki cert delete |
GET /certificates/{id} |
pki cert get |
GET /certificates/{id}/download |
pki cert download |
POST /certificates/{id}/renew |
pki cert renew |
POST /certificates/{id}/revoke |
pki cert revoke |
Bulk Operations
| Endpoint | Command |
|---|---|
DELETE /certificates/bulk/ |
pki cert bulk delete |
POST /certificates/bulk/download |
pki cert bulk download |
POST /certificates/bulk/issue |
pki cert bulk issue |
POST /certificates/bulk/renew |
pki cert bulk renew |
POST /certificates/bulk/revoke |
pki cert bulk revoke |
Domains
| Endpoint | Command |
|---|---|
GET /domains |
pki domain list |
Audit
| Endpoint | Command |
|---|---|
GET /audit |
pki audit list |
POST /reports |
pki report generate |
SSH Certificate Manager
| Endpoint | Command |
|---|---|
POST /ssh/blocks |
pki ssh block create |
GET /ssh/blocks/fleet |
pki ssh block fleet |
POST /ssh/blocks/unblock |
pki ssh block unblock |
GET /ssh/bulk/expiring |
pki ssh bulk expiring |
POST /ssh/bulk/renew |
pki ssh bulk renew |
POST /ssh/bulk/revoke |
pki ssh bulk revoke |
GET /ssh/cas |
pki ssh ca list |
POST /ssh/cas |
pki ssh ca create |
POST /ssh/cas/import |
pki ssh ca import |
GET /ssh/cas/{caId} |
pki ssh ca get |
POST /ssh/cas/{caId}/krl |
pki ssh ca krl-generate |
GET /ssh/cas/{caId}/krl.bin |
pki ssh ca krl |
POST /ssh/cas/{caId}/retire |
pki ssh ca retire |
GET /ssh/cas/{caId}/revocations |
pki ssh ca revocations |
POST /ssh/cas/{caId}/revoke |
pki ssh ca revoke |
POST /ssh/cas/{caId}/revoke-key |
pki ssh ca revoke-key |
POST /ssh/cas/{caId}/revoke-serial |
pki ssh ca revoke-serial |
POST /ssh/cas/{caId}/rotate |
pki ssh ca rotate |
POST /ssh/certs/{id}/revoke |
pki ssh cert revoke |
GET /ssh/hosts |
pki ssh host list |
POST /ssh/hosts |
pki ssh host register |
POST /ssh/hosts/issue |
pki ssh host issue |
GET /ssh/hosts/{id} |
pki ssh host get |
GET /ssh/hosts/{id}/access |
pki ssh host access |
GET /ssh/hosts/{id}/auth-principals |
pki ssh host auth-principals |
POST /ssh/hosts/{id}/auth-principals/pushed |
pki ssh host mark-pushed |
GET /ssh/hosts/{id}/blocks |
pki ssh host blocks |
GET /ssh/hosts/{id}/deploy-bundle |
pki ssh host deploy-bundle |
POST /ssh/hosts/{id}/ecies-key |
pki ssh host ecies-key |
POST /ssh/hosts/{id}/offboard |
pki ssh host offboard |
POST /ssh/hosts/{id}/revoke |
pki ssh host revoke |
GET /ssh/identities |
pki ssh identity list |
POST /ssh/identities |
pki ssh identity create |
GET /ssh/identities/{id}/blocks |
pki ssh identity blocks |
GET /ssh/identities/{id}/collisions |
pki ssh identity collisions |
POST /ssh/identities/{id}/disable |
pki ssh identity disable |
POST /ssh/identities/{id}/offboard |
pki ssh identity offboard |
GET /ssh/metrics |
pki ssh metrics |
GET /ssh/principals |
pki ssh principal list |
POST /ssh/principals |
pki ssh principal create |
POST /ssh/principals/grant |
pki ssh principal grant |
POST /ssh/principals/map |
pki ssh principal map |
GET /ssh/principals/mappings |
pki ssh principal mappings |
GET /ssh/principals/stale-hosts |
pki ssh principal stale-hosts |
DELETE /ssh/principals/{id} |
pki ssh principal delete |
GET /ssh/tokens |
pki ssh token list |
POST /ssh/tokens |
pki ssh token mint |
POST /ssh/tokens/{id}/revoke |
pki ssh token revoke |
GET /ssh/trust-anchors |
pki ssh trust-anchors |
GET /ssh/users/certificates |
pki ssh user certificates |
POST /ssh/users/issue |
pki ssh user issue |
GET /ssh/zones |
pki ssh zone list |
POST /ssh/zones |
pki ssh zone create |
GET /ssh/zones/{ref} |
pki ssh zone get |
POST /ssh/zones/{ref} |
pki ssh zone update |
POST /ssh/zones/{ref}/archive |
pki ssh zone archive |
POST /ssh/zones/{ref}/unarchive |
pki ssh zone unarchive |
External Issuer
| Endpoint | Command |
|---|---|
GET /external/ca-bundle |
pki external ca-bundle |
GET /external/health |
pki external health |
POST /external/revoke |
pki external revoke |
POST /external/sign |
pki external sign |
SSH External Issuer
| Endpoint | Command |
|---|---|
GET /external/ssh/hosts/{fqdn}/auth-principals |
pki external ssh auth-principals |
POST /external/ssh/krl |
pki external ssh krl |
POST /external/ssh/register-host-pubkey |
pki external ssh register-host-pubkey |
POST /external/ssh/sign-host |
pki external ssh sign-host |
POST /external/ssh/sign-user |
pki external ssh sign-user |
Public routes (served at the server root, not under /api/v1, and marked
hide: true so they do not appear in the OpenAPI document):
| Endpoint | Command |
|---|---|
GET /cas/{caId}.{format} |
pki ca pem |
GET /crl/{caId}.{format} |
pki crl download |
GET /krl/{caId}.{bin,json} |
pki ssh krl |
GET /krl/hosts/{hostId}.{bin,json} |
pki ssh krl-host |
GET /ssh/cas/{id}/ca.pub |
pki ssh ca pub |
GET /ssh/cert-authority |
pki ssh cert-authority |
GET /ssh/host-ca-keys |
pki ssh host-ca-keys |
GET /ssh/hosts/{id}/cert.pub |
pki ssh host cert |
GET /ssh/hosts/{id}/sshd-config |
pki ssh host sshd-config |
GET /ssh/trusted-user-ca-keys |
pki ssh trusted-user-ca-keys |
src/pki_api/ is generated from the server's OpenAPI document and openapi.json
is the copy it was generated from. After a PKI Manager upgrade:
curl -s "$PKI_API_URL/openapi.json" -o openapi.json
uv run openapi-python-client generate --path openapi.json --meta none \
--output-path /tmp/gen --overwrite
rm -rf src/pki_api/pki_manager_rest_api_client
cp -r /tmp/gen src/pki_api/pki_manager_rest_api_client
touch src/pki_api/pki_manager_rest_api_client/py.typedThen diff openapi.json against the previous revision for new or changed
operations, add commands for anything new, and bump PKI_MANAGER_VERSION in
src/pki_cli/__init__.py.
- Never commit
.envfiles containing credentials - The
.gitignoreis configured to exclude.envfiles - Use
~/.config/pki-cli/.envfor personal credentials - Token cache is stored in
~/.cache/pki-cli/tokenwith restricted permissions
| Project | Description |
|---|---|
| PKI Manager | Main PKI Manager web application |
| PKI Manager Ansible | Ansible Collection for certificate management (Galaxy) |
| PKI Manager Skill | Claude Code skill for AI-assisted certificate management |