96 lines
4 KiB
Terraform
96 lines
4 KiB
Terraform
|
|
# 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
|
||
|
|
}
|
||
|
|
}
|