feat: Scaleway bucket create script and founder put instructions

Read bootstrap creds from OpenBao only. Write non-secret attributes
after create. Never print keys.
This commit is contained in:
tegwick 2026-08-14 16:58:44 +02:00
parent fdde22d33d
commit 07d75fabdd
2 changed files with 140 additions and 0 deletions

View file

@ -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.

View file

@ -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 - <<PY
from pathlib import Path
p = Path("$ATTR")
text = p.read_text()
repl = {
"status: planned": "status: active",
"endpoint: null": "endpoint: $ENDPOINT",
"region: nl-ams": "region: $REGION",
"bucket: null": "bucket: $BUCKET",
"prefix: null": "prefix: $PREFIX",
"versioning: null": "versioning: true",
"lifecycle: null": "lifecycle: 30-day current and noncurrent expiration",
"provider_project_ref: null": "provider_project_ref: ${SCW_DEFAULT_PROJECT_ID}",
}
for a,b in repl.items():
text = text.replace(a, b, 1)
p.write_text(text)
print(f"updated {p}")
print(f"endpoint={ENDPOINT} bucket={BUCKET} region={REGION}")
PY
echo "Cost alert: set a project budget in the Scaleway console if scw billing is unavailable."
echo "Scoped Barman key is T04: $SCOPED_PATH (not written here)."
echo "done. Commit the YAML; do not commit any key."