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>
120 lines
4.4 KiB
YAML
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
|