diff --git a/infra/Pulumi.dev.yaml b/infra/Pulumi.dev.yaml index ebb842d..b1bda8a 100644 --- a/infra/Pulumi.dev.yaml +++ b/infra/Pulumi.dev.yaml @@ -1,5 +1,3 @@ encryptionsalt: v1:/EuCFtJK3aQ=:v1:gdT9CSE37lfsFVwI:3Q18OoBOcARW9XQiXzB+U091ve8PxA== config: - cloudflare:apiToken: - secure: v1:SpC3Q/LEQboSQqka:B4QZfgnwvnr4F//aJkpHWj3TQgrZ8J0zbua9VKYAhLjsOerzo4fXzi6OS5U9GIVe2rgSD61rBODMDFFr5VVYMnMgfjby fctc-website-under-construction:accountId: 73c0883bcde89365097896c8390fe751 diff --git a/infra/README.md b/infra/README.md index 2572516..d9fcc46 100644 --- a/infra/README.md +++ b/infra/README.md @@ -16,11 +16,29 @@ pulumi login --local # this keeps Pulumi's state in a local file. see ./notes/ export PULUMI_CONFIG_PASSPHRASE="" pulumi stack init dev # setting the passphrase to an empty string for now. see ./notes/PASSPHRASE.md -pulumi config set cloudflare:apiToken --secret # see ./notes/CLOUDFLARE.md +export CLOUDFLARE_API_TOKEN=... # NOT pulumi config. see ./notes/CLOUDFLARE.md pulumi config set accountId # see ./notes/CLOUDFLARE.md ``` +### The Cloudflare token goes in the environment, not the config + +`pulumi config set --secret` writes the value into `Pulumi.dev.yaml` as ciphertext, and +that file is committed. With the local backend the stack's secrets provider is a +passphrase, and this stack's passphrase is the empty string, documented three lines above. +Committed ciphertext plus a committed salt plus a documented passphrase is not encryption +— it is the token, spelled differently, readable by everyone with access to this repo. + +`pulumi-cloudflare` reads **`CLOUDFLARE_API_TOKEN`** from the environment when +`cloudflare:apiToken` is absent, so nothing in `__main__.py` changes and neither does +`pulumi up`. The token just stops living in git. + +**The empty passphrase is not the problem and does not need fixing.** It protects nothing +because nothing secret is in config any more, which is the arrangement that makes it +harmless. It becomes a problem again the moment someone runs `pulumi config set --secret` +— so if this stack ever does need a secret at rest, change the secrets provider first. +See `./notes/PASSPHRASE.md`. + ### Brind it up ``` pulumi up diff --git a/infra/notes/CLOUDFLARE.md b/infra/notes/CLOUDFLARE.md index 7038ff6..93eeb49 100644 --- a/infra/notes/CLOUDFLARE.md +++ b/infra/notes/CLOUDFLARE.md @@ -28,3 +28,34 @@ https://dash.cloudflare.com/profile/api-tokens 8. Click "Create Token" 9. The token will only be displayed once. + +### Where the token goes + +**Into the environment, never into `pulumi config`.** + +```bash +export CLOUDFLARE_API_TOKEN=... +pulumi up +``` + +`pulumi-cloudflare` falls back to `CLOUDFLARE_API_TOKEN` when `cloudflare:apiToken` is +not set in config, so this needs no change to `__main__.py`. + +Do **not** run `pulumi config set cloudflare:apiToken --secret`. On this stack that writes +ciphertext into the committed `Pulumi.dev.yaml`, next to a committed salt, unlocked by a +passphrase documented in `../README.md`. See `./PASSPHRASE.md`. + +For anything unattended later, put it in a GitHub Actions secret and export it in the job +rather than committing it in any form. + +### If a token has been exposed + +Rotating the value is the only fix — rewriting git history does not help, because anyone +with repo access has already been able to read it for as long as it was pushed. + +1. https://dash.cloudflare.com/profile/api-tokens, signed in as the user who created it + (tokens are user-scoped, so only that user sees them). +2. Check **Last used** before doing anything — deleting the token destroys the only + record of whether it was used. +3. **Roll** to keep the token and issue a new value, or **Delete** and create a fresh one + from the steps above. diff --git a/infra/notes/PASSPHRASE.md b/infra/notes/PASSPHRASE.md index f5af11d..e96f2d9 100644 --- a/infra/notes/PASSPHRASE.md +++ b/infra/notes/PASSPHRASE.md @@ -1,3 +1,16 @@ +## The passphrase + +The `dev` stack uses Pulumi's passphrase secrets provider with an **empty passphrase**, +set in the bootstrap steps in `../README.md`. + +That is fine only while **no secret is stored in stack config**. An empty passphrase plus +the `encryptionsalt` committed in `Pulumi.dev.yaml` means any `secure:` value in that file +is recoverable by anyone who can read this repo. The Cloudflare token therefore lives in +`CLOUDFLARE_API_TOKEN` in the environment instead — see `./CLOUDFLARE.md`. + +**Before putting any secret in stack config, change the secrets provider.** Otherwise the +value is public to everyone with repo access the moment it is committed. + ## Updating the passphrase -pulumi stack change-secrets-provider passphrase \ No newline at end of file +pulumi stack change-secrets-provider passphrase