From 07d75fabdd0dc855e653f5922afa21acf13e9c0f Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 14 Aug 2026 16:58:44 +0200 Subject: [PATCH] feat: Scaleway bucket create script and founder put instructions Read bootstrap creds from OpenBao only. Write non-secret attributes after create. Never print keys. --- docs/put-scaleway-bootstrap.md | 37 +++++++++ tools/create-platform-audit-bucket.sh | 103 ++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 docs/put-scaleway-bootstrap.md create mode 100755 tools/create-platform-audit-bucket.sh diff --git a/docs/put-scaleway-bootstrap.md b/docs/put-scaleway-bootstrap.md new file mode 100644 index 0000000..1175920 --- /dev/null +++ b/docs/put-scaleway-bootstrap.md @@ -0,0 +1,37 @@ +# Put the Scaleway bootstrap key (founder, local only) + +Do this on a trusted terminal. **Do not paste ACCESS_KEY or SECRET_KEY +into chat, Git, or State Hub.** + +CCR: `railiance-platform/credential-change-requests/CCR-2026-0011-scaleway-object-storage-bootstrap.yaml` +Path: `platform/workloads/railiance/scaleway/bootstrap` + +1. In [console.scaleway.com](https://console.scaleway.com) create or reuse + the Railiance (or GmbH) project. IAM → API keys: create a key that can + create Object Storage buckets in that project. Copy org id and project id + from the project dashboard (those two are not as sensitive as the secret + key, but still keep them out of git). +2. On this host, with a token that can write the `platform` mount: + +```bash +bao kv put platform/workloads/railiance/scaleway/bootstrap \ + ACCESS_KEY='SCWxxxxxxxx' \ + SECRET_KEY='xxxxxxxx' \ + DEFAULT_ORGANIZATION_ID='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' \ + DEFAULT_PROJECT_ID='xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' +``` + +3. Confirm **without printing values**: + +```bash +bao kv metadata get platform/workloads/railiance/scaleway/bootstrap +``` + +You should see a current version. Then tell the agent “bootstrap is in +OpenBao.” They will run `tools/create-platform-audit-bucket.sh`, which +creates the private Multi-AZ bucket, 30-day lifecycle, and writes only +endpoint/bucket/region into `substrate/object-stores/platform-audit-storage.yaml`. + +The Barman runtime key is a **different** path +(`platform/workloads/railiance/backup/platform-pg-backup-s3`) and is T04. +After the scoped key works, delete or lock down this bootstrap key. diff --git a/tools/create-platform-audit-bucket.sh b/tools/create-platform-audit-bucket.sh new file mode 100755 index 0000000..50ca36f --- /dev/null +++ b/tools/create-platform-audit-bucket.sh @@ -0,0 +1,103 @@ +#!/usr/bin/env bash +# Create the planned Scaleway backup bucket. Reads bootstrap creds from +# OpenBao. Never prints secret values. Writes only non-secret attributes +# into substrate/object-stores/platform-audit-storage.yaml. +set -euo pipefail + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +ATTR="$ROOT/substrate/object-stores/platform-audit-storage.yaml" +BAO_ADDR="${BAO_ADDR:-${VAULT_ADDR:-https://bao.coulomb.social}}" +BOOTSTRAP_PATH="platform/workloads/railiance/scaleway/bootstrap" +SCOPED_PATH="platform/workloads/railiance/backup/platform-pg-backup-s3" +REGION="nl-ams" +BUCKET="${BUCKET:-railiance-platform-pg-backup}" +PREFIX="${PREFIX:-platform-pg/}" +ENDPOINT="https://s3.nl-ams.scw.cloud" + +need() { command -v "$1" >/dev/null || { echo "missing $1" >&2; exit 2; }; } +need python3 +need curl + +TOKEN="${OPENBAO_TOKEN:-${VAULT_TOKEN:-}}" +if [[ -z "$TOKEN" && -f "$HOME/.vault-token" ]]; then + TOKEN="$(cat "$HOME/.vault-token")" +fi +[[ -n "$TOKEN" ]] || { echo "no OpenBao token" >&2; exit 2; } + +read_kv() { + local path="$1" + curl -fsS -H "X-Vault-Token: $TOKEN" \ + "$BAO_ADDR/v1/platform/data/${path#platform/}" \ + | python3 -c 'import json,sys; d=json.load(sys.stdin); print(json.dumps(d["data"]["data"]))' +} + +if ! BOOTSTRAP_JSON="$(read_kv workloads/railiance/scaleway/bootstrap 2>/dev/null)"; then + echo "Bootstrap secret missing at $BOOTSTRAP_PATH" >&2 + echo "Founder: put ACCESS_KEY SECRET_KEY DEFAULT_ORGANIZATION_ID DEFAULT_PROJECT_ID locally (not in chat)." >&2 + echo "See docs/put-scaleway-bootstrap.md" >&2 + exit 3 +fi + +eval "$(BOOTSTRAP_JSON="$BOOTSTRAP_JSON" python3 - <<'PY' +import json, os, sys +data = json.loads(os.environ["BOOTSTRAP_JSON"]) +missing = [k for k in ("ACCESS_KEY", "SECRET_KEY", "DEFAULT_ORGANIZATION_ID", "DEFAULT_PROJECT_ID") if not (data.get(k) or data.get(k.lower()))] +if missing: + sys.stderr.write("bootstrap fields missing: " + ",".join(missing) + "\n") + sys.exit(4) +def g(k): + return data.get(k) or data.get(k.lower()) +print("export SCW_ACCESS_KEY=" + json.dumps(g("ACCESS_KEY"))) +print("export SCW_SECRET_KEY=" + json.dumps(g("SECRET_KEY"))) +print("export SCW_DEFAULT_ORGANIZATION_ID=" + json.dumps(g("DEFAULT_ORGANIZATION_ID"))) +print("export SCW_DEFAULT_PROJECT_ID=" + json.dumps(g("DEFAULT_PROJECT_ID"))) +PY +)" + +export SCW_DEFAULT_REGION="$REGION" +need scw +need aws + +echo "creating private bucket $BUCKET in $REGION (versioning on)" +scw object bucket create "$BUCKET" region="$REGION" acl=private enable-versioning=true + +echo "applying 30-day lifecycle (current + noncurrent versions)" +aws --endpoint-url "$ENDPOINT" s3api put-bucket-lifecycle-configuration \ + --bucket "$BUCKET" \ + --lifecycle-configuration '{ + "Rules": [ + { + "ID": "retain-30-days", + "Status": "Enabled", + "Filter": {"Prefix": ""}, + "Expiration": {"Days": 30}, + "NoncurrentVersionExpiration": {"NoncurrentDays": 30} + } + ] + }' + +echo "writing non-secret attributes (no keys)" +python3 - <