Key management#
Every key that can sign an identity, a checkpoint or a licence, or decrypt stored data, is customer-held. MAQPNA never holds, escrows or phones home with a key. Keys are referenced either by a file path mounted from a Kubernetes Secret, or by a key URI resolved by pkg/keys.
Key URIs#
| URI | Algorithm | Status |
|---|---|---|
file:///abs/path |
Ed25519, PKCS#8 PEM | Implemented. The file is refused if it is readable by others or writable by the group (0600 or 0400; 0440 tolerated for Secret volumes with fsGroup) |
pkcs11:token=…;object=…?module-path=…&pin-source=file:… (RFC 7512) |
— | Planned. The URI is parsed and validated (an inline pin-value is rejected), but signing returns ErrNotConfigured until a backend is registered with keys.RegisterPKCS11. No backend is registered in the released binaries |
kms://<provider>/<key-id> |
— | Planned. Parsed and validated; signing returns ErrNotConfigured until a backend is registered with keys.RegisterKMS. None is registered in the released binaries |
azurekv://<vault>/<key>[/<version>] |
ES256 (ECDSA P-256 on a Key Vault HSM key) | Implemented for licence signing only (ParseJWSKeyURI, used by maqpna license issue). The key never leaves the HSM; the signer calls the Key Vault REST API (7.4) without an SDK |
Azure Key Vault authentication, first that applies: AZURE_TENANT_ID + AZURE_CLIENT_ID + AZURE_CLIENT_SECRET (client credentials); AZURE_TENANT_ID + AZURE_CLIENT_ID + AZURE_FEDERATED_TOKEN_FILE (workload identity); then az account get-access-token. For a sovereign cloud, give the vault's full host name.
Key IDs are RFC 7638 JWK thumbprints everywhere, so a kid in a token, checkpoint or card names exactly one public key.
Sealers encrypt data at rest. keys.ParseSealerURI accepts file:/// (a 32-byte AES-256 key) and, when registered, pkcs11: or kms://. AESGCMSealer uses AES-256-GCM with a random 96-bit nonce and key ID aes256gcm:<16 hex>; a Keyring holds one primary key and any retired keys, so data sealed under an old key still opens after rotation.
Which component holds which key#
flowchart LR
subgraph IDN["maqpna-identity"]
K1["identity signing key<br/>Ed25519, -key file"]
K2["tenant signing keys<br/>Secret maqpna-tenant-keys"]
end
subgraph GWN["maqpna-gateway"]
K3["audit checkpoint key<br/>auditCheckpointKey URI"]
K4["HMAC checkpoint key<br/>MAQPNA_AUDIT_HMAC_KEY"]
K5["token vault key<br/>tokenVault.keyURI sealer"]
K6["capture key<br/>captureArgs key sealer"]
K7["Txn-Token key<br/>txnTokens.signingKey"]
K8["A2A card key<br/>a2a.cardSigningKey"]
end
subgraph ATN["maqpna-attest"]
K9["response signing key<br/>-response-signing-key file"]
end
subgraph CLI["maqpna CLI, offline"]
K10["licence signing key<br/>file or azurekv URI"]
K11["usage report key<br/>file URI"]
end
K1 -- "JWKS /.well-known/jwks.json" --> V1["gateway, broker verify tokens"]
K3 -- "JWKS /v1/audit/jwks" --> V2["auditors verify checkpoints<br/>and erasure certificates"]
K8 -- "JWKS /a2a/jwks.json" --> V3["remote A2A peers"]
K9 -- "public key to the operator" --> V4["attest agents in the VM"]
| Key | Held by | Signs or protects | Published as |
|---|---|---|---|
| Identity signing key | Identity broker (-key, Ed25519 PEM; identity.key.mode generate, helm or existingSecret) |
Session, exchanged and delegated tokens | GET /.well-known/jwks.json |
| Tenant signing keys | Identity broker (Secret maqpna-tenant-keys, <tenant>.pem, generated by the operator or copied from signingKeySecretRef) |
Tokens for a tenant's namespaces, in its trust domain | Same JWKS |
| Audit checkpoint key | Gateway (auditCheckpointKey, key URI) |
Ed25519 JWS ledger checkpoints and memory erasure certificates | GET /v1/audit/jwks |
| HMAC checkpoint key | Gateway (MAQPNA_AUDIT_HMAC_KEY, from the chart's Secret) |
HMAC checkpoints in <ledger>.checkpoints |
Not published (symmetric) |
| Token vault key | Gateway (tokenVault.keyURI, sealer) |
Users' OAuth tokens, bound to realm, user, provider and field | — |
| Capture key | Gateway (captureArgs, sealer) |
DLP-redacted argument captures for replay | — |
| Txn-Token key | Gateway (txnTokens.signingKey, key URI) |
Transaction tokens to upstreams; give the broker its public key with -txn-token-keys |
— |
| A2A card key | Gateway (a2a.cardSigningKey, key URI) |
Signed agent cards | GET /a2a/jwks.json |
| Attest response key | Attestation service (-response-signing-key, Ed25519 PEM) |
Attest and renew responses, so an on-path host cannot substitute a sealed token | Public key to the operator (--attest-response-pubkey-file) |
| Licence signing key | Vendor or service provider, offline (maqpna license issue) |
Licence JWS (EdDSA or ES256) | Embedded trusted key in the binaries |
| Usage report key | Service provider, offline (maqpna usage report) |
Signed usage reports (EdDSA JWS) | Given to the party that verifies |
MAQPNA_BROKER_TOKEN |
Operator, broker, attestation service | Bearer for mint and release calls (not a signing key) | — |
Rotation#
maqpna keys rotate rotates a key without downtime. Every key lives in a Secret mounted as a directory, and the servers also load sibling files next to the active key, so a rotation edits the Secret and restarts the Deployment that reads it:
| Kind | Sibling file | Effect |
|---|---|---|
identity |
previous-<kid>.pem |
Published in the broker JWKS, so tokens signed with the old key verify until they expire |
audit-checkpoint |
retired-<kid>.pub.pem |
Kept in /v1/audit/jwks, so old checkpoints stay verifiable |
vault |
retired-<id> |
Still opens records sealed under it; --drop-old removes it after re-sealing |
attest |
pub.pem with two keys |
Attest agents trust the old and the new response key during the grace period |
maqpna keys rotate identitygenerates a new key and keeps the old one asprevious-<kid>.pem.- It restarts the broker, then waits for gateways to pick up the new JWKS (
--jwks-wait, default 5m30s) or restarts them (--restart-gateway). - After the grace period (
--grace, default 1 hour, longer than the maximum token TTL),--finishremoves the old key. - Progress is recorded in the Secret annotation
maqpna.com/key-rotation(kind,phaseprepared/swapped/done,oldKid,newKid), so an interrupted rotation can be resumed.
What you see#
$ maqpna keys rotate identity -n maqpna-system --dry-run
prints the plan without changing anything. The usage lines (from cmd/maqpna/keys.go):
maqpna keys rotate identity [-n NS] [--grace 1h] [--jwks-wait 5m30s | --restart-gateway] [--no-finish | --finish [--force]]
maqpna keys rotate audit-checkpoint [-n NS] [--secret NAME]
maqpna keys rotate vault [-n NS] [--secret NAME] [--drop-old]
maqpna keys rotate attest [-n NS] --secret NAME [--grace 24h] [--no-finish | --finish [--force]]
See maqpna keys rotate, maqpna keygen and maqpna license issue.