Give every project its own credentials on the shared services
Every project used the Postgres superuser, the RabbitMQ administrator, an admin S3 identity and the Zitadel instance-admin PAT, so any one of them could read or change the others' data. A new one-shot, `provision` (runs on every `up`, idempotent), gives each project in x-projects its own: - Postgres role, owner of its database. No database is open to PUBLIC anymore, so a role can only connect to its own. An existing database and everything in it is handed over to the project's role. - RabbitMQ user, with permissions on its own vhost only. - Zitadel service account, PROJECT_OWNER of its own project only; its PAT is written to zitadel/.output/<project>/ for that project's zitadel-init to mount instead of the admin PAT. SeaweedFS renders one S3 identity per project, limited to its bucket, and refuses to start with a clear message on a malformed or repeated key. The admin accounts stay with this stack. postgres-init is gone: provision creates the databases. *.sh files are checked out with LF so they run in the Linux containers from a Windows checkout.
This commit is contained in:
+71
-21
@@ -22,15 +22,21 @@ x-logging: &default-logging
|
||||
max-size: "10m"
|
||||
max-file: "3"
|
||||
|
||||
# The consuming projects that get their own credentials (see provision/).
|
||||
# Adding one: add it here, give it the <PROJECT>_* settings in both
|
||||
# `provision` and `seaweedfs` below, and their values in the env files.
|
||||
x-projects: &projects musify ping dockermanager
|
||||
|
||||
networks:
|
||||
infra-net:
|
||||
name: infra-net
|
||||
|
||||
services:
|
||||
# --- Database ---------------------------------------------------------
|
||||
# One instance, one database per consuming project (see postgres-init/).
|
||||
# Zitadel provisions its own `zitadel` database itself, from the admin
|
||||
# credentials below, so it does not need an init script.
|
||||
# One instance, one database and one role per consuming project (created
|
||||
# by `provision` below). Zitadel creates its own `zitadel` database and
|
||||
# user itself, from the admin credentials. The superuser is only for this
|
||||
# stack: no project gets it.
|
||||
postgres:
|
||||
image: postgres:17-alpine
|
||||
container_name: infra-postgres
|
||||
@@ -39,7 +45,6 @@ services:
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- ./postgres-init:/docker-entrypoint-initdb.d:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER}"]
|
||||
interval: 10s
|
||||
@@ -94,8 +99,8 @@ services:
|
||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_DISPLAYNAME: ${ZITADEL_ADMIN_DISPLAY_NAME:-ZITADEL Admin}
|
||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORD: ${ZITADEL_ADMIN_PASSWORD}
|
||||
ZITADEL_FIRSTINSTANCE_ORG_HUMAN_PASSWORDCHANGEREQUIRED: "false"
|
||||
# Service account + PAT written on init; each consuming project's own
|
||||
# zitadel-init one-shot uses it to create its own project/apps.
|
||||
# Instance-admin service account + PAT written on init. Only `provision`
|
||||
# uses it, to give each project its own, project-scoped account.
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_USERNAME: zitadel-admin-sa
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_MACHINE_NAME: Admin Service Account
|
||||
ZITADEL_FIRSTINSTANCE_ORG_MACHINE_PAT_EXPIRATIONDATE: "2099-01-01T00:00:00Z"
|
||||
@@ -158,27 +163,31 @@ services:
|
||||
|
||||
# --- Storage --------------------------------------------------------------
|
||||
# S3-compatible object store shared by every project. All-in-one: master +
|
||||
# volume + filer + S3 gateway. The S3 identity file is rendered from the
|
||||
# environment on start so the credentials can never drift from the ones
|
||||
# handed to consuming projects. No bucket is created here; each
|
||||
# consuming project's own seaweedfs-init one-shot creates its own.
|
||||
# volume + filer + S3 gateway. The S3 identities are rendered from the
|
||||
# environment on start (provision/seaweedfs-s3-config.sh): an admin one
|
||||
# for this stack, and one per project limited to that project's bucket.
|
||||
# No bucket is created here; each consuming project's own seaweedfs-init
|
||||
# one-shot creates its own.
|
||||
seaweedfs:
|
||||
image: chrislusf/seaweedfs:4.47
|
||||
container_name: infra-seaweedfs
|
||||
environment:
|
||||
PROJECTS: *projects
|
||||
S3_ACCESS_KEY: ${S3_ACCESS_KEY}
|
||||
S3_SECRET_KEY: ${S3_SECRET_KEY}
|
||||
MUSIFY_S3_ACCESS_KEY: ${MUSIFY_S3_ACCESS_KEY:-}
|
||||
MUSIFY_S3_SECRET_KEY: ${MUSIFY_S3_SECRET_KEY:-}
|
||||
MUSIFY_S3_BUCKET: webapi-storage
|
||||
PING_S3_ACCESS_KEY: ${PING_S3_ACCESS_KEY:-}
|
||||
PING_S3_SECRET_KEY: ${PING_S3_SECRET_KEY:-}
|
||||
PING_S3_BUCKET: ping-storage
|
||||
DOCKERMANAGER_S3_ACCESS_KEY: ${DOCKERMANAGER_S3_ACCESS_KEY:-}
|
||||
DOCKERMANAGER_S3_SECRET_KEY: ${DOCKERMANAGER_S3_SECRET_KEY:-}
|
||||
DOCKERMANAGER_S3_BUCKET: dockermanager
|
||||
entrypoint: ["/bin/sh", "-c"]
|
||||
command:
|
||||
- |
|
||||
cat > /tmp/s3.conf <<'JSON'
|
||||
{
|
||||
"identities": [
|
||||
{
|
||||
"name": "infra",
|
||||
"credentials": [{ "accessKey": "${S3_ACCESS_KEY}", "secretKey": "${S3_SECRET_KEY}" }],
|
||||
"actions": ["Admin", "Read", "Write"]
|
||||
}
|
||||
]
|
||||
}
|
||||
JSON
|
||||
sh /provision/seaweedfs-s3-config.sh >/tmp/s3.conf || exit 1
|
||||
exec weed server \
|
||||
-ip.bind=0.0.0.0 \
|
||||
-dir=/data \
|
||||
@@ -189,6 +198,7 @@ services:
|
||||
-s3.config=/tmp/s3.conf
|
||||
volumes:
|
||||
- seaweedfs_data:/data
|
||||
- ./provision:/provision:ro
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:9333/cluster/healthz"]
|
||||
interval: 10s
|
||||
@@ -243,6 +253,46 @@ services:
|
||||
logging: *default-logging
|
||||
restart: unless-stopped
|
||||
|
||||
# --- Per-project credentials ----------------------------------------------
|
||||
# One-shot, runs on every `up` (idempotent): each project's own Postgres
|
||||
# role/database, RabbitMQ user/vhost and Zitadel service account, from the
|
||||
# <PROJECT>_* settings (see provision/provision.sh). An empty setting skips
|
||||
# that service for that project.
|
||||
provision:
|
||||
image: postgres:17-alpine
|
||||
container_name: infra-provision
|
||||
entrypoint: ["/bin/sh", "/provision/provision.sh"]
|
||||
environment:
|
||||
PROJECTS: *projects
|
||||
POSTGRES_USER: ${POSTGRES_USER}
|
||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||
RABBITMQ_DEFAULT_USER: ${RABBITMQ_DEFAULT_USER}
|
||||
RABBITMQ_DEFAULT_PASS: ${RABBITMQ_DEFAULT_PASS}
|
||||
ZITADEL_EXTERNAL_DOMAIN: ${ZITADEL_EXTERNAL_DOMAIN}
|
||||
ZITADEL_PUBLIC_SCHEME: ${ZITADEL_PUBLIC_SCHEME}
|
||||
MUSIFY_DB_PASSWORD: ${MUSIFY_DB_PASSWORD:-}
|
||||
MUSIFY_RABBITMQ_PASSWORD: ${MUSIFY_RABBITMQ_PASSWORD:-}
|
||||
MUSIFY_ZITADEL_PROJECT: Musify
|
||||
PING_DB_PASSWORD: ${PING_DB_PASSWORD:-}
|
||||
PING_RABBITMQ_PASSWORD: ${PING_RABBITMQ_PASSWORD:-}
|
||||
PING_ZITADEL_PROJECT: Ping
|
||||
DOCKERMANAGER_DB_PASSWORD: ${DOCKERMANAGER_DB_PASSWORD:-}
|
||||
DOCKERMANAGER_RABBITMQ_PASSWORD: ${DOCKERMANAGER_RABBITMQ_PASSWORD:-}
|
||||
DOCKERMANAGER_ZITADEL_PROJECT: ""
|
||||
volumes:
|
||||
- ./provision:/provision:ro
|
||||
- ./zitadel/.output:/output
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
rabbitmq:
|
||||
condition: service_healthy
|
||||
zitadel:
|
||||
condition: service_healthy
|
||||
networks: [infra-net]
|
||||
logging: *default-logging
|
||||
restart: "no"
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
zitadel_bootstrap:
|
||||
|
||||
Reference in New Issue
Block a user