Files
Nasim SadatandClaude Opus 5.5 78a498f1ce Sign-in by customer: realm petrolyst, row rules, guest role (PD-21)
Keycloak import for realm petrolyst: two clients, petrolyst with MFA for
Superset and Traefik, petrolyst-app password only. It lists only
app.petrolyst.com and traefik.petrolyst.com, and no customer names, as this
repository is public. public.v_operator shows each customer's group. Dashboards
overview and site, role Petrolyst Guest, filtered by row. Superset refuses a
user with no mapped role. The realm travels with the release: each release's
compose files name their realm and secret file, and a rollback needs only its
target's secret. Not deployed.

Checks earlier on this content: tests/run.sh 109 of 109 and read-only, dashboards
import twice, column drift 0, compose config on old and new trees, the secret
check on a scratch folder, a rehearsal on a throwaway Keycloak 26.4. Never a real
deploy or rollback; shellcheck not installed today, clean when last run. Codex
inspected once; its fixes are not re-inspected, on the owner's word.

Bands: persistence, Superset. Rules: 1, 2, 3, 6, 14.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 18:31:14 +01:00

120 lines
4.4 KiB
YAML

# Edge proxy for the whole server. Deployed first by deploy/deploy.sh, which
# exports TRAEFIK_CONFIG_HASH from the release's ARTIFACTS file.
#
# Any stack joins by attaching a service to the external network "proxy" and
# setting traefik.* labels; apps without their own login can add the "sso"
# middleware (Keycloak client petrolyst, role checked by oauth2-proxy).
name: traefik
services:
socket-proxy:
image: tecnativa/docker-socket-proxy:v0.5.0
environment:
CONTAINERS: "1"
EVENTS: "1"
PING: "1"
VERSION: "1"
POST: "0"
LOG_LEVEL: warning
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
networks:
- socket
restart: unless-stopped
mem_limit: 64m
traefik:
image: traefik:v3.7.16
command: ["--configFile=/etc/traefik/static/traefik.yml"]
ports:
- "80:80"
- "443:443"
# Folders, not single files: promotion replaces files by renaming, which a
# single-file bind mount would not follow.
volumes:
- ./static:/etc/traefik/static:ro
- ./dynamic:/etc/traefik/dynamic:ro
- letsencrypt:/letsencrypt
depends_on:
- socket-proxy
networks:
- proxy
- socket
restart: unless-stopped
mem_limit: 256m
healthcheck:
test: ["CMD", "traefik", "healthcheck", "--configFile=/etc/traefik/static/traefik.yml"]
interval: 10s
timeout: 5s
retries: 6
labels:
petrolyst.config-hash: ${TRAEFIK_CONFIG_HASH:?TRAEFIK_CONFIG_HASH unset - run through deploy/deploy.sh}
traefik.enable: "true"
traefik.http.routers.dashboard.rule: Host(`${TRAEFIK_HOST:-traefik.petrolyst.com}`)
traefik.http.routers.dashboard.entrypoints: websecure
traefik.http.routers.dashboard.tls.certresolver: le
traefik.http.routers.dashboard.service: api@internal
# secure-headers first: sso answers unauthenticated requests itself, and
# middlewares after it never see that response.
traefik.http.routers.dashboard.middlewares: secure-headers@file,sso
# Keycloak gate for the dashboard host only (cookie, return hosts and
# role are tied to it; README "Adding a project"): unauthenticated
# requests are sent to Keycloak, users without petrolyst:traefik-admin
# get 403.
traefik.http.middlewares.sso.forwardauth.address: http://oauth2-proxy:4180/
traefik.http.middlewares.sso.forwardauth.trustForwardHeader: "true"
traefik.http.middlewares.sso.forwardauth.authResponseHeaders: X-Auth-Request-User,X-Auth-Request-Email
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.5
command:
- --provider=keycloak-oidc
- --oidc-issuer-url=${KEYCLOAK_REALM_URL:-https://iam.thedigitalbridge.io/realms/petrolyst}
- --client-id=petrolyst
- --client-secret-file=/run/secrets/keycloak_petrolyst_client_secret
- --cookie-secret-file=/run/secrets/oauth2_proxy_cookie_secret
- --code-challenge-method=S256
- --allowed-role=petrolyst:traefik-admin
# Required by oauth2-proxy at startup; access is decided by the role above.
- --email-domain=*
- --redirect-url=https://${TRAEFIK_HOST:-traefik.petrolyst.com}/oauth2/callback
- --whitelist-domain=${TRAEFIK_HOST:-traefik.petrolyst.com}
- --cookie-domain=${TRAEFIK_HOST:-traefik.petrolyst.com}
- --cookie-secure=true
- --cookie-samesite=lax
- --reverse-proxy=true
- --upstream=static://202
- --skip-provider-button=true
- --set-xauthrequest=true
- --http-address=0.0.0.0:4180
secrets:
- keycloak_petrolyst_client_secret
- oauth2_proxy_cookie_secret
networks:
- proxy
restart: unless-stopped
mem_limit: 128m
labels:
traefik.enable: "true"
traefik.docker.network: proxy
traefik.http.routers.oauth2.rule: Host(`${TRAEFIK_HOST:-traefik.petrolyst.com}`) && PathPrefix(`/oauth2/`)
traefik.http.routers.oauth2.entrypoints: websecure
traefik.http.routers.oauth2.tls.certresolver: le
traefik.http.routers.oauth2.middlewares: secure-headers@file
traefik.http.services.oauth2.loadbalancer.server.port: "4180"
networks:
proxy:
external: true
socket:
internal: true
volumes:
letsencrypt:
secrets:
keycloak_petrolyst_client_secret:
file: ${PETROLYST_ROOT:-/opt/petrolyst}/secrets/shared/keycloak_petrolyst_realm_client_secret
oauth2_proxy_cookie_secret:
file: ${PETROLYST_ROOT:-/opt/petrolyst}/secrets/traefik/oauth2_proxy_cookie_secret