From b6bcc9f88223ce31c8531a418d9f6ea06805327d Mon Sep 17 00:00:00 2001 From: Sergei Poljanski Date: Mon, 12 Oct 2026 03:47:57 +0400 Subject: [PATCH] Provision read-only credentials for datacenterbingo The public bingo site is moving out of the asxpio container and only needs to read the bingo tables and the bingo/ photos. Give it a role limited to SELECT on those two tables (with read-only transactions by default) and a MinIO user limited to GetObject under asxpio-invoices/bingo/, instead of reusing asxpio's read-write credentials. Both steps are skipped while the BINGO_* secrets are unset, so this deploys safely before they exist. Tables missing on a fresh install are granted on the next deploy. Assisted-by: Claude:opus-5 --- .env.example | 4 ++++ .forgejo/workflows/deploy.yaml | 8 +++++++- SECRETS.md | 18 ++++++++++++++++++ docker-compose.yml | 3 +++ init/minio-bootstrap.sh | 29 +++++++++++++++++++++++++++++ init/postgres-init.sh | 25 +++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 70c4331..957c131 100644 --- a/.env.example +++ b/.env.example @@ -14,3 +14,7 @@ MINIO_ROOT_PASSWORD=change-me-32-chars-min ASXPIO_DB_PASSWORD=change-me ASXPIO_S3_ACCESS_KEY=change-me ASXPIO_S3_SECRET_KEY=change-me +# datacenterbingo: read-only on asxpio's bingo tables and bingo/ photos. +BINGO_DB_PASSWORD=change-me +BINGO_S3_ACCESS_KEY=change-me +BINGO_S3_SECRET_KEY=change-me diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index b3c86a9..5bdb8b8 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -35,11 +35,14 @@ jobs: ASXPIO_DB_PASSWORD: ${{ secrets.ASXPIO_DB_PASSWORD }} ASXPIO_S3_ACCESS_KEY: ${{ secrets.ASXPIO_S3_ACCESS_KEY }} ASXPIO_S3_SECRET_KEY: ${{ secrets.ASXPIO_S3_SECRET_KEY }} + BINGO_DB_PASSWORD: ${{ secrets.BINGO_DB_PASSWORD }} + BINGO_S3_ACCESS_KEY: ${{ secrets.BINGO_S3_ACCESS_KEY }} + BINGO_S3_SECRET_KEY: ${{ secrets.BINGO_S3_SECRET_KEY }} with: host: ${{ secrets.DEPLOY_IP }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_SSH_KEY }} - envs: POSTGRES_PASSWORD,MINIO_ROOT_USER,MINIO_ROOT_PASSWORD,ASXPIO_DB_PASSWORD,ASXPIO_S3_ACCESS_KEY,ASXPIO_S3_SECRET_KEY + envs: POSTGRES_PASSWORD,MINIO_ROOT_USER,MINIO_ROOT_PASSWORD,ASXPIO_DB_PASSWORD,ASXPIO_S3_ACCESS_KEY,ASXPIO_S3_SECRET_KEY,BINGO_DB_PASSWORD,BINGO_S3_ACCESS_KEY,BINGO_S3_SECRET_KEY script_stop: true script: | umask 077 @@ -51,6 +54,9 @@ jobs: ASXPIO_DB_PASSWORD=${ASXPIO_DB_PASSWORD} ASXPIO_S3_ACCESS_KEY=${ASXPIO_S3_ACCESS_KEY} ASXPIO_S3_SECRET_KEY=${ASXPIO_S3_SECRET_KEY} + BINGO_DB_PASSWORD=${BINGO_DB_PASSWORD} + BINGO_S3_ACCESS_KEY=${BINGO_S3_ACCESS_KEY} + BINGO_S3_SECRET_KEY=${BINGO_S3_SECRET_KEY} EOF chmod 600 /opt/storage/.env chmod +x /opt/storage/init/postgres-init.sh /opt/storage/init/minio-bootstrap.sh diff --git a/SECRETS.md b/SECRETS.md index 4f9806a..ea2728f 100644 --- a/SECRETS.md +++ b/SECRETS.md @@ -59,6 +59,24 @@ can connect. | `ASXPIO_S3_ACCESS_KEY` | 20-char alphanumeric, see snippet above. | | `ASXPIO_S3_SECRET_KEY` | 40-char alphanumeric, see snippet above. | +### Per-app credentials provisioned for datacenterbingo + +These are read-only: +- Role `bingo` gets SELECT on `bingo_datacenters` and `bingo_incidents` in + the `asxpio` DB, and `default_transaction_read_only`. +- The MinIO user may only `GetObject` under `asxpio-invoices/bingo/`. + +The same values go on the **datacenterbingo repo**. + +| Secret | Value | +|------------------------|--------------------------------------------------------| +| `BINGO_DB_PASSWORD` | Postgres password for the `bingo` role. `openssl rand -hex 32`. | +| `BINGO_S3_ACCESS_KEY` | 20-char alphanumeric. | +| `BINGO_S3_SECRET_KEY` | 40-char alphanumeric. | + +The table grants need asxpio's migrations to have run. On a fresh install, +deploy storage again after asxpio's first boot. + ## After adding secrets 1. Push to `main`. The Forgejo workflow brings Postgres and MinIO up, then runs diff --git a/docker-compose.yml b/docker-compose.yml index be20d51..d6c6741 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -28,6 +28,7 @@ services: PGUSER: postgres PGPASSWORD: ${POSTGRES_PASSWORD} ASXPIO_DB_PASSWORD: ${ASXPIO_DB_PASSWORD} + BINGO_DB_PASSWORD: ${BINGO_DB_PASSWORD} volumes: - ./init/postgres-init.sh:/init.sh:ro entrypoint: ["/bin/sh", "/init.sh"] @@ -75,6 +76,8 @@ services: MINIO_ROOT_PASSWORD: ${MINIO_ROOT_PASSWORD} ASXPIO_S3_ACCESS_KEY: ${ASXPIO_S3_ACCESS_KEY} ASXPIO_S3_SECRET_KEY: ${ASXPIO_S3_SECRET_KEY} + BINGO_S3_ACCESS_KEY: ${BINGO_S3_ACCESS_KEY} + BINGO_S3_SECRET_KEY: ${BINGO_S3_SECRET_KEY} volumes: - ./init/minio-bootstrap.sh:/bootstrap.sh:ro entrypoint: ["/bin/sh", "/bootstrap.sh"] diff --git a/init/minio-bootstrap.sh b/init/minio-bootstrap.sh index 4cb5516..58dd605 100644 --- a/init/minio-bootstrap.sh +++ b/init/minio-bootstrap.sh @@ -37,6 +37,27 @@ EOF || mc admin policy update local "${name}" "/tmp/${name}.json" } +# Read-only on one prefix of a bucket. +write_ro_prefix_policy() { + name="$1" + bucket="$2" + prefix="$3" + cat > "/tmp/${name}.json" </dev/null \ + || mc admin policy update local "${name}" "/tmp/${name}.json" +} + ensure_user() { access="$1" secret="$2" @@ -53,4 +74,12 @@ ensure_bucket "asxpio-invoices" write_policy "asxpio-invoices-rw" "asxpio-invoices" ensure_user "${ASXPIO_S3_ACCESS_KEY}" "${ASXPIO_S3_SECRET_KEY}" "asxpio-invoices-rw" +# datacenterbingo serves the card photos asxpio's admin uploads under bingo/. +if [ -n "${BINGO_S3_ACCESS_KEY:-}" ] && [ -n "${BINGO_S3_SECRET_KEY:-}" ]; then + write_ro_prefix_policy "asxpio-invoices-bingo-ro" "asxpio-invoices" "bingo/" + ensure_user "${BINGO_S3_ACCESS_KEY}" "${BINGO_S3_SECRET_KEY}" "asxpio-invoices-bingo-ro" +else + echo "minio-init: BINGO_S3_* unset, skipping bingo user" +fi + echo "minio-init: done" diff --git a/init/postgres-init.sh b/init/postgres-init.sh index 733bcf8..007e487 100644 --- a/init/postgres-init.sh +++ b/init/postgres-init.sh @@ -33,4 +33,29 @@ ensure_role "asxpio" "${ASXPIO_DB_PASSWORD}" ensure_db "asxpio" "asxpio" ensure_extension "asxpio" "citext" +# datacenterbingo: read-only on the bingo tables in the asxpio DB (asxpio's +# admin writes them and owns the schema). Tables that don't exist yet (fresh +# install, asxpio not migrated) are granted on the next storage deploy. +if [ -n "${BINGO_DB_PASSWORD:-}" ]; then + ensure_role "bingo" "${BINGO_DB_PASSWORD}" + psql -c "ALTER ROLE bingo SET default_transaction_read_only = on;" + psql -d asxpio -v ON_ERROR_STOP=1 <<'SQL' # heredoc body stays at column 0 +GRANT CONNECT ON DATABASE asxpio TO bingo; +GRANT USAGE ON SCHEMA public TO bingo; +DO $$ +DECLARE t text; +BEGIN + FOREACH t IN ARRAY ARRAY['bingo_datacenters', 'bingo_incidents'] LOOP + IF to_regclass('public.' || t) IS NOT NULL THEN + EXECUTE format('GRANT SELECT ON public.%I TO bingo', t); + ELSE + RAISE NOTICE 'bingo: % missing, not granted yet', t; + END IF; + END LOOP; +END $$; +SQL +else + echo "postgres-init: BINGO_DB_PASSWORD unset, skipping bingo role" +fi + echo "postgres-init: done"