diff --git a/docs/architecture/chrome-service.md b/docs/architecture/chrome-service.md index 74e100cf..9d8901d3 100644 --- a/docs/architecture/chrome-service.md +++ b/docs/architecture/chrome-service.md @@ -89,14 +89,24 @@ PVC) and reuses it on subsequent restarts. ## Network controls -- **`kubernetes_network_policy_v1.ws_ingress`** — only namespaces labelled - `chrome-service.viktorbarzin.me/client = "true"` (plus an explicit - fallback for `f1-stream` by `kubernetes.io/metadata.name`) can reach - TCP/3000. +- **`kubernetes_network_policy_v1.ws_ingress`** — two separate ingress + rules on the same policy: + - **TCP/3000** (Playwright WS): only namespaces labelled + `chrome-service.viktorbarzin.me/client = "true"` (plus an explicit + fallback for `f1-stream` by `kubernetes.io/metadata.name`). + - **TCP/6080** (noVNC HTTP+WS): only the `traefik` namespace, since + the public-facing path is `chrome.viktorbarzin.me` ingress → + Traefik → sidecar. Authentik forward-auth still gates external + access at the Traefik layer. - **WS port 3000** is internal-only (no ingress, no Cloudflare DNS). -- **HTTP port 80** (sidecar `nginxinc/nginx-unprivileged:alpine`) serves - a static health stub at `chrome.viktorbarzin.me`, Authentik-gated. - Lets a human confirm pod liveness without spinning a browser. +- **noVNC sidecar** (`forgejo.viktorbarzin.me/viktor/chrome-service-novnc`) + exposes a live HTML5 view of the headed Chromium session via + `x11vnc` (connected to Xvfb on `localhost:6099`) bridged to + `websockify` on port 6080. Service `chrome` maps :80 → :6080 and is + exposed via `ingress_factory` at `chrome.viktorbarzin.me`, + Authentik-gated. Both static page and WebSocket upgrade share the + same path — Cloudflare proxy, Cloudflared tunnel, Traefik, and + Authentik forward-auth all preserve `Upgrade: websocket`. ## Adding a new caller diff --git a/stacks/chrome-service/main.tf b/stacks/chrome-service/main.tf index aae58be8..13ab49ee 100644 --- a/stacks/chrome-service/main.tf +++ b/stacks/chrome-service/main.tf @@ -21,9 +21,9 @@ resource "kubernetes_namespace" "chrome_service" { metadata { name = local.namespace labels = { - "istio-injection" = "disabled" - tier = local.tiers.aux - "chrome-service.viktorbarzin.me/server" = "true" + "istio-injection" = "disabled" + tier = local.tiers.aux + "chrome-service.viktorbarzin.me/server" = "true" } } lifecycle { @@ -366,10 +366,14 @@ module "ingress" { } } -# --- NetworkPolicy: TCP/3000 ingress only from labelled client namespaces. +# --- NetworkPolicy: scoped ingress. +# - TCP/3000 (Playwright WS): only from labelled client namespaces. +# - TCP/6080 (noVNC HTTP+WS): only from the traefik namespace, since the +# public-facing path is `chrome.viktorbarzin.me` ingress → Traefik → +# sidecar. Authentik forward-auth still gates external access at the +# Traefik layer. # The cluster has no default-deny, so this NP only takes effect inside -# chrome-service ns — pods elsewhere remain unaffected. Callers opt in by -# labelling their namespace `chrome-service.viktorbarzin.me/client = "true"`. +# chrome-service ns — pods elsewhere remain unaffected. resource "kubernetes_network_policy_v1" "ws_ingress" { metadata { name = "chrome-service-ws-ingress" @@ -402,6 +406,19 @@ resource "kubernetes_network_policy_v1" "ws_ingress" { protocol = "TCP" } } + ingress { + from { + namespace_selector { + match_labels = { + "kubernetes.io/metadata.name" = "traefik" + } + } + } + ports { + port = "6080" + protocol = "TCP" + } + } } }