chrome-service: grant emo shared browser access (noVNC + homelab browser CLI)
All checks were successful
ci/woodpecker/push/default Pipeline was successful
All checks were successful
ci/woodpecker/push/default Pipeline was successful
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>
This commit is contained in:
parent
50077b43d4
commit
2e50c1235c
4 changed files with 214 additions and 8 deletions
95
stacks/chrome-service/rbac.tf
Normal file
95
stacks/chrome-service/rbac.tf
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
# emo's hands-off "homelab browser" credential + chrome-service port-forward RBAC.
|
||||
#
|
||||
# Access decision (2026-06-28, Viktor's explicit call): emo SHARES Viktor's single
|
||||
# chrome-service browser rather than getting an isolated instance. The noVNC half of
|
||||
# that grant is the Authentik allowlist in
|
||||
# stacks/authentik/admin-services-restriction.tf (CHROME_ALLOWED); THIS file is the
|
||||
# CLI half — it lets emo's `homelab browser` reach the headed Chrome over CDP.
|
||||
#
|
||||
# `homelab browser` shells out to `kubectl port-forward -n chrome-service svc/chrome-service`
|
||||
# (cli/browser.go). emo's normal kubeconfig is interactive-OIDC-only (kubelogin) and
|
||||
# can't authenticate a headless agent session, and his power-user tier has no
|
||||
# pods/portforward. So we mint a dedicated ServiceAccount with a long-lived token
|
||||
# (the dashboard-sa.tf pattern) that the devvm provisioner installs as emo's DEFAULT
|
||||
# kubeconfig context (scripts/t3-provision-users.sh install_browser_kubeconfig); his
|
||||
# personal OIDC login stays available as the `oidc@homelab` named context.
|
||||
#
|
||||
# TRADE-OFF (accepted): CDP access == full control of the shared browser, including
|
||||
# the persistent profile (browser.contexts[0]) where Viktor's warmed logins live.
|
||||
# CDP has no per-context auth, so this SA can reach Viktor's sessions. That is inherent
|
||||
# to sharing one browser (the isolated per-user instance was declined).
|
||||
# See docs/architecture/chrome-service.md "Multi-user access".
|
||||
|
||||
resource "kubernetes_service_account" "emo_browser" {
|
||||
metadata {
|
||||
name = "emo-browser"
|
||||
namespace = kubernetes_namespace.chrome_service.metadata[0].name
|
||||
}
|
||||
}
|
||||
|
||||
# Long-lived (non-expiring) token for the SA — the devvm provisioner reads this and
|
||||
# writes it into emo's kubeconfig. Same pattern as stacks/rbac/.../dashboard-sa.tf.
|
||||
resource "kubernetes_secret" "emo_browser_token" {
|
||||
metadata {
|
||||
name = "emo-browser-token"
|
||||
namespace = kubernetes_namespace.chrome_service.metadata[0].name
|
||||
annotations = {
|
||||
"kubernetes.io/service-account.name" = kubernetes_service_account.emo_browser.metadata[0].name
|
||||
}
|
||||
}
|
||||
type = "kubernetes.io/service-account-token"
|
||||
wait_for_service_account_token = true
|
||||
}
|
||||
|
||||
# The ONLY verb emo's SA lacks for `kubectl port-forward svc/chrome-service`: the
|
||||
# port-forward subresource. (get/list of pods + services + endpoints comes from the
|
||||
# cluster-read binding below.) Namespace-scoped to chrome-service.
|
||||
resource "kubernetes_role" "browser_portforward" {
|
||||
metadata {
|
||||
name = "chrome-service-portforward"
|
||||
namespace = kubernetes_namespace.chrome_service.metadata[0].name
|
||||
}
|
||||
rule {
|
||||
api_groups = [""]
|
||||
resources = ["pods/portforward"]
|
||||
verbs = ["create"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_role_binding" "emo_browser_portforward" {
|
||||
metadata {
|
||||
name = "emo-browser-portforward"
|
||||
namespace = kubernetes_namespace.chrome_service.metadata[0].name
|
||||
}
|
||||
role_ref {
|
||||
api_group = "rbac.authorization.k8s.io"
|
||||
kind = "Role"
|
||||
name = kubernetes_role.browser_portforward.metadata[0].name
|
||||
}
|
||||
subject {
|
||||
kind = "ServiceAccount"
|
||||
name = kubernetes_service_account.emo_browser.metadata[0].name
|
||||
namespace = kubernetes_namespace.chrome_service.metadata[0].name
|
||||
}
|
||||
}
|
||||
|
||||
# Cluster-wide read-only (NO secrets), mirroring emo's power-user OIDC access, bound
|
||||
# to the SA. Needed because the SA becomes emo's DEFAULT kubectl context, so without
|
||||
# this his everyday `kubectl get ...` would regress — AND port-forward itself needs
|
||||
# get/list on services + pods + endpoints (all covered by oidc-power-user-readonly).
|
||||
# That ClusterRole is defined in stacks/rbac (modules/rbac/main.tf); referenced by name.
|
||||
resource "kubernetes_cluster_role_binding" "emo_browser_readonly" {
|
||||
metadata {
|
||||
name = "emo-browser-readonly"
|
||||
}
|
||||
role_ref {
|
||||
api_group = "rbac.authorization.k8s.io"
|
||||
kind = "ClusterRole"
|
||||
name = "oidc-power-user-readonly"
|
||||
}
|
||||
subject {
|
||||
kind = "ServiceAccount"
|
||||
name = kubernetes_service_account.emo_browser.metadata[0].name
|
||||
namespace = kubernetes_namespace.chrome_service.metadata[0].name
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue