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:
parent
fdde22d33d
commit
07d75fabdd
2 changed files with 140 additions and 0 deletions
37
docs/put-scaleway-bootstrap.md
Normal file
37
docs/put-scaleway-bootstrap.md
Normal 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.
|
||||
103
tools/create-platform-audit-bucket.sh
Executable file
103
tools/create-platform-audit-bucket.sh
Executable 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."
|
||||
Loading…
Add table
Add a link
Reference in a new issue