feat(home-assistant): gate vscode/code-server behind Authentik passkey step-up
code-server (the HA /config editor sidecar) runs with --auth=none, as root, mounting the entire /config incl. plaintext secrets.yaml, .storage, and the git PAT in the PVC remote URL. Until now the only control on it was network reachability of vscode.home.renner.uno — any LAN client could read every HA secret and edit config. Put it behind Authentik using the existing paperless-gate proxy-outpost pattern: - New Authentik proxy provider + application + dedicated outpost (ak-outpost-vscode-gate) in mode: proxy. authorization_flow is the passkey-stepup-authorization flow (same one Homeassistant/Immich/ Audiobookshelf/Netbird use), so every authorize requires a passkey. - New "Home Assistant Config Editors" group (git-owned membership, k.renner only) bound to the app — explicit allowlist for who can read HA secrets via the editor. - vscode Ingress now backends the gate outpost (ExternalName mirror) instead of code-server directly. - home-assistant CNP tightened: traefik no longer allowed to :8080; only the vscode-gate outpost pod may reach code-server. Matching scoped egress CNP added in the authentik namespace. - code-server is a service-worker app; skip_path_regex carves out its static/manifest/SW tree to avoid the credentials:'omit' redirect loop that forced retirement of the music-assistant gate. Exact skip set to be confirmed post-deploy (see PR checklist). Verified: kubectl kustomize builds clean for home-assistant, authentik, and network-policies. The Authentik proxy flow + code-server PWA behaviour require a browser passkey login test after apply. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -214,6 +214,120 @@ data:
|
||||
slug: paperless-proxy
|
||||
state: absent
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# vscode config-editor gate
|
||||
#
|
||||
# code-server (the HA /config editor sidecar) runs with --auth=none,
|
||||
# as root, mounting the whole HA /config incl. plaintext secrets.yaml
|
||||
# and .storage. This gate moves it behind Authentik with a passkey
|
||||
# step-up, mirroring the paperless-gate proxy-outpost pattern above:
|
||||
# the dedicated ak-outpost-vscode-gate terminates the request,
|
||||
# enforces the session, and forwards to the code-server Service.
|
||||
# Traefik only ever routes vscode.home.renner.uno to the outpost; the
|
||||
# home-assistant CNP is tightened so ONLY the gate outpost may reach
|
||||
# :8080 (see home-assistant-ns.yaml + vscode-gate-egress.yaml).
|
||||
# ------------------------------------------------------------------
|
||||
- id: vscode-config-editors-group
|
||||
model: authentik_core.group
|
||||
identifiers:
|
||||
name: Home Assistant Config Editors
|
||||
state: present
|
||||
attrs:
|
||||
is_superuser: false
|
||||
# Git owns membership for this gate — the group's entire purpose
|
||||
# is to be the explicit allowlist of who can read HA secrets via
|
||||
# the editor. Add/remove members HERE, not in the UI (blueprint
|
||||
# apply is authoritative for this M2M and will revert UI edits).
|
||||
users:
|
||||
- !Find [authentik_core.user, [username, k.renner]]
|
||||
|
||||
- id: vscode-gate-provider
|
||||
model: authentik_providers_proxy.proxyprovider
|
||||
identifiers:
|
||||
name: vscode-gate
|
||||
state: present
|
||||
attrs:
|
||||
# Passkey step-up on every authorize — the same flow the
|
||||
# Homeassistant, Immich, Audiobookshelf and Netbird OAuth2
|
||||
# providers use (passkey-stepup.yaml blueprint). Referenced by
|
||||
# !Find (not !KeyOf) because that flow is defined in a different
|
||||
# blueprint document within this ConfigMap.
|
||||
authorization_flow: !Find [authentik_flows.flow, [slug, passkey-stepup-authorization]]
|
||||
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
|
||||
# Proxy mode: the outpost terminates the request and forwards to
|
||||
# code-server, moving the trust boundary onto the gate. Traefik
|
||||
# never talks to code-server directly.
|
||||
mode: proxy
|
||||
internal_host: http://home-assistant-vscode.home-assistant.svc.cluster.local
|
||||
internal_host_ssl_validation: false
|
||||
external_host: https://vscode.home.renner.uno
|
||||
cookie_domain: renner.uno
|
||||
access_token_validity: hours=24
|
||||
refresh_token_validity: days=30
|
||||
intercept_header_auth: true
|
||||
# code-server is a service-worker app: <link rel="manifest"> and
|
||||
# the service worker are fetched with credentials:'omit', which
|
||||
# turned into a /authorize redirect loop and forced the RETIREMENT
|
||||
# of the music-assistant gate (see comment above). Carve its
|
||||
# static/manifest/SW tree out so those cookieless fetches pass
|
||||
# through instead of 302-looping; the editor's actual content
|
||||
# (the /config tree) is served over authenticated websocket/API
|
||||
# paths, which stay gated.
|
||||
# NOTE: the exact skip set is code-server-version-specific and
|
||||
# MUST be confirmed post-deploy against the real redirect-loop
|
||||
# behaviour (see the PR validation checklist).
|
||||
skip_path_regex: ^/(_static/|manifest\.json|.*\.webmanifest|.*serviceWorker\.js).*
|
||||
|
||||
- model: authentik_core.application
|
||||
identifiers:
|
||||
slug: vscode-gate
|
||||
state: present
|
||||
attrs:
|
||||
name: HA Config Editor (Gate)
|
||||
slug: vscode-gate
|
||||
provider: !KeyOf vscode-gate-provider
|
||||
meta_launch_url: https://vscode.home.renner.uno
|
||||
meta_description: "Passkey-gated VS Code editor for Home Assistant /config"
|
||||
|
||||
# Only the Home Assistant Config Editors group passes the gate.
|
||||
- model: authentik_policies.policybinding
|
||||
identifiers:
|
||||
target: !Find [authentik_core.application, [slug, vscode-gate]]
|
||||
group: !KeyOf vscode-config-editors-group
|
||||
order: 0
|
||||
state: present
|
||||
attrs:
|
||||
enabled: true
|
||||
negate: false
|
||||
failure_result: false
|
||||
|
||||
- model: authentik_outposts.outpost
|
||||
identifiers:
|
||||
name: vscode-gate
|
||||
state: present
|
||||
attrs:
|
||||
type: proxy
|
||||
service_connection: !KeyOf k8s-conn
|
||||
providers:
|
||||
- !KeyOf vscode-gate-provider
|
||||
config:
|
||||
log_level: info
|
||||
authentik_host: "http://authentik-server.authentik.svc.cluster.local"
|
||||
authentik_host_insecure: false
|
||||
authentik_host_browser: "https://auth.renner.uno/"
|
||||
refresh_interval: "minutes=5"
|
||||
container_image: null
|
||||
object_naming_template: "ak-outpost-%(name)s"
|
||||
kubernetes_namespace: authentik
|
||||
kubernetes_replicas: 1
|
||||
kubernetes_service_type: ClusterIP
|
||||
kubernetes_disabled_components: ["ingress"]
|
||||
kubernetes_image_pull_secrets: []
|
||||
kubernetes_ingress_annotations: {}
|
||||
kubernetes_ingress_class_name: null
|
||||
kubernetes_ingress_secret_name: "authentik-outpost-tls"
|
||||
kubernetes_json_patches: null
|
||||
|
||||
- model: authentik_outposts.outpost
|
||||
identifiers:
|
||||
name: authentik Embedded Outpost
|
||||
|
||||
@@ -7,6 +7,7 @@ resources:
|
||||
- helmrelease.yaml
|
||||
- paperless-gate-ingress.yaml
|
||||
- paperless-gate-egress.yaml
|
||||
- vscode-gate-egress.yaml
|
||||
- outpost-servicemonitors.yaml
|
||||
- ldap-outpost-cert.yaml
|
||||
- ldap-outpost-lb.yaml
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
---
|
||||
# Allow the dedicated vscode-gate outpost to egress into the
|
||||
# home-assistant namespace and reach the code-server sidecar (:8080) on
|
||||
# the HA pod. Without this, the authentik-namespace baseline CNP
|
||||
# (kubernetes/infrastructure/network-policies/authentik-ns.yaml) has no
|
||||
# egress rule to home-assistant, so the gate outpost's forwarded requests
|
||||
# are dropped. Scoped to the gate outpost's pod label so no other
|
||||
# authentik component can reach code-server. Mirrors paperless-gate-egress.yaml.
|
||||
apiVersion: cilium.io/v2
|
||||
kind: CiliumNetworkPolicy
|
||||
metadata:
|
||||
name: vscode-gate-egress
|
||||
namespace: authentik
|
||||
spec:
|
||||
endpointSelector:
|
||||
matchLabels:
|
||||
goauthentik.io/outpost-name: vscode-gate
|
||||
egress:
|
||||
- toEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": home-assistant
|
||||
app.kubernetes.io/name: home-assistant
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "8080"
|
||||
protocol: TCP
|
||||
@@ -7,6 +7,7 @@ resources:
|
||||
- service.yaml
|
||||
- bgp-service.yaml
|
||||
- vscode-service.yaml
|
||||
- vscode-gate-service.yaml
|
||||
- ingress.yaml
|
||||
- vscode-ingress.yaml
|
||||
- prometheus-token.sops.yaml
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
---
|
||||
# ExternalName mirror so the home-assistant-namespace vscode Ingress can
|
||||
# backend to the vscode-gate outpost that Authentik deploys into the
|
||||
# authentik namespace (cross-namespace Service refs aren't supported in a
|
||||
# standard Ingress). Mirrors the ak-outpost-paperless-gate ExternalName in
|
||||
# kubernetes/apps/paperless/paperless.yaml.
|
||||
#
|
||||
# Traffic path:
|
||||
# browser → traefik (vscode.home.renner.uno)
|
||||
# → ak-outpost-vscode-gate (ExternalName → authentik ns)
|
||||
# → outpost validates session + passkey step-up
|
||||
# → http://home-assistant-vscode.home-assistant.svc:80 (code-server :8080)
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ak-outpost-vscode-gate
|
||||
namespace: home-assistant
|
||||
spec:
|
||||
type: ExternalName
|
||||
externalName: ak-outpost-vscode-gate.authentik.svc.cluster.local
|
||||
ports:
|
||||
- port: 9000
|
||||
targetPort: 9000
|
||||
@@ -1,3 +1,14 @@
|
||||
---
|
||||
# vscode.home.renner.uno — the code-server sidecar that edits HA /config.
|
||||
#
|
||||
# code-server runs with --auth=none as root and mounts the entire /config
|
||||
# (plaintext secrets.yaml, .storage, the git PAT in the PVC remote URL).
|
||||
# Traefik no longer routes to code-server directly: this Ingress backends
|
||||
# the Authentik vscode-gate proxy outpost (ExternalName → authentik ns),
|
||||
# which enforces a passkey step-up and forwards to code-server only after
|
||||
# the session is validated. The home-assistant CNP is tightened so ONLY
|
||||
# that outpost pod may reach :8080 (see home-assistant-ns.yaml). Pattern
|
||||
# mirrors kubernetes/apps/paperless (paperless.renner.uno → gate outpost).
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
@@ -15,9 +26,9 @@ spec:
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: home-assistant-vscode
|
||||
name: ak-outpost-vscode-gate
|
||||
port:
|
||||
number: 80
|
||||
number: 9000
|
||||
tls:
|
||||
- secretName: home-assistant-vscode-tls
|
||||
hosts:
|
||||
|
||||
@@ -12,8 +12,7 @@ spec:
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": home-assistant
|
||||
# Public web UI: traefik → home-assistant Service → pod:8124 (proxy sidecar)
|
||||
# VSCode editor: traefik → home-assistant-vscode Service → pod:8080 (code-server)
|
||||
# Public web UI: traefik → home-assistant Service → pod:8124 (proxy sidecar)
|
||||
# The proxy sidecar is the XFF trust anchor: HA's trusted_proxies is
|
||||
# [127.0.0.1, ::1] only, so this port-scoped rule — restricting :8124
|
||||
# to traefik — is what guarantees nothing else can present
|
||||
@@ -26,6 +25,20 @@ spec:
|
||||
- ports:
|
||||
- port: "8124"
|
||||
protocol: TCP
|
||||
# VSCode editor: code-server (:8080) is NO LONGER reached by traefik
|
||||
# directly. It sits behind the Authentik vscode-gate proxy outpost,
|
||||
# which terminates the request, enforces a passkey step-up, and
|
||||
# forwards here. Only that outpost pod (in the authentik namespace)
|
||||
# may reach :8080 — this is what turns the --auth=none/root editor
|
||||
# from "any LAN client can edit" into "authenticated editors only".
|
||||
# See kubernetes/apps/authentik/vscode-gate-egress.yaml (the matching
|
||||
# egress rule) and the vscode-gate blueprint in blueprints-configmap.yaml.
|
||||
- fromEndpoints:
|
||||
- matchLabels:
|
||||
"k8s:io.kubernetes.pod.namespace": authentik
|
||||
goauthentik.io/outpost-name: vscode-gate
|
||||
toPorts:
|
||||
- ports:
|
||||
- port: "8080"
|
||||
protocol: TCP
|
||||
# Prometheus scrape: kube-prometheus-stack hits HA's /api/prometheus.
|
||||
|
||||
Reference in New Issue
Block a user