Pulumi

Pulumi's hashivault:// secrets provider wraps a stack's data key under a Vault Transit key. Point it at an edgekms tenant and the stack's secrets are encrypted with a key that lives in edgekms, with no passphrase to share and no Pulumi Cloud account needed. Pulumi decrypts the data key once per command, so a preview or an up costs one decrypt.

1. A key and a policy

The default config already has a key, default, and your owner role can use it, which is enough to try this. For a real stack, give it its own key and a policy that can encrypt and decrypt with it (edgekms tenant config get, edit, edgekms tenant config set, or the console's Keys and Policies tabs):

{
  "keys": {
    "pulumi-infra": { "id": "k_9d41c07a2e6b3f58", "latest_version": 1, "min_decryption_version": 1 }
  },
  "policies": {
    "pulumi-infra": { "keys": { "pulumi-infra": ["encrypt", "decrypt"] } }
  }
}

Then grant the policy to whoever runs Pulumi: your own role for a laptop, and a GitHub Actions role for CI.

2. Log in

Pulumi reads VAULT_ADDR and VAULT_TOKEN like any Vault client. From a terminal:

export VAULT_ADDR=https://acme.edgekms.dev
export EDGEKMS_GITHUB_CLIENT_ID="$(curl -fsS https://api.edgekms.dev/v1/info | jq -r .github_client_id)"
export VAULT_TOKEN="$(edgekms login github --addr "$VAULT_ADDR" --role owner)"

In a workflow, the GitHub Actions login step sets both.

3. Use the key for a stack

A new stack:

pulumi stack init prod --secrets-provider hashivault://pulumi-infra

An existing stack, moved off its passphrase or Pulumi Cloud encryption:

pulumi stack change-secrets-provider hashivault://pulumi-infra

The stack file then holds secretsprovider: hashivault://pulumi-infra and an encryptedkey, the data key as a vault:v1: ciphertext (base64-encoded by Pulumi). Secrets you set with pulumi config set --secret are encrypted under that data key, as before. Commit the stack file; the ciphertext is safe to keep in git, and nothing decrypts it without a token that can use the key.

Rotation and leaving

Rotating pulumi-infra (the console's Rotate, or raising latest_version) changes nothing for existing stacks: their data key stays decryptable under its old version until you raise the key's min_decryption_version past it. The stack keeps working through an export, too: with the tenant's root key and config, edgekms decrypt unwraps the data key offline, and edgekms export hands the key version to OpenBao's BYOK import, so the same stack file works against an OpenBao Transit engine. The security model has the details.