Vault Transit on Cloudflare Workers

Keys for Pulumi, SOPS and OpenBao, with no secret stored in CI.

edgekms answers the part of Vault's API that encryption clients use. A CI job logs in with GitHub's OIDC token, a person with an SSH key or a GitHub account, and each gets a short-lived token for exactly the keys its role allows. Your clients only need VAULT_ADDR.

Request an invite Read the quickstart

edgekms.dev is invite-only for now. Already in? Sign in to the console.

Works with what you already run

From sign-in to an encrypted stack

Your first sign-in gives you a tenant, https://<you>.edgekms.dev, with a key called default and a role for your GitHub account. Log in to it with GitHub's device flow, and a Pulumi stack's secrets can be wrapped under that key:

export VAULT_ADDR=https://you.edgekms.dev
export VAULT_TOKEN="$(edgekms login github --addr "$VAULT_ADDR" --role owner \
  --client-id "$(curl -fsS https://api.edgekms.dev/v1/info | jq -r .github_client_id)")"
pulumi stack init prod --secrets-provider hashivault://default

In CI the same token comes from the job's OIDC token instead, so nothing long-lived is stored in the repository. The GitHub Actions guide shows the step.

You can always leave

An owner can export a tenant's root key whenever they like, even from a suspended tenant. With it and the tenant's config, the open edgekms CLI decrypts everything offline, hands any key version to OpenBao's or Vault's BYOK import, or a self-hosted edgekms Worker in your own Cloudflare account serves the same keys. Every ciphertext and token stays valid.

How it keeps keys

Each tenant has its own root key, stored only wrapped under a master key in Cloudflare Secrets Store and used as a non-extractable WebCrypto key. Keys are derived from it, never stored. Every operation is audited before it returns, and plaintext, ciphertext and tokens are never logged. How it works is the overview, and the security model the detail, threat model included.