Skip to main content
AWS KMS Vault calls GenerateDataKey or Decrypt for the data key only. Secret plaintext never goes to KMS. The AWS SDK is imported inside tooling-vault-aws-kms, and the backend loader imports that package only when provider is aws_kms.

Why

Production keys should live in a CMK with automatic rotation. The app still holds only a wrapped data key plus the AES-GCM ciphertext.

Setup

  1. Add tooling-vault-aws-kms to apps/backend (already a workspace dependency).
  2. Set environment on the deployed service:
The consumer process uses VAULT_KMS_CAPABILITY=decrypt and a principal that can kms:Decrypt only. Omit VAULT_PROVIDER to leave vault disabled. local throws at boot outside development and test.
  1. Key policy: require encryption context org_id, split GenerateDataKey from Decrypt, rotation on. The repo does not provision the key. See Vault.

Local observability

KMS exceptions are stripped of Plaintext, CiphertextBlob, and secret-like strings before they leave the package. Domain metrics: vault.operation tags action and outcome only.

Adding a new command

Do not add Encrypt or Decrypt of the secret itself. New KMS calls stay inside this package and must map failures to the closed VaultError codes.

Gotchas

  • capability_mismatch is raised before the SDK call. access_denied means KMS was called and denied.
  • Encryption context on GenerateDataKey and Decrypt is {org_id, name, secret_id}. A moved blob fails closed.
  • CloudTrail should alarm on Decrypt volume and AccessDenied for this key ARN.

What’s next

Local wrap for development: Local Vault.