Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

41 changes: 20 additions & 21 deletions terraform/cloudflare/b0xp.io/lolice-member-portal/README.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
# lolice member portal Worker

Terraform deploys the Worker script and its non-sensitive bindings. It does
not manage Worker secrets: passing secret values through Terraform would store
them in Terraform state.
Terraform deploys the Worker script and its non-sensitive bindings.
Worker secrets (`CF_API_TOKEN` and `RESEND_API_KEY`) are **not** stored in
Terraform state. Instead, a `null_resource` provisioner reads them from
AWS SSM Parameter Store and sets them directly via the Cloudflare API
after each `terraform apply`.

After the Worker has been created or updated, set the following secrets
manually for the `lolice-member-portal` Worker:
## Secrets source of truth: AWS SSM Parameter Store

- `CF_API_TOKEN` — Cloudflare API token used to update the Access policy
- `RESEND_API_KEY` — Resend API key used to send notification emails
The following SSM parameters must exist before the first `terraform apply`:

## Wrangler CLI
| SSM Parameter | Type | Description |
|---|---|---|
| `/lolice-member-portal/CF_API_TOKEN` | SecureString | Cloudflare API token (Zero Trust: Edit) |
| `/lolice-member-portal/RESEND_API_KEY` | SecureString | Resend API key for email notifications |

Authenticate with an account that can edit the Worker, then run the following
commands from this directory. Each command prompts for the value and does not
write it to the shell history.
The `null_resource` provisioner reads these values with decryption via the
AWS CLI (available in the tfaction runner) and pushes them to the Worker via
the Cloudflare REST API. Secret values never touch Terraform state.

```sh
wrangler secret put CF_API_TOKEN --name lolice-member-portal
wrangler secret put RESEND_API_KEY --name lolice-member-portal
```
## Rotating secrets

## Cloudflare Dashboard
To rotate a secret:
1. Update the value in SSM Parameter Store.
2. Run `terraform apply` — the provisioner re-applies secrets from SSM.

Open **Workers & Pages** → **lolice-member-portal** → **Settings** →
**Variables and Secrets**, then add each value above as an encrypted secret.

Repeat this step whenever either secret is rotated. Do not put secret values in
Terraform variables, `plain_text_binding`, or `.tfvars` files.
> **Warning**: Do NOT update secrets via Wrangler CLI or the Cloudflare
> Dashboard. The next `terraform apply` will overwrite them with the SSM values.
4 changes: 4 additions & 0 deletions terraform/cloudflare/b0xp.io/lolice-member-portal/backend.tf
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,9 @@ terraform {
source = "cloudflare/cloudflare"
version = "~> 4.52"
}
null = {
source = "hashicorp/null"
version = "~> 3.0"
}
}
}
2 changes: 2 additions & 0 deletions terraform/cloudflare/b0xp.io/lolice-member-portal/provider.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
provider "cloudflare" {
# token pulled from $CLOUDFLARE_API_TOKEN
}

provider "null" {}
36 changes: 36 additions & 0 deletions terraform/cloudflare/b0xp.io/lolice-member-portal/worker.tf
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,42 @@ resource "cloudflare_workers_script" "lolice_member_portal" {
}
}

# Set Worker secrets from AWS SSM Parameter Store via Cloudflare API.
# Values are read at apply-time by the local-exec provisioner and pushed
# directly to the Worker — they are never stored in Terraform state.
# Always re-apply secrets after every Terraform apply, because script updates
# can remove undeclared bindings.
resource "null_resource" "worker_secrets" {
triggers = {
always_run = timestamp()
}

provisioner "local-exec" {
interpreter = ["/bin/bash", "-c"]
command = <<-BASH
set -euo pipefail
ACCOUNT_ID="${var.account_id}"
for SECRET in CF_API_TOKEN RESEND_API_KEY; do
VALUE=$(aws ssm get-parameter \
--name "/lolice-member-portal/$$SECRET" \
--with-decryption \
--query Parameter.Value \
--output text \
--region ap-northeast-1)
BODY=$(jq -n --arg name "$$SECRET" --arg text "$$VALUE" '{"name":$name,"text":$text,"type":"secret_text"}')
curl -sf -X PUT \
"https://api.cloudflare.com/client/v4/accounts/$$ACCOUNT_ID/workers/scripts/lolice-member-portal/secrets" \
-H "Authorization: Bearer $$CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
-d "$$BODY"
echo "Set secret $$SECRET"
done
BASH
}

depends_on = [cloudflare_workers_script.lolice_member_portal]
}

resource "cloudflare_worker_route" "lolice_member_portal" {
zone_id = "ec593206d0ef695c3aae3a4cb3173264"
pattern = "lolice.b0xp.io/*"
Expand Down
Loading