--- id: reef-storage-scaleway-bootstrap demand_source: resource-control/workplans/RESOURCE-WP-0002-T03 consumer_repo: reef-storage credential_type: openbao-kv-founder-provision status: built approved_by: "Bernd Worsch" approved_at: "2026-08-14" created: "2026-08-14" updated: "2026-08-14" --- # Construction plan: Scaleway bootstrap key for reef-storage ## 1. Demand We need to create a private Scaleway Multi-AZ bucket in `nl-ams` for postgres backup (`RESOURCE-WP-0002`). The founder has a new Scaleway account and an API key in the usual Terraform snippet shape (`access_key`, `secret_key`, `organization_id`, `project_id` in `scaleway.auto.tfvars`). Those four values must live in OpenBao, not in git or chat. ops-mason builds the empty lane. The founder pastes the values once (desk or local tfvars ingest). reef-storage then creates the bucket by reading OpenBao and never printing the keys. ## 2. Existing-structure survey Checked 2026-08-14: - **KV path does not exist.** `platform/metadata/workloads/railiance/scaleway/bootstrap` is 404. Same for the later backup path `platform/workloads/railiance/backup/object-storage`. - **CCR already reserved the path.** `railiance-platform` `CCR-2026-0011` names `platform/workloads/railiance/scaleway/bootstrap` and the four fields. Reuse that path. Do not invent a second one. - **CCR-2026-0004** is Nextcloud/age offsite backup (`platform/workloads/railiance/backup/offsite-lane`). Different provider, different fields. Do not merge. - **Catalog.** No `scaleway` / `s3-backup` lane. `openbao-api-key` is the generic template and must not become this concrete key. - **Auth.** This is an operator-workstation bootstrap key for a one-shot (or rare) `scw` create, not an in-cluster ESO consumer. No AppRole. Founder paste-once / local tfvars ingest is the right desk. The later scoped Barman key (T04) is a different path and may get a workload CCR then. - **Does an existing lane already satisfy this?** No live secret. The reserved CCR path is the one to build. Terraform snippet → OpenBao fields (same four facts, scw-native names): | Terraform (`scaleway.auto.tfvars`) | OpenBao field | | --- | --- | | `access_key` | `ACCESS_KEY` | | `secret_key` | `SECRET_KEY` | | `organization_id` | `DEFAULT_ORGANIZATION_ID` | | `project_id` | `DEFAULT_PROJECT_ID` | ## 3. Proposed changes | # | Action | Object | Reuse-vs-new rationale | |---|---|---|---| | 1 | reuse | CCR-2026-0011 path `platform/workloads/railiance/scaleway/bootstrap` | Already reserved; do not fork | | 2 | create | empty KV v2 path (metadata only, no secret data) | Structure so paste-once / ingest has a home | | 3 | create | policy `operator-kv-scaleway-bootstrap` (read/write that path only) | Founder desk and the bucket-create script; no sibling paths | | 4 | reuse | founder paste-once desk + optional local tfvars ingest | ops-mason never sees values; four fields, four desk pastes or one founder-run ingest | | 5 | propose | catalog `scaleway-bootstrap` (draft) + playbook | Pointer only; `warden_executes: false` | | 6 | defer | AppRole / ESO / scoped backup key | T04 / CCR for `backup/object-storage`; not this plan | No new rail. Scaleway operates S3. ## 4. Review notes - **Naming:** path follows `platform/workloads/railiance/…`. Policy `operator-kv-scaleway-bootstrap` matches “operator workstation, not workload ESO.” - **TTL/scoping:** no AppRole token. The *downstream* Scaleway key is long-lived until the founder deletes it after the scoped key exists. - **Redundancy:** does not reuse Nextcloud offsite-lane. Does not put org-admin keys in the Barman runtime path. - **Compaction:** none. This is the first Scaleway lane. - **Secret values:** mason does not read, write, or log them. Founder uses `warden desk --act paste_once_provision` four times, or `reef-storage/tools/ingest-scaleway-tfvars.sh` on a local 0600 `scaleway.auto.tfvars` they already have. - **Founder remaining acts:** approve this plan; paste four fields (or run ingest once); say “bootstrap is in OpenBao.” ## 5. Executive summary **One-line ask:** approve an OpenBao home for the four Scaleway Terraform variables so we can create the backup bucket without putting keys in git or chat. **Who gets access:** you (founder / operator token) to write the four fields; the bucket-create script, using that same operator token, to read them once. No new AppRole. No cluster workload. Nothing else gains a path. **To what:** only `platform/workloads/railiance/scaleway/bootstrap` (`ACCESS_KEY`, `SECRET_KEY`, `DEFAULT_ORGANIZATION_ID`, `DEFAULT_PROJECT_ID`). Not the later Barman key path. Not other `platform/workloads/railiance/backup/*` secrets. **For how long:** the KV version stays until you rotate or delete it. This bootstrap key should be revoked at Scaleway after the scoped bucket key works (T04). **Blast radius if the credential leaks:** create/list/delete Object Storage (and whatever else that API key can do in the project). Revoke the key in the Scaleway console, delete the KV version, rotate to a scoped bucket-only key. **Cost to reverse:** delete the KV path, draft catalog entry, and policy. No other consumer is bound. The bucket, once created, stays until you empty and delete it. **Decision:** approve, reject, or send back to phase 1. Approval authorizes structure (empty path, policy, catalog draft) and your paste-once / tfvars ingest. It does not authorize ops-mason to handle the Terraform secret values. ## 6. Build result Executed 2026-08-14 after founder asked to bootstrap with example data and replace values in the OpenBao UI. Created (structure + placeholders only; values are the string `xxx`): - policy `operator-kv-scaleway-bootstrap` (read/write that one path) - KV v2 `platform/workloads/railiance/scaleway/bootstrap` version 1 fields `ACCESS_KEY`, `SECRET_KEY`, `DEFAULT_ORGANIZATION_ID`, `DEFAULT_PROJECT_ID` - custom_metadata `placeholder=true`, `ccr=CCR-2026-0011`, `replace_in_ui=true` Catalog draft `scaleway-bootstrap` already proposed. **Founder next:** OpenBao UI → secret `platform/workloads/railiance/scaleway/bootstrap` → create version 2 with the real Terraform values. Then say “bootstrap is in OpenBao.” The bucket-create script refuses `xxx` placeholders.