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
This commit is contained in:
Sergei Poljanski 2026-11-11 03:24:59 +04:00
commit 10c620e796
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
4 changed files with 76 additions and 1 deletions

View file

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

View file

@ -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/<key>`
(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.

View file

@ -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"]

View file

@ -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" <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": ["*"]},
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::${bucket}/*"]
}
]
}
EOF
mc anonymous set-json "/tmp/${bucket}-anonymous.json" "local/${bucket}" >/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" <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": ["s3:PutObject", "s3:DeleteObject"],
"Resource": ["arn:aws:s3:::${bucket}/*"]
}
]
}
EOF
mc admin policy create local "${name}" "/tmp/${name}.json" 2>/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/<key>. 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