Episode 15: Pocket ID as identity provider, tinyauth gate, offboarding script
Pocket ID replaces Forgejo as the OIDC provider for Outline, Vaultwarden and NetBird; Forgejo logs in through it. Uptime Kuma sits behind tinyauth via Caddy forward_auth (group ops), the public status page stays public. offboarding/offboard.py switches one person off in every service with an API and prints what a human still has to do: the shared passwords they could read. Measured: SSO lock alone left 10 of 13 ways open for 45 minutes; the script leaves 1 of 14, the vault copy on their laptop. HARDENING.md: the ufw hairpin that broke Vaultwarden SSO and Kuma, the mesh route into the docker bridges, and a fail2ban exception for Pocket ID.
This commit is contained in:
parent
3edef38ba3
commit
12542ecb19
9 changed files with 490 additions and 21 deletions
90
compose.yml
90
compose.yml
|
|
@ -3,8 +3,10 @@
|
|||
#
|
||||
# One server. One file. Your whole company stack:
|
||||
# caddy reverse proxy + automatic HTTPS
|
||||
# forgejo git hosting + CI (also acts as SSO/OIDC provider)
|
||||
# outline team wiki (logs in via Forgejo SSO)
|
||||
# forgejo git hosting + CI (was the SSO/OIDC provider until episode 15)
|
||||
# outline team wiki (logs in via Pocket ID)
|
||||
# pocket-id identity provider, passkeys + groups (episode 15)
|
||||
# tinyauth login gate for services without SSO (episode 15)
|
||||
# vaultwarden password manager (Bitwarden-compatible)
|
||||
# uptime-kuma monitoring + alerts
|
||||
# ntfy push notifications for those alerts (episode 6)
|
||||
|
|
@ -51,6 +53,8 @@ volumes:
|
|||
ntfy_data:
|
||||
ntfy_cache:
|
||||
n8n_data:
|
||||
pocket_id_data:
|
||||
tinyauth_data:
|
||||
wg_data:
|
||||
docker_certs:
|
||||
dind_data:
|
||||
|
|
@ -96,6 +100,12 @@ services:
|
|||
# the container name has to be listed. "external" keeps ordinary public
|
||||
# webhook targets working (episode 8).
|
||||
FORGEJO__webhook__ALLOWED_HOST_LIST: "external,n8n"
|
||||
# Episode 15: people log in with Pocket ID (auth source "PocketID", added
|
||||
# once with: forgejo admin auth add-oauth ...). An SSO login is linked to
|
||||
# the existing account with the same mail address instead of a duplicate.
|
||||
FORGEJO__oauth2_client__ENABLE_AUTO_REGISTRATION: "true"
|
||||
FORGEJO__oauth2_client__ACCOUNT_LINKING: auto
|
||||
FORGEJO__oauth2_client__USERNAME: nickname
|
||||
ports:
|
||||
- "2222:22" # git over SSH
|
||||
volumes:
|
||||
|
|
@ -133,14 +143,14 @@ services:
|
|||
FILE_STORAGE: local
|
||||
FILE_STORAGE_LOCAL_ROOT_DIR: /var/lib/outline/data
|
||||
FORCE_HTTPS: "false" # TLS terminates at caddy
|
||||
# --- SSO via your own Forgejo (set up the OAuth2 app first, see README) ---
|
||||
# --- SSO via Pocket ID (episode 15; Forgejo was the provider until then) ---
|
||||
OIDC_CLIENT_ID: ${OUTLINE_OIDC_CLIENT_ID}
|
||||
OIDC_CLIENT_SECRET: ${OUTLINE_OIDC_CLIENT_SECRET}
|
||||
OIDC_AUTH_URI: https://git.${DOMAIN}/login/oauth/authorize
|
||||
OIDC_TOKEN_URI: https://git.${DOMAIN}/login/oauth/access_token
|
||||
OIDC_USERINFO_URI: https://git.${DOMAIN}/login/oauth/userinfo
|
||||
OIDC_LOGOUT_URI: https://git.${DOMAIN}/logout
|
||||
OIDC_DISPLAY_NAME: Forgejo
|
||||
OIDC_AUTH_URI: https://id.${DOMAIN}/authorize
|
||||
OIDC_TOKEN_URI: https://id.${DOMAIN}/api/oidc/token
|
||||
OIDC_USERINFO_URI: https://id.${DOMAIN}/api/oidc/userinfo
|
||||
OIDC_LOGOUT_URI: https://id.${DOMAIN}/api/oidc/end-session
|
||||
OIDC_DISPLAY_NAME: Pocket ID
|
||||
OIDC_SCOPES: openid profile email
|
||||
volumes:
|
||||
- outline_data:/var/lib/outline/data
|
||||
|
|
@ -153,20 +163,22 @@ services:
|
|||
DOMAIN: https://vault.${DOMAIN}
|
||||
ADMIN_TOKEN: ${VAULTWARDEN_ADMIN_TOKEN}
|
||||
SIGNUPS_ALLOWED: "${VAULTWARDEN_SIGNUPS_ALLOWED:-true}" # set to false after your team joined!
|
||||
# --- Single sign-on via your own Forgejo (episode 4) ---
|
||||
# Create the OAuth2 app in Forgejo FIRST: Site Administration ->
|
||||
# Applications -> OAuth2. Its redirect URI must be exactly:
|
||||
# --- Single sign-on (episode 4 with Forgejo, since episode 15 Pocket ID) ---
|
||||
# Create the OIDC client in Pocket ID FIRST. Its callback URL must be exactly:
|
||||
# https://vault.${DOMAIN}/identity/connect/oidc-signin
|
||||
# That path is not documented in the UI. Getting it wrong fails only
|
||||
# AFTER the Forgejo login, which is why it costs people twenty minutes.
|
||||
# AFTER the provider login, which is why it costs people twenty minutes.
|
||||
SSO_ENABLED: "true"
|
||||
# Just the base URL. Vaultwarden reads Forgejo's discovery document
|
||||
# Just the base URL. Vaultwarden reads the discovery document
|
||||
# (/.well-known/openid-configuration) from here on the first login.
|
||||
SSO_AUTHORITY: https://git.${DOMAIN}
|
||||
# SSO_AUTH_ONLY_NOT_SESSION stays false (default): every token refresh
|
||||
# asks the provider again, so a person disabled there is out at the next
|
||||
# refresh. Episode 15 measured why that alone was not enough with Forgejo.
|
||||
SSO_AUTHORITY: https://id.${DOMAIN}
|
||||
SSO_CLIENT_ID: ${VAULTWARDEN_SSO_CLIENT_ID}
|
||||
SSO_CLIENT_SECRET: ${VAULTWARDEN_SSO_CLIENT_SECRET}
|
||||
# Vaultwarden prepends "openid" itself, so listing it here again shows up
|
||||
# duplicated on the Forgejo consent screen. Harmless, but untidy.
|
||||
# duplicated on the consent screen. Harmless, but untidy.
|
||||
SSO_SCOPES: "profile email"
|
||||
# Attach an SSO login to the existing account with the same mail address
|
||||
# instead of creating a duplicate. Turn this on BEFORE people log in.
|
||||
|
|
@ -247,6 +259,54 @@ services:
|
|||
- ./backup/backup.log:/backup/backup.log:ro
|
||||
networks: [proxy]
|
||||
|
||||
# Identity provider (episode 15). Forgejo used to be the OIDC provider for
|
||||
# outline, vaultwarden and netbird. Now everyone logs in here, with a
|
||||
# passkey, and switching one person off happens in one place. Pocket ID is
|
||||
# small enough for a 4 GB box; groups decide who gets which service.
|
||||
pocket-id:
|
||||
image: ghcr.io/pocket-id/pocket-id:v2.16.0
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
APP_URL: https://id.${DOMAIN}
|
||||
# Caddy sits in front, so trust X-Forwarded-For for the audit log.
|
||||
TRUST_PROXY: "true"
|
||||
# Encrypts the signing keys at rest. Generate once: openssl rand -base64 32
|
||||
ENCRYPTION_KEY: ${POCKET_ID_ENCRYPTION_KEY}
|
||||
ANALYTICS_DISABLED: "true"
|
||||
volumes:
|
||||
- pocket_id_data:/app/data
|
||||
networks: [proxy]
|
||||
|
||||
# Login gate for services without their own SSO (episode 15). Caddy asks
|
||||
# tinyauth on every request (forward_auth); tinyauth sends people to Pocket
|
||||
# ID and lets through only members of the right group. No docker socket,
|
||||
# apps are configured below, not via labels.
|
||||
tinyauth:
|
||||
image: ghcr.io/tinyauthapp/tinyauth:v5.2.0
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
TINYAUTH_APPURL: https://auth.${DOMAIN}
|
||||
TINYAUTH_LABELPROVIDER: none
|
||||
TINYAUTH_ANALYTICS_ENABLED: "false"
|
||||
TINYAUTH_DATABASE_PATH: /data/tinyauth.db
|
||||
TINYAUTH_AUTH_SECURECOOKIE: "true"
|
||||
TINYAUTH_AUTH_TRUSTEDPROXIES: 172.18.0.0/16
|
||||
TINYAUTH_OAUTH_AUTOREDIRECT: pocketid
|
||||
TINYAUTH_OAUTH_PROVIDERS_POCKETID_NAME: Pocket ID
|
||||
TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTID: ${TINYAUTH_CLIENT_ID}
|
||||
TINYAUTH_OAUTH_PROVIDERS_POCKETID_CLIENTSECRET: ${TINYAUTH_CLIENT_SECRET}
|
||||
TINYAUTH_OAUTH_PROVIDERS_POCKETID_AUTHURL: https://id.${DOMAIN}/authorize
|
||||
TINYAUTH_OAUTH_PROVIDERS_POCKETID_TOKENURL: https://id.${DOMAIN}/api/oidc/token
|
||||
TINYAUTH_OAUTH_PROVIDERS_POCKETID_USERINFOURL: https://id.${DOMAIN}/api/oidc/userinfo
|
||||
TINYAUTH_OAUTH_PROVIDERS_POCKETID_REDIRECTURL: https://auth.${DOMAIN}/api/oauth/callback/pocketid
|
||||
TINYAUTH_OAUTH_PROVIDERS_POCKETID_SCOPES: openid email profile groups
|
||||
# Uptime Kuma: only the Pocket ID group "ops" gets past the gate.
|
||||
TINYAUTH_APPS_KUMA_CONFIG_DOMAIN: status.${DOMAIN}
|
||||
TINYAUTH_APPS_KUMA_OAUTH_GROUPS: ops
|
||||
volumes:
|
||||
- tinyauth_data:/data
|
||||
networks: [proxy]
|
||||
|
||||
# Optional starter VPN — enable with: docker compose --profile vpn up -d
|
||||
# (The proper Netbird mesh deployment is Episode 5.)
|
||||
wg-easy:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue