Compare commits

..
Author SHA1 Message Date
Admin 7e9e90c3ba Use Ping's own credentials on the shared services
Infrastructure now gives each project its own Postgres role, S3 identity
limited to its bucket and a Zitadel service account that owns only its
project, instead of handing out the admin accounts.

- Postgres and S3 settings default to Ping's own (ping).
- zitadel-init mounts ZITADEL_PAT_DIR (Infrastructure's
  zitadel/.output/ping), waits for that PAT and uses it. It no longer
  creates the Zitadel project, which that account can't do:
  Infrastructure does.
2026-09-24 17:05:24 +02:00
4 changed files with 35 additions and 29 deletions
+1 -1
View File
@@ -54,7 +54,7 @@ in place:
```bash ```bash
cp backend/deploy/.env.example backend/deploy/.env cp backend/deploy/.env.example backend/deploy/.env
# edit backend/deploy/.env if your Infrastructure checkout isn't where # edit backend/deploy/.env if your Infrastructure checkout isn't where
# ZITADEL_ADMIN_PAT_DIR assumes, or its S3 keys differ from the defaults # ZITADEL_PAT_DIR assumes, or its PING_* credentials differ from the defaults
docker compose -f backend/deploy/compose.yml -f backend/deploy/compose.dev.yml up -d docker compose -f backend/deploy/compose.yml -f backend/deploy/compose.dev.yml up -d
``` ```
+17 -13
View File
@@ -4,9 +4,11 @@
# cp deploy/.env.example deploy/.env # cp deploy/.env.example deploy/.env
# #
# Requires ../../../Infrastructure running first (Postgres, Zitadel, # Requires ../../../Infrastructure running first (Postgres, Zitadel,
# SeaweedFS). See ../../../Infrastructure/README.md. Everything marked # SeaweedFS). See ../../../Infrastructure/README.md. Ping has its own
# "must match Infrastructure's .env" below has to be the same value as the # credentials on each shared service (role, S3 identity, Zitadel service
# one in that file. # account), provisioned by Infrastructure. Every value marked
# "= Infrastructure's PING_*" below has to be the same as that setting in
# Infrastructure's .env.
DOTNET_ENVIRONMENT=Development DOTNET_ENVIRONMENT=Development
@@ -16,17 +18,18 @@ PUBLIC_API_URL=http://localhost:5211
PUBLIC_AUTH_URL=http://host.docker.internal:8080 PUBLIC_AUTH_URL=http://host.docker.internal:8080
# --- PostgreSQL (Infrastructure's shared instance, Ping's own database) ----- # --- PostgreSQL (Infrastructure's shared instance, Ping's own database) -----
# POSTGRES_USER / POSTGRES_PASSWORD must match Infrastructure's .env. # Ping's own role, owner of ping_db only.
POSTGRES_HOST=host.docker.internal POSTGRES_HOST=host.docker.internal
POSTGRES_USER=postgres POSTGRES_USER=ping
POSTGRES_PASSWORD=postgres # = Infrastructure's PING_DB_PASSWORD
POSTGRES_PASSWORD=ping
POSTGRES_DB=ping_db POSTGRES_DB=ping_db
# --- Zitadel (OIDC, shared instance) ----------------------------------------- # --- Zitadel (OIDC, shared instance) -----------------------------------------
# Path to the admin service-account PAT Infrastructure's Zitadel writes on # Folder with Ping's own Zitadel PAT (a service account that owns the Ping
# init. Adjust if your Infrastructure checkout lives somewhere else relative # project only), written by Infrastructure's `provision`. Adjust if your
# to this one. # Infrastructure checkout lives somewhere else relative to this one.
ZITADEL_ADMIN_PAT_DIR=../../../Infrastructure/zitadel/.output ZITADEL_PAT_DIR=../../../Infrastructure/zitadel/.output/ping
ZITADEL_DEV_MODE=true ZITADEL_DEV_MODE=true
# OIDC clients: the zitadel-init one-shot service (see compose.yml) fills these in # OIDC clients: the zitadel-init one-shot service (see compose.yml) fills these in
@@ -35,9 +38,10 @@ AUTH_CLIENT_ID=
WEB_CLIENT_ID= WEB_CLIENT_ID=
# --- SeaweedFS (S3, shared instance, Ping's own bucket) ---------------------- # --- SeaweedFS (S3, shared instance, Ping's own bucket) ----------------------
# S3_ACCESS_KEY / S3_SECRET_KEY must match Infrastructure's .env. # Ping's own identity, limited to S3_BUCKET.
S3_ACCESS_KEY=admin_access_key # = Infrastructure's PING_S3_ACCESS_KEY / PING_S3_SECRET_KEY
S3_SECRET_KEY=admin_secret_key S3_ACCESS_KEY=ping_access_key
S3_SECRET_KEY=ping_secret_key
S3_BUCKET=ping-storage S3_BUCKET=ping-storage
S3_PUBLIC_URL=http://host.docker.internal:8333 S3_PUBLIC_URL=http://host.docker.internal:8333
S3_USE_HTTP=true S3_USE_HTTP=true
+2 -2
View File
@@ -7,8 +7,8 @@ up first (see its own README for the exact command).
```bash ```bash
cp deploy/.env.example deploy/.env cp deploy/.env.example deploy/.env
# edit deploy/.env if your Infrastructure checkout isn't where # edit deploy/.env if your Infrastructure checkout isn't where
# ZITADEL_ADMIN_PAT_DIR assumes, or if its S3_ACCESS_KEY/SECRET_KEY differ # ZITADEL_PAT_DIR assumes, or if its PING_* credentials differ from the
# from the defaults # defaults
docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
``` ```
+15 -13
View File
@@ -1,7 +1,8 @@
# Ping: base stack, shared by development and production. # Ping: base stack, shared by development and production.
# #
# Zitadel and SeaweedFS are NOT run here: Ping reuses the shared instances in # Zitadel and SeaweedFS are NOT run here: Ping reuses the shared instances in
# ../../../Infrastructure (see .env.example, ZITADEL_ADMIN_PAT_DIR / S3_*). # ../../../Infrastructure (see .env.example, ZITADEL_PAT_DIR / S3_*), with
# Ping's own credentials on each (provisioned there).
# Bring that stack up first. # Bring that stack up first.
# #
# dev docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d # dev docker compose -f deploy/compose.yml -f deploy/compose.dev.yml up -d
@@ -72,7 +73,7 @@ services:
image: alpine:3.20 image: alpine:3.20
container_name: ping-zitadel-init container_name: ping-zitadel-init
volumes: volumes:
- ${ZITADEL_ADMIN_PAT_DIR}:/zitadel-output:ro - ${ZITADEL_PAT_DIR}:/zitadel-output:ro
- ./.env:/workspace/.env - ./.env:/workspace/.env
environment: environment:
PUBLIC_AUTH_URL: ${PUBLIC_AUTH_URL} PUBLIC_AUTH_URL: ${PUBLIC_AUTH_URL}
@@ -99,11 +100,14 @@ services:
fi fi
} }
if [ ! -f /zitadel-output/admin-sa.pat ]; then # Ping's own service account, owner of the Ping project only
echo "admin-sa.pat not found under ZITADEL_ADMIN_PAT_DIR. Is Infrastructure's Zitadel running?" >&2 # (created by Infrastructure's `provision`, see its README).
exit 1 deadline=$$(( $$(date +%s) + 180 ))
fi while [ ! -s /zitadel-output/zitadel.pat ]; do
PAT=$$(tr -d '\r\n' </zitadel-output/admin-sa.pat) [ "$$(date +%s)" -lt "$$deadline" ] || { echo "no zitadel.pat in ZITADEL_PAT_DIR. Is Infrastructure up, and does ZITADEL_PAT_DIR point at its zitadel/.output/ping? Check 'docker logs infra-provision'." >&2; exit 1; }
sleep 3
done
PAT=$$(tr -d '\r\n' </zitadel-output/zitadel.pat)
MGMT="$$PUBLIC_AUTH_URL/management/v1" MGMT="$$PUBLIC_AUTH_URL/management/v1"
echo "==> waiting for Zitadel to answer at $$PUBLIC_AUTH_URL" echo "==> waiting for Zitadel to answer at $$PUBLIC_AUTH_URL"
@@ -124,13 +128,11 @@ services:
echo "==> project" echo "==> project"
PROJECT_NAME=Ping PROJECT_NAME=Ping
# Created by Infrastructure's `provision`: this account can't create
# projects, only manage this one.
PROJECT_ID=$$(api_call POST /projects/_search "$$(name_query "$$PROJECT_NAME")" | jq -r --arg n "$$PROJECT_NAME" '.result // [] | map(select(.name==$$n)) | first | .id // empty') PROJECT_ID=$$(api_call POST /projects/_search "$$(name_query "$$PROJECT_NAME")" | jq -r --arg n "$$PROJECT_NAME" '.result // [] | map(select(.name==$$n)) | first | .id // empty')
if [ -z "$$PROJECT_ID" ]; then [ -n "$$PROJECT_ID" ] || { echo "Zitadel project $$PROJECT_NAME not found. Is ping in Infrastructure's x-projects?" >&2; exit 1; }
PROJECT_ID=$$(api_call POST /projects "$$(printf '{"name":"%s"}' "$$PROJECT_NAME")" | jq -r '.id') echo " found (id $$PROJECT_ID)"
echo " created (id $$PROJECT_ID)"
else
echo " reused (id $$PROJECT_ID)"
fi
find_app() { api_call POST "/projects/$$PROJECT_ID/apps/_search" "$$(name_query "$$1")" | jq -r --arg n "$$1" '.result // [] | map(select(.name==$$n)) | first | .id // empty'; } find_app() { api_call POST "/projects/$$PROJECT_ID/apps/_search" "$$(name_query "$$1")" | jq -r --arg n "$$1" '.result // [] | map(select(.name==$$n)) | first | .id // empty'; }