Complete TB-006 batch 1 SSO so admin apps use Authentik without double login.
Deploy to valhalla / deploy (push) Has been cancelled
Deploy to valhalla / deploy (push) Has been cancelled
Native OAuth/OIDC for Homarr, BookStack, Gitea, and MinIO console; forward_auth with local auth disabled for code-server, uptime, and kopia; Caddy and Authentik scripts updated to match.
This commit is contained in:
@@ -210,13 +210,7 @@ storage.j-costa.com, https://storage.j-costa.com {
|
||||
# MINIO CONSOLE — internal only
|
||||
# =============================================================
|
||||
minio.ginnoir.com {
|
||||
route {
|
||||
import authentik_outpost
|
||||
handle {
|
||||
import authentik_forward_auth
|
||||
reverse_proxy resume-minio:9001
|
||||
}
|
||||
}
|
||||
reverse_proxy resume-minio:9001
|
||||
}
|
||||
|
||||
# =============================================================
|
||||
@@ -238,19 +232,15 @@ dev.ginnoir.com {
|
||||
# MANAGEMENT — internal only
|
||||
# =============================================================
|
||||
portainer.ginnoir.com {
|
||||
route {
|
||||
import authentik_outpost
|
||||
@webhook {
|
||||
method POST
|
||||
path /api/stacks/webhooks/*
|
||||
}
|
||||
handle @webhook {
|
||||
reverse_proxy portainer:9000
|
||||
}
|
||||
handle {
|
||||
import authentik_forward_auth
|
||||
reverse_proxy portainer:9000
|
||||
}
|
||||
@webhook {
|
||||
method POST
|
||||
path /api/stacks/webhooks/*
|
||||
}
|
||||
handle @webhook {
|
||||
reverse_proxy portainer:9000
|
||||
}
|
||||
handle {
|
||||
reverse_proxy portainer:9000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -265,13 +255,7 @@ uptime.ginnoir.com {
|
||||
}
|
||||
|
||||
homarr.ginnoir.com {
|
||||
route {
|
||||
import authentik_outpost
|
||||
handle {
|
||||
import authentik_forward_auth
|
||||
reverse_proxy homarr:7575
|
||||
}
|
||||
}
|
||||
reverse_proxy homarr:7575
|
||||
}
|
||||
|
||||
backup.ginnoir.com {
|
||||
@@ -293,20 +277,16 @@ router.ginnoir.com {
|
||||
# DEV STACK — internal only
|
||||
# =============================================================
|
||||
gitea.ginnoir.com {
|
||||
route {
|
||||
import authentik_outpost
|
||||
@api path /api/*
|
||||
@git path_regexp (?i)\.git(/|$)
|
||||
handle @api {
|
||||
reverse_proxy gitea:3000
|
||||
}
|
||||
handle @git {
|
||||
reverse_proxy gitea:3000
|
||||
}
|
||||
handle {
|
||||
import authentik_forward_auth
|
||||
reverse_proxy gitea:3000
|
||||
}
|
||||
@api path /api/*
|
||||
@git path_regexp (?i)\.git(/|$)
|
||||
handle @api {
|
||||
reverse_proxy gitea:3000
|
||||
}
|
||||
handle @git {
|
||||
reverse_proxy gitea:3000
|
||||
}
|
||||
handle {
|
||||
reverse_proxy gitea:3000
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,28 +340,18 @@ vault.ginnoir.com {
|
||||
}
|
||||
|
||||
docs.ginnoir.com {
|
||||
route {
|
||||
import authentik_outpost
|
||||
handle {
|
||||
import authentik_forward_auth
|
||||
reverse_proxy bookstack:80
|
||||
}
|
||||
}
|
||||
reverse_proxy bookstack:80
|
||||
}
|
||||
|
||||
plane.ginnoir.com {
|
||||
route {
|
||||
import authentik_outpost
|
||||
handle /api/* {
|
||||
reverse_proxy plane_api:8000
|
||||
}
|
||||
handle /auth/* {
|
||||
reverse_proxy plane_api:8000
|
||||
}
|
||||
handle {
|
||||
import authentik_forward_auth
|
||||
reverse_proxy plane_web:3000
|
||||
}
|
||||
handle /api/* {
|
||||
reverse_proxy plane_api:8000
|
||||
}
|
||||
handle /auth/* {
|
||||
reverse_proxy plane_api:8000
|
||||
}
|
||||
handle {
|
||||
reverse_proxy plane_web:3000
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
| | |
|
||||
|---|---|
|
||||
| **Decision** | Do it — batch 1 admin deployed; batches 2–3 pending |
|
||||
| **Notes** | Out: obsidian, foundry×2, overseerr. Batch 1: 12 admin sites + Caddy forward_auth + Authentik proxy providers. Push repo to persist authentik compose/blueprint changes. |
|
||||
| **Notes** | Out: obsidian, foundry×2, overseerr. Batch 1: homarr native OIDC done; rest split native OAuth vs forward_auth+disable local auth (2026-06-11). Portainer needs one-time OAuth in UI; Plane needs god-mode OIDC. Push repo to persist. |
|
||||
| **Date** | 2026-06-10 (batch 1 deployed) |
|
||||
|
||||
---
|
||||
@@ -69,8 +69,9 @@ Caddyfile + Authentik UI
|
||||
|
||||
1. Authentik embedded outpost + proxy provider(s) on `edge`
|
||||
2. Caddy `(authentik_auth)` snippet — `forward_auth authentik-server:9000` with trusted headers
|
||||
3. Roll out site-by-site; per-site `handle` bypasses for webhooks/API (`/api/*` where key-auth, Portainer `/api/stacks/webhooks/*`, Gitea hooks, ntfy publish, etc.)
|
||||
4. Disable or bypass redundant in-app login where the app supports it (optional second pass)
|
||||
3. Roll out site-by-site; per-site `handle` bypasses for webhooks/API
|
||||
4. **Important:** `forward_auth` only gates the edge — apps still show their own login unless you add **native OIDC/OAuth** (famapp/homarr pattern) or disable local auth (e.g. code-server `--auth none`). Homarr switched to native OIDC 2026-06-11.
|
||||
5. **Batch 1 rest (2026-06-11):** BookStack/Gitea/MinIO/Portainer/Plane → native OAuth2 providers; code/uptime/kopia/registry-ui/dbx/vault → forward_auth with local auth disabled where possible. **Portainer:** one-time OAuth setup in Settings → Authentication (see `setup-batch1-oidc.py` output). **Plane:** god-mode at `/god-mode/authentication/oidc`.
|
||||
|
||||
**Rollout batches**
|
||||
|
||||
@@ -84,7 +85,13 @@ Caddyfile + Authentik UI
|
||||
|
||||
### Done when
|
||||
|
||||
- [x] Batch 1 admin sites require Authentik before app UI loads
|
||||
- [x] Batch 1 admin sites require Authentik before app UI loads (or native OAuth equivalent)
|
||||
- [x] Homarr native OIDC (no double login)
|
||||
- [x] BookStack, Gitea, MinIO console → native Authentik OAuth/OIDC
|
||||
- [x] code-server, uptime, kopia → forward_auth only (local auth disabled)
|
||||
- [x] registry-ui, dbx, vault → forward_auth at edge
|
||||
- [ ] Portainer OAuth configured in Settings → Authentication (provider created; UI step pending)
|
||||
- [ ] Plane OIDC configured in god-mode (provider created; UI step pending)
|
||||
- [x] Portainer git webhooks still work (bypass path)
|
||||
- [x] Gitea `/api/*` + `.git` and Vault `/v1/*` bypass forward_auth
|
||||
- [ ] Batch 2 media sites
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
docker cp /tmp/disable-uptime-auth.py uptime_kuma:/tmp/disable-uptime-auth.py
|
||||
docker exec uptime_kuma python3 /tmp/disable-uptime-auth.py /app/data/kuma.db
|
||||
|
||||
docker exec -u git gitea gitea admin auth add-oauth \
|
||||
--name Authentik \
|
||||
--provider openidConnect \
|
||||
--key y7rEvQjZ2J6sGuLndPZalGXn4jx0Tus601U9USOS \
|
||||
--secret FCbRN0YUh5K6rGhirX5HlrSumYgmKPGi6xBPQY5YvpJIPGi9st4g8FZN2kyJ7IzDXbj0FGmE3Q2qysNcZYePc7mofq6k98fz9Ue97Tjj4y3ZPNXAeAqlJrclv9DzR6eo \
|
||||
--auto-discover-url https://auth.ginnoir.com/application/o/gitea/.well-known/openid-configuration \
|
||||
--scopes "openid profile email" || echo "gitea oauth may already exist"
|
||||
|
||||
echo done
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Apply TB-006 batch 1 proxy providers via authentik ORM (run: ak shell < script)."""
|
||||
"""Apply TB-006 batch 1 forward_auth proxy providers (forward_auth-only sites).
|
||||
|
||||
Native OAuth/OIDC apps (bookstack, gitea, portainer, minio, plane, homarr) use
|
||||
setup-batch1-oidc.py instead. Run this via: ak shell -c exec(open(...).read())
|
||||
"""
|
||||
from authentik.core.models import Application
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.models import Outpost, OutpostType
|
||||
@@ -8,33 +12,38 @@ from authentik.providers.proxy.models import ProxyMode, ProxyProvider
|
||||
AUTHZ = Flow.objects.get(slug="default-provider-authorization-implicit-consent")
|
||||
INVALID = Flow.objects.get(slug="default-provider-invalidation-flow")
|
||||
|
||||
UPTIME_UNAUTH = """^/status/.*
|
||||
^/assets/.*
|
||||
^/api/push/.*
|
||||
^/api/badge/.*
|
||||
^/api/status-page/heartbeat/.*
|
||||
^/icon.svg
|
||||
^/upload/.*"""
|
||||
|
||||
SITES = [
|
||||
("homelab-portainer", "portainer", "Portainer", "https://portainer.ginnoir.com"),
|
||||
("homelab-code", "code", "code-server", "https://code.ginnoir.com"),
|
||||
("homelab-registry-ui", "registry-ui", "Registry UI", "https://registry-ui.ginnoir.com"),
|
||||
("homelab-vault", "vault", "Vault", "https://vault.ginnoir.com"),
|
||||
("homelab-minio", "minio", "MinIO Console", "https://minio.ginnoir.com"),
|
||||
("homelab-homarr", "homarr", "Homarr", "https://homarr.ginnoir.com"),
|
||||
("homelab-uptime", "uptime", "Uptime Kuma", "https://uptime.ginnoir.com"),
|
||||
("homelab-backup", "backup", "Kopia", "https://backup.ginnoir.com"),
|
||||
("homelab-gitea", "gitea", "Gitea", "https://gitea.ginnoir.com"),
|
||||
("homelab-dbx", "dbx", "DBX", "https://dbx.ginnoir.com"),
|
||||
("homelab-bookstack", "bookstack", "BookStack", "https://docs.ginnoir.com"),
|
||||
("homelab-plane", "plane", "Plane", "https://plane.ginnoir.com"),
|
||||
("homelab-code", "code", "code-server", "https://code.ginnoir.com", None, None),
|
||||
("homelab-registry-ui", "registry-ui", "Registry UI", "https://registry-ui.ginnoir.com", None, None),
|
||||
("homelab-vault", "vault", "Vault", "https://vault.ginnoir.com", None, None),
|
||||
("homelab-uptime", "uptime", "Uptime Kuma", "https://uptime.ginnoir.com", "http://uptime-kuma:3001", UPTIME_UNAUTH),
|
||||
("homelab-backup", "backup", "Kopia", "https://backup.ginnoir.com", None, None),
|
||||
("homelab-dbx", "dbx", "DBX", "https://dbx.ginnoir.com", None, None),
|
||||
]
|
||||
|
||||
providers = []
|
||||
for pname, slug, aname, host in SITES:
|
||||
provider, _ = ProxyProvider.objects.update_or_create(
|
||||
name=pname,
|
||||
defaults={
|
||||
"mode": ProxyMode.FORWARD_SINGLE,
|
||||
"external_host": host,
|
||||
"authorization_flow": AUTHZ,
|
||||
"invalidation_flow": INVALID,
|
||||
"intercept_header_auth": True,
|
||||
},
|
||||
)
|
||||
for pname, slug, aname, host, internal, unauth in SITES:
|
||||
defaults = {
|
||||
"mode": ProxyMode.FORWARD_SINGLE,
|
||||
"external_host": host,
|
||||
"authorization_flow": AUTHZ,
|
||||
"invalidation_flow": INVALID,
|
||||
"intercept_header_auth": True,
|
||||
}
|
||||
if internal:
|
||||
defaults["internal_host"] = internal
|
||||
if unauth:
|
||||
defaults["skip_path_regex"] = unauth
|
||||
|
||||
provider, _ = ProxyProvider.objects.update_or_create(name=pname, defaults=defaults)
|
||||
Application.objects.update_or_create(
|
||||
slug=slug,
|
||||
defaults={
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
"""Configure Portainer OAuth via API (run on valhalla with admin JWT)."""
|
||||
import json
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
JWT = sys.argv[1] if len(sys.argv) > 1 else ""
|
||||
CLIENT_ID = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
CLIENT_SECRET = sys.argv[3] if len(sys.argv) > 3 else ""
|
||||
if not JWT or not CLIENT_ID or not CLIENT_SECRET:
|
||||
print("usage: configure-portainer-oauth.py <admin_jwt> <client_id> <client_secret>")
|
||||
sys.exit(1)
|
||||
|
||||
BASE = "http://localhost:9100/api"
|
||||
AUTH = "https://auth.ginnoir.com/application/o"
|
||||
|
||||
payload = {
|
||||
"AuthenticationMethod": 3,
|
||||
"OAuthSettings": {
|
||||
"ClientID": CLIENT_ID,
|
||||
"ClientSecret": CLIENT_SECRET,
|
||||
"AuthorizationURI": f"{AUTH}/authorize/",
|
||||
"AccessTokenURI": f"{AUTH}/token/",
|
||||
"ResourceURI": f"{AUTH}/userinfo/",
|
||||
"RedirectURI": "https://portainer.ginnoir.com/",
|
||||
"LogoutURI": f"{AUTH}/portainer/end-session/",
|
||||
"UserIdentifier": "email",
|
||||
"Scopes": "openid email profile",
|
||||
"OAuthAutoCreateUsers": True,
|
||||
"DefaultTeamID": 0,
|
||||
},
|
||||
}
|
||||
|
||||
req = urllib.request.Request(
|
||||
f"{BASE}/settings",
|
||||
data=json.dumps(payload).encode(),
|
||||
headers={"Authorization": f"Bearer {JWT}", "Content-Type": "application/json"},
|
||||
method="PUT",
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req) as resp:
|
||||
print("portainer oauth configured:", resp.status)
|
||||
except urllib.error.HTTPError as e:
|
||||
print("error:", e.code, e.read().decode())
|
||||
sys.exit(1)
|
||||
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
docker cp /tmp/disable-uptime-auth.py uptime_kuma:/tmp/disable-uptime-auth.py
|
||||
docker exec uptime_kuma python3 /tmp/disable-uptime-auth.py /app/data/kuma.db
|
||||
|
||||
docker exec -u git gitea gitea admin auth add-oauth \
|
||||
--name Authentik \
|
||||
--provider openidConnect \
|
||||
--key y7rEvQjZ2J6sGuLndPZalGXn4jx0Tus601U9USOS \
|
||||
--secret FCbRN0YUh5K6rGhirX5HlrSumYgmKPGi6xBPQY5YvpJIPGi9st4g8FZN2kyJ7IzDXbj0FGmE3Q2qysNcZYePc7mofq6k98fz9Ue97Tjj4y3ZPNXAeAqlJrclv9DzR6eo \
|
||||
--auto-discover-url https://auth.ginnoir.com/application/o/gitea/.well-known/openid-configuration \
|
||||
--scopes "openid profile email" 2>&1 || echo "gitea oauth may already exist"
|
||||
|
||||
cp /tmp/init-kopia.sh /config/backup/init-kopia.sh
|
||||
chmod +x /config/backup/init-kopia.sh
|
||||
mkdir -p /config/code-server/.config/code-server
|
||||
cp /tmp/code-server.yaml /config/code-server/code-server.yaml
|
||||
|
||||
docker compose -p dev -f /tmp/batch1-deploy/dev/docker-compose.yml --env-file /tmp/batch1-deploy/dev/stack.env up -d --force-recreate bookstack code-server gitea
|
||||
docker compose -p resume -f /tmp/batch1-deploy/resume/docker-compose.yml --env-file /tmp/batch1-deploy/resume/stack.env up -d --force-recreate resume-minio
|
||||
docker compose -p backup -f /data/compose/29/stacks/backup/docker-compose.yml up -d --force-recreate kopia
|
||||
|
||||
docker exec caddy caddy reload --config /etc/caddy/Caddyfile
|
||||
|
||||
echo batch1 live deploy done
|
||||
@@ -0,0 +1,15 @@
|
||||
"""Disable Uptime Kuma local auth (forward_auth is the gate)."""
|
||||
import sqlite3
|
||||
import sys
|
||||
|
||||
DB = sys.argv[1] if len(sys.argv) > 1 else "/config/uptime-kuma/kuma.db"
|
||||
|
||||
conn = sqlite3.connect(DB)
|
||||
cur = conn.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO setting (key, value) VALUES ('disableAuth', '1') "
|
||||
"ON CONFLICT(key) DO UPDATE SET value='1'"
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
print(f"disableAuth=1 in {DB}")
|
||||
@@ -0,0 +1,77 @@
|
||||
"""Create OAuth2 providers for batch-1 apps with native SSO (ak shell: exec(open(...).read()))."""
|
||||
from authentik.core.models import Application
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.models import Outpost
|
||||
from authentik.providers.oauth2.models import OAuth2Provider, RedirectURI, RedirectURIMatchingMode
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
AUTHZ = Flow.objects.get(slug="default-provider-authorization-implicit-consent")
|
||||
INVALID = Flow.objects.get(slug="default-provider-invalidation-flow")
|
||||
DEFAULT_MAPPINGS = OAuth2Provider.objects.get(name="famapp").property_mappings.all()
|
||||
|
||||
# slug, provider name, redirect URI(s)
|
||||
OIDC_APPS = [
|
||||
("bookstack", "bookstack", ["https://docs.ginnoir.com/oidc/callback"]),
|
||||
("gitea", "gitea", ["https://gitea.ginnoir.com/user/oauth2/Authentik/callback"]),
|
||||
("portainer", "portainer", ["https://portainer.ginnoir.com/"]),
|
||||
("minio", "minio-console", ["https://minio.ginnoir.com/oauth_callback"]),
|
||||
("plane", "plane", ["https://plane.ginnoir.com/auth/oidc/callback/"]),
|
||||
]
|
||||
|
||||
native_slugs = []
|
||||
for slug, pname, redirects in OIDC_APPS:
|
||||
provider, created = OAuth2Provider.objects.update_or_create(
|
||||
name=pname,
|
||||
defaults={
|
||||
"authorization_flow": AUTHZ,
|
||||
"invalidation_flow": INVALID,
|
||||
"redirect_uris": [
|
||||
RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, url=url)
|
||||
for url in redirects
|
||||
],
|
||||
"access_code_validity": "minutes=1",
|
||||
"access_token_validity": "hours=24",
|
||||
"refresh_token_validity": "days=30",
|
||||
},
|
||||
)
|
||||
provider.property_mappings.set(DEFAULT_MAPPINGS)
|
||||
provider.save()
|
||||
|
||||
app, _ = Application.objects.update_or_create(
|
||||
slug=slug,
|
||||
defaults={
|
||||
"name": pname.replace("-", " ").title(),
|
||||
"provider": provider,
|
||||
"meta_launch_url": redirects[0].split("/oidc")[0].split("/user/oauth2")[0].rstrip("/"),
|
||||
"policy_engine_mode": "any",
|
||||
},
|
||||
)
|
||||
app.provider = provider
|
||||
app.save()
|
||||
|
||||
native_slugs.append(slug)
|
||||
print(f"oauth2 {slug} created={created} client_id={provider.client_id}")
|
||||
print(f" {slug.upper()}_OIDC_CLIENT_ID={provider.client_id}")
|
||||
print(f" {slug.upper()}_OIDC_CLIENT_SECRET={provider.client_secret}")
|
||||
|
||||
# Drop native-OIDC apps from the embedded proxy outpost (avoid double auth).
|
||||
PROXY_NAMES = {
|
||||
"bookstack": "homelab-bookstack",
|
||||
"gitea": "homelab-gitea",
|
||||
"portainer": "homelab-portainer",
|
||||
"minio": "homelab-minio",
|
||||
"plane": "homelab-plane",
|
||||
}
|
||||
outpost = Outpost.objects.get(name="authentik Embedded Outpost")
|
||||
for slug in native_slugs:
|
||||
pname = PROXY_NAMES.get(slug)
|
||||
if not pname:
|
||||
continue
|
||||
try:
|
||||
proxy = ProxyProvider.objects.get(name=pname)
|
||||
outpost.providers.remove(proxy)
|
||||
print(f"removed proxy {pname} from embedded outpost")
|
||||
except ProxyProvider.DoesNotExist:
|
||||
print(f"proxy {pname} not found (ok)")
|
||||
|
||||
print("done — configure each app with printed client_id/secret")
|
||||
@@ -0,0 +1,59 @@
|
||||
"""Update proxy providers for forward_auth-only batch-1 apps (ak shell: exec(open(...).read()))."""
|
||||
from authentik.core.models import Application
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.models import Outpost, OutpostType
|
||||
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
|
||||
|
||||
AUTHZ = Flow.objects.get(slug="default-provider-authorization-implicit-consent")
|
||||
INVALID = Flow.objects.get(slug="default-provider-invalidation-flow")
|
||||
|
||||
UPTIME_UNAUTH = """^/status/.*
|
||||
^/assets/.*
|
||||
^/api/push/.*
|
||||
^/api/badge/.*
|
||||
^/api/status-page/heartbeat/.*
|
||||
^/icon.svg
|
||||
^/upload/.*"""
|
||||
|
||||
# Proxy-only sites (native OIDC apps managed by setup-batch1-oidc.py).
|
||||
SITES = [
|
||||
("homelab-code", "code", "code-server", "https://code.ginnoir.com", None, None),
|
||||
("homelab-registry-ui", "registry-ui", "Registry UI", "https://registry-ui.ginnoir.com", None, None),
|
||||
("homelab-vault", "vault", "Vault", "https://vault.ginnoir.com", None, None),
|
||||
("homelab-uptime", "uptime", "Uptime Kuma", "https://uptime.ginnoir.com", "http://uptime-kuma:3001", UPTIME_UNAUTH),
|
||||
("homelab-backup", "backup", "Kopia", "https://backup.ginnoir.com", None, None),
|
||||
("homelab-dbx", "dbx", "DBX", "https://dbx.ginnoir.com", None, None),
|
||||
]
|
||||
|
||||
providers = []
|
||||
for pname, slug, aname, host, internal, unauth in SITES:
|
||||
defaults = {
|
||||
"mode": ProxyMode.FORWARD_SINGLE,
|
||||
"external_host": host,
|
||||
"authorization_flow": AUTHZ,
|
||||
"invalidation_flow": INVALID,
|
||||
"intercept_header_auth": True,
|
||||
}
|
||||
if internal:
|
||||
defaults["internal_host"] = internal
|
||||
if unauth:
|
||||
defaults["skip_path_regex"] = unauth
|
||||
|
||||
provider, _ = ProxyProvider.objects.update_or_create(name=pname, defaults=defaults)
|
||||
Application.objects.update_or_create(
|
||||
slug=slug,
|
||||
defaults={
|
||||
"name": aname,
|
||||
"provider": provider,
|
||||
"meta_launch_url": host,
|
||||
"policy_engine_mode": "any",
|
||||
},
|
||||
)
|
||||
providers.append(provider)
|
||||
print(f"ok {slug} -> {host}")
|
||||
|
||||
outpost = Outpost.objects.get(name="authentik Embedded Outpost")
|
||||
outpost.type = OutpostType.PROXY
|
||||
outpost.providers.set(providers)
|
||||
outpost.save()
|
||||
print(f"outpost providers: {outpost.providers.count()}")
|
||||
@@ -0,0 +1,24 @@
|
||||
"""Register Authentik as Gitea OAuth source (run on host: docker exec gitea ...)."""
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
CLIENT_ID = sys.argv[1] if len(sys.argv) > 1 else ""
|
||||
CLIENT_SECRET = sys.argv[2] if len(sys.argv) > 2 else ""
|
||||
if not CLIENT_ID or not CLIENT_SECRET:
|
||||
print("usage: setup-gitea-oauth.py <client_id> <client_secret>")
|
||||
sys.exit(1)
|
||||
|
||||
DISCOVERY = "https://auth.ginnoir.com/application/o/gitea/.well-known/openid-configuration"
|
||||
|
||||
cmd = [
|
||||
"docker", "exec", "-u", "git", "gitea", "gitea", "admin", "auth", "add-oauth",
|
||||
"--name", "Authentik",
|
||||
"--provider", "openidConnect",
|
||||
"--key", CLIENT_ID,
|
||||
"--secret", CLIENT_SECRET,
|
||||
"--auto-discover-url", DISCOVERY,
|
||||
"--scopes", "openid profile email",
|
||||
]
|
||||
print("running:", " ".join(cmd[:8]), "...")
|
||||
subprocess.run(cmd, check=True)
|
||||
print("gitea oauth source Authentik registered")
|
||||
@@ -0,0 +1,47 @@
|
||||
"""Switch homarr from forward-auth proxy provider to native OIDC (run: ak shell -c exec(open(...).read()))."""
|
||||
from authentik.core.models import Application
|
||||
from authentik.flows.models import Flow
|
||||
from authentik.outposts.models import Outpost
|
||||
from authentik.providers.oauth2.models import OAuth2Provider, RedirectURI, RedirectURIMatchingMode
|
||||
from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
AUTHZ = Flow.objects.get(slug="default-provider-authorization-implicit-consent")
|
||||
INVALID = Flow.objects.get(slug="default-provider-invalidation-flow")
|
||||
|
||||
REDIRECT = "https://homarr.ginnoir.com/api/auth/callback/oidc"
|
||||
|
||||
provider, created = OAuth2Provider.objects.update_or_create(
|
||||
name="homarr",
|
||||
defaults={
|
||||
"authorization_flow": AUTHZ,
|
||||
"invalidation_flow": INVALID,
|
||||
"redirect_uris": [
|
||||
RedirectURI(matching_mode=RedirectURIMatchingMode.STRICT, url=REDIRECT),
|
||||
],
|
||||
"access_code_validity": "minutes=1",
|
||||
"access_token_validity": "hours=24",
|
||||
"refresh_token_validity": "days=30",
|
||||
},
|
||||
)
|
||||
# Required for Homarr profile parsing — without these, userinfo lacks name/email claims.
|
||||
default_mappings = OAuth2Provider.objects.get(name="famapp").property_mappings.all()
|
||||
provider.property_mappings.set(default_mappings)
|
||||
provider.save()
|
||||
print(f"oauth2 provider homarr created={created} client_id={provider.client_id}")
|
||||
|
||||
app = Application.objects.get(slug="homarr")
|
||||
app.provider = provider
|
||||
app.save()
|
||||
print(f"application homarr -> OAuth2Provider homarr")
|
||||
|
||||
# Drop homarr from the embedded proxy outpost (native OIDC replaces forward_auth here).
|
||||
try:
|
||||
proxy = ProxyProvider.objects.get(name="homelab-homarr")
|
||||
outpost = Outpost.objects.get(name="authentik Embedded Outpost")
|
||||
outpost.providers.remove(proxy)
|
||||
print("removed homelab-homarr from embedded outpost")
|
||||
except ProxyProvider.DoesNotExist:
|
||||
print("homelab-homarr proxy provider not found (ok)")
|
||||
|
||||
print(f"AUTH_OIDC_CLIENT_ID={provider.client_id}")
|
||||
print(f"AUTH_OIDC_CLIENT_SECRET={provider.client_secret}")
|
||||
@@ -44,7 +44,5 @@ else
|
||||
echo "init-kopia: B2 credentials empty — start UI only; configure repo manually (see README)"
|
||||
fi
|
||||
|
||||
# Web UI uses HTTP basic auth (distinct from repository encryption password).
|
||||
exec kopia server start --ui --insecure --address=0.0.0.0:51515 \
|
||||
--server-username=kopia \
|
||||
--server-password="${KOPIA_PASSWORD}"
|
||||
# Web UI auth is handled by Caddy forward_auth — no second login prompt.
|
||||
exec kopia server start --ui --insecure --address=0.0.0.0:51515 --without-password
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
bind-addr: 0.0.0.0:8443
|
||||
auth: none
|
||||
cert: false
|
||||
@@ -82,11 +82,11 @@ services:
|
||||
- PUID=1000
|
||||
- PGID=1000
|
||||
- TZ=${TZ}
|
||||
- PASSWORD=${CODE_SERVER_PASSWORD}
|
||||
- SUDO_PASSWORD=${CODE_SERVER_PASSWORD}
|
||||
- DEFAULT_WORKSPACE=/workspace
|
||||
# Authentik forward_auth at the edge — local password auth disabled via config mount.
|
||||
volumes:
|
||||
- /config/code-server:/config
|
||||
- /config/code-server/code-server.yaml:/config/.config/code-server/config.yaml
|
||||
- /home/ginnoir:/workspace
|
||||
ports:
|
||||
- "8443:8443"
|
||||
@@ -146,6 +146,13 @@ services:
|
||||
- DB_USER=bookstack
|
||||
- DB_PASS=${BOOKSTACK_DB_PASSWORD}
|
||||
- DB_DATABASE=bookstack
|
||||
- AUTH_METHOD=oidc
|
||||
- AUTH_AUTO_INITIATE=true
|
||||
- OIDC_NAME=Authentik
|
||||
- OIDC_CLIENT_ID=${BOOKSTACK_OIDC_CLIENT_ID}
|
||||
- OIDC_CLIENT_SECRET=${BOOKSTACK_OIDC_CLIENT_SECRET}
|
||||
- OIDC_ISSUER=https://auth.ginnoir.com/application/o/bookstack/
|
||||
- OIDC_ISSUER_DISCOVER=true
|
||||
volumes:
|
||||
- /config/bookstack:/config
|
||||
ports:
|
||||
|
||||
@@ -12,6 +12,8 @@ CODE_SERVER_PASSWORD=c0de_s3rver_pass
|
||||
BOOKSTACK_APP_KEY=base64:cSR6L4TPEbM2IFi9MyZcnmexvzAlIHAjn/qMzQKO/t4=
|
||||
BOOKSTACK_DB_PASSWORD=bookstack_d3v_pass
|
||||
BOOKSTACK_DB_ROOT_PASSWORD=bookstack_r00t_pass
|
||||
BOOKSTACK_OIDC_CLIENT_ID=8YV4BI2JVIG1QzS2DbylEgqv56sgfiOVRXOgw2Z5
|
||||
BOOKSTACK_OIDC_CLIENT_SECRET=wIyiw7LmGBORDyTdc2crSZryXJHzIq4gtajAKSpOU7e8BkBSO6SstM9gGPrnp4tbJhCFSQggYAgtYd5LOjUvQpSWInzm0JLlmLg1BTlPq5EVVieNYuWRCCQrerj8gRZ0
|
||||
|
||||
# Plane project management
|
||||
# Rotate with: openssl rand -hex 32
|
||||
|
||||
@@ -21,6 +21,8 @@ services:
|
||||
networks: [edge]
|
||||
env_file:
|
||||
- stack.env
|
||||
environment:
|
||||
- BASE_URL=https://homarr.ginnoir.com
|
||||
ports:
|
||||
- "7575:7575"
|
||||
volumes:
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
# monitoring stack secrets — injected via env_file; container-exact var names.
|
||||
# Homarr config encryption key. Generate with: openssl rand -hex 32
|
||||
SECRET_ENCRYPTION_KEY=98989838bf06c9e42d77944ce848963a22ab316b55a09b0d199652ef0934394f
|
||||
# Auth.js session/state signing — required for OIDC (openssl rand -base64 32)
|
||||
AUTH_SECRET=7Kp2mN9xQvR4sT6wY8zA1bC3dE5fG0hJ2kL4nM6pS8uV0xZ=
|
||||
|
||||
# Homarr native OIDC via Authentik (replaces forward_auth + credentials login)
|
||||
AUTH_PROVIDERS=oidc
|
||||
AUTH_OIDC_ISSUER=https://auth.ginnoir.com/application/o/homarr/
|
||||
AUTH_OIDC_CLIENT_ID=u5XiQcEenMoo3cViXOw3OWt644m2AMsbUXNpInqu
|
||||
AUTH_OIDC_CLIENT_SECRET=PTnXULynOv7HreiqnCmAWOc9vy81gyA57NIS7rxJnBc8g6WeD7fo4d5kWoK54U3QodMrTXVAtT6GoTcqJznEONQDzeZXGe25fZAbcQNtOdgpLakoLKHP1aVU2jJ2nkOR
|
||||
AUTH_OIDC_CLIENT_NAME=Authentik
|
||||
AUTH_OIDC_AUTO_LOGIN=true
|
||||
AUTH_OIDC_ENABLE_DANGEROUS_ACCOUNT_LINKING=true
|
||||
AUTH_OIDC_SCOPE_OVERWRITE=openid email profile
|
||||
AUTH_OIDC_GROUPS_ATTRIBUTE=groups
|
||||
AUTH_OIDC_FORCE_USERINFO=true
|
||||
AUTH_OIDC_NAME_ATTRIBUTE_OVERWRITE=name
|
||||
AUTH_LOGOUT_REDIRECT_URL=https://auth.ginnoir.com/application/o/homarr/end-session/
|
||||
|
||||
@@ -38,6 +38,12 @@ services:
|
||||
- /storage1/labdata/resume/minio:/data
|
||||
env_file:
|
||||
- stack.env
|
||||
environment:
|
||||
- MINIO_BROWSER_REDIRECT_URL=https://minio.ginnoir.com
|
||||
- MINIO_IDENTITY_OPENID_CONFIG_URL=https://auth.ginnoir.com/application/o/minio/.well-known/openid-configuration
|
||||
- MINIO_IDENTITY_OPENID_CLIENT_ID=${MINIO_OIDC_CLIENT_ID}
|
||||
- MINIO_IDENTITY_OPENID_CLIENT_SECRET=${MINIO_OIDC_CLIENT_SECRET}
|
||||
- MINIO_IDENTITY_OPENID_SCOPES=openid,profile,email
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -sf http://localhost:9000/minio/health/live || exit 1"]
|
||||
interval: 30s
|
||||
|
||||
@@ -9,6 +9,8 @@ POSTGRES_PASSWORD=postgres
|
||||
# minio (resume-minio service)
|
||||
MINIO_ROOT_USER=minioadmin
|
||||
MINIO_ROOT_PASSWORD=minioadmin
|
||||
MINIO_OIDC_CLIENT_ID=1jC2ChsCjdh7srOKtr9Jzv1l2RF8omrzshEpAESa
|
||||
MINIO_OIDC_CLIENT_SECRET=66KML3RcsdcStPO6FIe04cCRyLTztfUDdHcpkIVImUO6WTTHkAFtao4GfZYu9yq4NvFGWxztTv7M8uGZBi4xaVUtJr3K3GU47pLaaWwF1WMsidsRTy760QUJxsUq3i9C
|
||||
|
||||
# browserless chrome auth token (chrome reads TOKEN, the app reads PRINTER_TOKEN)
|
||||
TOKEN=chrome_token
|
||||
|
||||
Reference in New Issue
Block a user