diff --git a/.github/workflows/deploy-proxy-latha-org.yml b/.github/workflows/deploy-proxy-latha-org.yml index 8f634b6..31633fa 100644 --- a/.github/workflows/deploy-proxy-latha-org.yml +++ b/.github/workflows/deploy-proxy-latha-org.yml @@ -12,18 +12,18 @@ # CLOUDFLARE_ACCOUNT_ID isn't a secret in the same sense — not stored in # OpenBao, just resolved live from the API token itself (GET /accounts). # -# BUILDBUDDY_API_KEY, TANGLED_WEBHOOK_SECRET and UPLOAD_TOKEN are read -# from OpenBao if already present there, else seeded/generated + persisted -# back (so later runs reuse the same value instead of rotating on every -# deploy — deploy.sh overwrites the live Worker's secret bindings each -# run). BUILDBUDDY_API_KEY isn't randomly generated if missing — it has to -# be the real org key from https://app.buildbuddy.io/ → Settings, so a -# missing copy falls back to the repo secret BUILDBUDDY_API_KEY_FALLBACK -# (never written into any tracked file — same reason .buckconfig.local, -# which holds this same key locally, is gitignored). Only -# TANGLED_WEBHOOK_SECRET has an external dependency (Tangled's own -# Settings → Hooks → Secret config must match), so a freshly generated one -# is printed in the job log as a one-time follow-up instruction. +# TANGLED_WEBHOOK_SECRET / BUILDBUDDY_API_KEY / UPLOAD_TOKEN are +# deliberately *not* provisioned here at all: deploy.sh (see its own +# header comment) now sends `inherit`-type bindings for any of these 3 +# secret_text values it isn't given, which tells Cloudflare to carry the +# live Worker's already-configured values forward unchanged. This +# workflow only ever needs to update worker.js's code, never these +# secrets' values — so it never needs to know them, matching the +# constraint that this key material only ever lives in a human's own +# machine or wherever it was originally deployed from. If any of these +# 3 secrets ever needs to be rotated, that's a separate, deliberate +# `./deploy.sh` run (by a human, with the new value exported locally) — +# not something this workflow does. name: Deploy proxy.latha.org on: @@ -55,49 +55,6 @@ jobs: ./scripts/fetch-openbao-env.sh --github-env --keys \ CLOUDFLARE_API_TOKEN - - name: Read or seed/generate BUILDBUDDY_API_KEY / TANGLED_WEBHOOK_SECRET / UPLOAD_TOKEN - env: - OPENBAO_ADDR: https://openbao.boxd.sh - OPENBAO_TOKEN: ${{ secrets.OPENBAO_TOKEN }} - OPENBAO_SECRET_PATH: secret/data/ai-api-keys - # Repo secret, not a literal value in source — see the header - # comment above for why. Only used as a fallback if OpenBao - # doesn't already have its own BUILDBUDDY_API_KEY. - KNOWN_BUILDBUDDY_API_KEY: ${{ secrets.BUILDBUDDY_API_KEY_FALLBACK }} - run: | - set -euo pipefail - chmod +x scripts/openbao-put-key.sh - for key in BUILDBUDDY_API_KEY TANGLED_WEBHOOK_SECRET UPLOAD_TOKEN; do - if exports="$(./scripts/fetch-openbao-env.sh --export --keys "$key" 2>/dev/null)"; then - eval "$exports" - echo "using existing $key from OpenBao" - else - if [[ "$key" == "BUILDBUDDY_API_KEY" ]]; then - if [[ -z "${KNOWN_BUILDBUDDY_API_KEY:-}" ]]; then - echo "BUILDBUDDY_API_KEY missing from OpenBao and no BUILDBUDDY_API_KEY_FALLBACK repo secret set" >&2 - exit 1 - fi - val="$KNOWN_BUILDBUDDY_API_KEY" - else - val="$(openssl rand -hex 32)" - fi - ./scripts/openbao-put-key.sh "$key" --value "$val" >/dev/null - export "$key=$val" - if [[ "$key" == "TANGLED_WEBHOOK_SECRET" ]]; then - echo "generated + stored a NEW $key in OpenBao — paste this into Tangled's Settings > Hooks > Secret for this repo:" - echo " $val" - else - echo "::add-mask::$val" - echo "seeded/generated + stored a new $key in OpenBao" - fi - fi - { - echo "${key}<>"$GITHUB_ENV" - done - - name: Resolve CLOUDFLARE_ACCOUNT_ID from the API token run: | set -euo pipefail diff --git a/cloudflare/proxy-latha-org/deploy.sh b/cloudflare/proxy-latha-org/deploy.sh index 1ecff1b..3bf137f 100755 --- a/cloudflare/proxy-latha-org/deploy.sh +++ b/cloudflare/proxy-latha-org/deploy.sh @@ -13,16 +13,32 @@ # Account:Workers R2 Storage:Edit + Zone:Edit for # DNS on latha.org) # CLOUDFLARE_ACCOUNT_ID +# +# Optional env (only needed to *set/rotate* a value — see below): # TANGLED_WEBHOOK_SECRET Paste the same value into Tangled's # Settings -> Hooks -> Secret for this repo. # BUILDBUDDY_API_KEY Org key from https://app.buildbuddy.io/ -> Settings # UPLOAD_TOKEN Bearer token the remote build script uses to PUT # artifacts back to this worker. # -# Usage: +# Each of the 3 secret_text bindings above is independently optional on +# every deploy after the first: leaving one unset makes this script send +# an `inherit`-type binding for it instead of `secret_text`, which tells +# Cloudflare to carry the existing bound value forward from the +# currently-live script version unchanged — no need to know/resupply a +# secret's value just to redeploy worker.js's code. `?bindings_inherit=strict` +# on the upload makes this fail loudly (not silently drop the binding) if +# there's no previous version to inherit from — i.e. on a script's very +# first-ever deploy, all 3 of these *are* required. +# +# Usage (rotating/first deploy): # export CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... # export TANGLED_WEBHOOK_SECRET=... BUILDBUDDY_API_KEY=... UPLOAD_TOKEN=... # ./deploy.sh +# +# Usage (code-only redeploy, preserving existing secrets): +# export CLOUDFLARE_API_TOKEN=... CLOUDFLARE_ACCOUNT_ID=... +# ./deploy.sh set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -34,9 +50,9 @@ API="https://api.cloudflare.com/client/v4" : "${CLOUDFLARE_API_TOKEN:?export CLOUDFLARE_API_TOKEN}" : "${CLOUDFLARE_ACCOUNT_ID:?export CLOUDFLARE_ACCOUNT_ID}" -: "${TANGLED_WEBHOOK_SECRET:?export TANGLED_WEBHOOK_SECRET}" -: "${BUILDBUDDY_API_KEY:?export BUILDBUDDY_API_KEY}" -: "${UPLOAD_TOKEN:?export UPLOAD_TOKEN}" +: "${TANGLED_WEBHOOK_SECRET:=}" +: "${BUILDBUDDY_API_KEY:=}" +: "${UPLOAD_TOKEN:=}" auth=(-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN") @@ -49,25 +65,42 @@ curl -fsS "${auth[@]}" -X POST \ # (a 10004 "bucket already exists" error here is fine on re-deploy) echo "--- upload worker script $SCRIPT_NAME" >&2 +# secret_binding NAME VALUE_VAR: emits a real secret_text binding when +# VALUE_VAR is non-empty, else an inherit binding that carries forward +# whatever's already bound to NAME on the live script (see the header +# comment above). +secret_binding() { + local name="$1" val="$2" + if [[ -n "$val" ]]; then + jq -n --arg name "$name" --arg text "$val" '{type: "secret_text", name: $name, text: $text}' + else + jq -n --arg name "$name" '{type: "inherit", name: $name}' + fi +} + metadata="$(jq -n \ --arg main "worker.js" \ --arg bucket "$BUCKET_NAME" \ - --arg webhook_secret "$TANGLED_WEBHOOK_SECRET" \ - --arg bb_key "$BUILDBUDDY_API_KEY" \ - --arg upload_token "$UPLOAD_TOKEN" \ + --argjson webhook_secret_binding "$(secret_binding TANGLED_WEBHOOK_SECRET "$TANGLED_WEBHOOK_SECRET")" \ + --argjson bb_key_binding "$(secret_binding BUILDBUDDY_API_KEY "$BUILDBUDDY_API_KEY")" \ + --argjson upload_token_binding "$(secret_binding UPLOAD_TOKEN "$UPLOAD_TOKEN")" \ '{ main_module: $main, compatibility_date: "2024-09-23", bindings: [ {type: "r2_bucket", name: "ARTIFACTS", bucket_name: $bucket}, - {type: "secret_text", name: "TANGLED_WEBHOOK_SECRET", text: $webhook_secret}, - {type: "secret_text", name: "BUILDBUDDY_API_KEY", text: $bb_key}, - {type: "secret_text", name: "UPLOAD_TOKEN", text: $upload_token} + $webhook_secret_binding, + $bb_key_binding, + $upload_token_binding ] }')" +# bindings_inherit=strict: fail this request (not silently drop the +# binding) if any inherit-type binding above can't be resolved against +# the previous script version — e.g. this script's actual first-ever +# deploy, when there is no previous version to inherit from. curl -fsS "${auth[@]}" -X PUT \ - "$API/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/scripts/$SCRIPT_NAME" \ + "$API/accounts/$CLOUDFLARE_ACCOUNT_ID/workers/scripts/$SCRIPT_NAME?bindings_inherit=strict" \ -F "metadata=$metadata;type=application/json" \ -F "worker.js=@$ROOT/worker.js;type=application/javascript+module" \ | jq -c '{success, errors}'