infra/stacks/authentik/admin-services-restriction.tf
Viktor Barzin 2e50c1235c
All checks were successful
ci/woodpecker/push/default Pipeline was successful
chrome-service: grant emo shared browser access (noVNC + homelab browser CLI)
Viktor asked to give emo access to the cluster's headed Chrome so he can fill
in forms and get past anti-bot / captcha pages. emo was deliberately locked
out of chrome-service (noVNC Authentik allowlist was Viktor-only + his
power-user RBAC has no pods/portforward). Viktor's explicit decision: SHARE
his existing browser rather than stand up an isolated per-user instance,
accepting that emo can therefore reach Viktor's warmed logged-in sessions
(CDP has no per-context auth, so the single shared persistent profile is
reachable by anyone who can drive the browser). emo's CLI use is hands-off
(his agent can run it unattended).

- authentik: add emo (emil.barzin / emil.barzin@gmail.com) to CHROME_ALLOWED
  so the admin-services-restriction policy admits him to chrome.viktorbarzin.me
  (noVNC). Reverses the prior Viktor-only lock; comment updated to record why.
- chrome-service/rbac.tf (new): emo-browser ServiceAccount + long-lived token
  (dashboard-sa.tf pattern), a chrome-service-portforward Role granting
  pods/portforward, and a cluster read-only binding (oidc-power-user-readonly)
  so the SA can resolve the Service and emo's normal read access doesn't regress.
- t3-provision-users.sh: install_browser_kubeconfig installs a dual-context
  kubeconfig for any user with a <user>-browser SA — SA token as the default
  context (non-interactive, works headless), personal OIDC retained as the
  oidc@homelab named context. emo's OIDC-only kubeconfig can't authenticate the
  headless agent session that homelab browser needs.
- docs/architecture/chrome-service.md: document the shared-browser multi-user
  access model, the session-exposure trade-off, and how to grant/revoke a user.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 15:20:07 +00:00

82 lines
3.6 KiB
HCL

# Catch-all forward-auth restriction: gate the admin-only hostnames to the
# "Home Server Admins" group. Bound to the "Domain wide catch all" application
# (binding stays UI-managed; only the expression is adopted here).
#
# Adopted into Terraform 2026-06-04 to add a carve-out: the Kubernetes Dashboard
# (k8s.viktorbarzin.me) ALSO admits the kubernetes-* RBAC groups, so
# namespace-owners (e.g. gheorghe) can reach the dashboard login page. The
# dashboard itself enforces per-namespace access via the pasted ServiceAccount
# token (stacks/rbac/modules/rbac/dashboard-sa.tf) — this policy only controls
# who reaches the page. All other admin-only hosts remain Home-Server-Admins-only.
import {
to = authentik_policy_expression.admin_services_restriction
id = "07a11b85-8f37-4844-aebb-ac9c112ec87c"
}
resource "authentik_policy_expression" "admin_services_restriction" {
name = "admin-services-restriction"
expression = trimspace(<<-EOT
ADMIN_ONLY_HOSTS = {
"terminal.viktorbarzin.me",
"frigate.viktorbarzin.me",
"netbox.viktorbarzin.me",
"trading.viktorbarzin.me",
"speedtest.viktorbarzin.me",
"meshcentral.viktorbarzin.me",
"k8s.viktorbarzin.me",
"dashy.viktorbarzin.me",
"prowlarr.viktorbarzin.me",
"qbittorrent.viktorbarzin.me",
"listenarr.viktorbarzin.me",
"shlink.viktorbarzin.me",
"openclaw.viktorbarzin.me",
"openlobster.viktorbarzin.me",
"wealthfolio.viktorbarzin.me",
}
ADMIN_GROUP = "Home Server Admins"
# The K8s Dashboard additionally admits the Kubernetes RBAC groups. Access
# to the page is not the security boundary — the pasted ServiceAccount token
# is (per-namespace admin + cluster read-only). See dashboard-sa.tf.
K8S_DASHBOARD_HOST = "k8s.viktorbarzin.me"
K8S_DASHBOARD_GROUPS = [
"Home Server Admins",
"kubernetes-admins",
"kubernetes-power-users",
"kubernetes-namespace-owners",
]
host = request.context.get("host", "")
# chrome-service noVNC (chrome.viktorbarzin.me) exposes LIVE logged-in browser
# sessions from the SHARED persistent profile. Originally Viktor-only.
# 2026-06-28 (Viktor's explicit decision): emo SHARES Viktor's browser, so emo
# (emil.barzin / emil.barzin@gmail.com) is allowed in for noVNC form-filling +
# captcha solving. Trade-off accepted: emo can therefore reach Viktor's warmed
# sessions (the CLI half is the emo-browser ServiceAccount in
# stacks/chrome-service/rbac.tf). akadmin kept as break-glass. Match username OR
# email so neither attribute alone can lock anyone out.
CHROME_ALLOWED = {"akadmin", "akadmin@viktorbarzin.me", "vbarzin@gmail.com", "emil.barzin", "emil.barzin@gmail.com"}
if host == "chrome.viktorbarzin.me":
return request.user.username in CHROME_ALLOWED or request.user.email in CHROME_ALLOWED
# t3 Workstation edge gate: only members of "T3 Users" may reach t3.
# Placed BEFORE the ADMIN_ONLY_HOSTS early-return (t3 is intentionally not in
# that set — it must not require Home-Server-Admins, just T3 Users membership).
if host == "t3.viktorbarzin.me":
return ak_is_group_member(request.user, name="T3 Users")
# Not an admin-only host: allow any authenticated user.
if host not in ADMIN_ONLY_HOSTS:
return True
# K8s Dashboard: allow admins OR any Kubernetes RBAC group.
if host == K8S_DASHBOARD_HOST:
return any(ak_is_group_member(request.user, name=g) for g in K8S_DASHBOARD_GROUPS)
# Every other admin-only host: Home Server Admins only.
return ak_is_group_member(request.user, name=ADMIN_GROUP)
EOT
)
}