From 10c620e796b8fa1b177a3640e15b31d8f2685b55 Mon Sep 17 00:00:00 2001 From: Sergei Poljanski Date: Wed, 11 Nov 2026 03:24:59 +0400 Subject: [PATCH] minio-init: add a public bucket with a put-only uploader There was no way to share a file by link: both buckets are private and the bootstrap forces anonymous access off on every run. Add a bucket named public whose objects anyone can GET by key, with no anonymous listing or writes, and an optional user that may only put and delete there, so a leaked uploader key reads nothing and reaches no other bucket. Assisted-by: Claude:fable-5-2 --- .forgejo/workflows/deploy.yaml | 6 +++- SECRETS.md | 16 ++++++++++ docker-compose.yml | 2 ++ init/minio-bootstrap.sh | 53 ++++++++++++++++++++++++++++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) diff --git a/.forgejo/workflows/deploy.yaml b/.forgejo/workflows/deploy.yaml index 5bdb8b8..58c7cb3 100644 --- a/.forgejo/workflows/deploy.yaml +++ b/.forgejo/workflows/deploy.yaml @@ -38,11 +38,13 @@ jobs: 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 }} + PUBLIC_S3_ACCESS_KEY: ${{ secrets.PUBLIC_S3_ACCESS_KEY }} + PUBLIC_S3_SECRET_KEY: ${{ secrets.PUBLIC_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,BINGO_DB_PASSWORD,BINGO_S3_ACCESS_KEY,BINGO_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,PUBLIC_S3_ACCESS_KEY,PUBLIC_S3_SECRET_KEY script_stop: true script: | umask 077 @@ -57,6 +59,8 @@ jobs: BINGO_DB_PASSWORD=${BINGO_DB_PASSWORD} BINGO_S3_ACCESS_KEY=${BINGO_S3_ACCESS_KEY} BINGO_S3_SECRET_KEY=${BINGO_S3_SECRET_KEY} + PUBLIC_S3_ACCESS_KEY=${PUBLIC_S3_ACCESS_KEY} + PUBLIC_S3_SECRET_KEY=${PUBLIC_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 cc885f0..72cc270 100644 --- a/SECRETS.md +++ b/SECRETS.md @@ -75,6 +75,22 @@ The same values go on the **datacenterbingo repo**. | `BINGO_S3_ACCESS_KEY` | 20-char alphanumeric. | | `BINGO_S3_SECRET_KEY` | 40-char alphanumeric. | +### Uploader for the `public` bucket + +Objects in `public` are readable by anyone at `https://s3.asxp.io/public/` +(no anonymous listing). This user may only put and delete there. Optional: +without the secrets the bucket exists and only root can upload. + +| Secret | Value | +|-------------------------|-----------------------| +| `PUBLIC_S3_ACCESS_KEY` | 20-char alphanumeric. | +| `PUBLIC_S3_SECRET_KEY` | 40-char alphanumeric. | + +```sh +aws --endpoint-url https://s3.asxp.io --profile asxp-public \ + s3 cp shot.png s3://public/shot.png +``` + The table grants need asxpio's migrations to have run. On a fresh install, deploy storage again after asxpio's first boot. diff --git a/docker-compose.yml b/docker-compose.yml index d6c6741..6aed8a7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -78,6 +78,8 @@ services: ASXPIO_S3_SECRET_KEY: ${ASXPIO_S3_SECRET_KEY} BINGO_S3_ACCESS_KEY: ${BINGO_S3_ACCESS_KEY} BINGO_S3_SECRET_KEY: ${BINGO_S3_SECRET_KEY} + PUBLIC_S3_ACCESS_KEY: ${PUBLIC_S3_ACCESS_KEY} + PUBLIC_S3_SECRET_KEY: ${PUBLIC_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 7062542..893c720 100644 --- a/init/minio-bootstrap.sh +++ b/init/minio-bootstrap.sh @@ -58,6 +58,48 @@ EOF || mc admin policy update local "${name}" "/tmp/${name}.json" } +# World-readable by object key; no anonymous listing or writes. +ensure_public_bucket() { + bucket="$1" + if ! mc ls "local/${bucket}" >/dev/null 2>&1; then + mc mb "local/${bucket}" + fi + cat > "/tmp/${bucket}-anonymous.json" </dev/null +} + +# Put and delete only: the holder can publish and retract, not read or list. +write_upload_policy() { + name="$1" + bucket="$2" + cat > "/tmp/${name}.json" </dev/null \ + || mc admin policy update local "${name}" "/tmp/${name}.json" +} + # $3: one or more policy names, space-separated. ensure_user() { access="$1" @@ -89,6 +131,17 @@ else echo "minio-init: BINGO_S3_* unset, skipping bingo user" fi +# Files shared by link: https://s3.asxp.io/public/. Everything in this +# bucket is readable by anyone who knows the key. +ensure_public_bucket "public" +write_upload_policy "public-upload" "public" + +if [ -n "${PUBLIC_S3_ACCESS_KEY:-}" ] && [ -n "${PUBLIC_S3_SECRET_KEY:-}" ]; then + ensure_user "${PUBLIC_S3_ACCESS_KEY}" "${PUBLIC_S3_SECRET_KEY}" "public-upload" +else + echo "minio-init: PUBLIC_S3_* unset, skipping public uploader" +fi + # Leftover from before the photos had their own bucket. mc admin policy remove local "asxpio-invoices-bingo-ro" >/dev/null 2>&1 || true