Gate shelly-manager behind Authentik passkey step-up #138

Merged
k.renner merged 1 commits from feat/shelly-manager-authentik-gate into master 2026-07-09 11:44:48 +00:00
7 changed files with 187 additions and 2 deletions
@@ -328,6 +328,111 @@ data:
kubernetes_ingress_secret_name: "authentik-outpost-tls"
kubernetes_json_patches: null
# ------------------------------------------------------------------
# shelly-manager gate
#
# shelly-manager has no authentication of its own: anything that can
# reach :8080 can rewrite Shelly device config, push scripts, and read
# the stored per-device AdminPassword values. Until now the only
# control was reachability of shelly-manager.home.renner.uno.
#
# Same shape as the vscode-gate above: a dedicated outpost terminates
# the request, enforces a passkey step-up, and forwards to the
# shelly-manager Service. Traefik only ever routes the hostname to the
# outpost; the home-assistant CNP allows ONLY this outpost to reach
# :8080 (see home-assistant-ns.yaml + shelly-manager-gate-egress.yaml).
# ------------------------------------------------------------------
- id: shelly-managers-group
model: authentik_core.group
identifiers:
name: Shelly Managers
state: present
attrs:
is_superuser: false
# Git owns membership, as with Home Assistant Config Editors: this
# group is the explicit allowlist for who may reconfigure the
# Shellies. Add/remove members HERE, not in the UI — blueprint
# apply is authoritative for this M2M and reverts UI edits.
users:
- !Find [authentik_core.user, [username, k.renner]]
- id: shelly-manager-gate-provider
model: authentik_providers_proxy.proxyprovider
identifiers:
name: shelly-manager-gate
state: present
attrs:
authorization_flow: !Find [authentik_flows.flow, [slug, passkey-stepup-authorization]]
invalidation_flow: !Find [authentik_flows.flow, [slug, default-provider-invalidation-flow]]
mode: proxy
internal_host: http://shelly-manager.home-assistant.svc.cluster.local
internal_host_ssl_validation: false
external_host: https://shelly-manager.home.renner.uno
cookie_domain: renner.uno
access_token_validity: hours=24
refresh_token_validity: days=30
intercept_header_auth: true
# No skip_path_regex, deliberately. The music-assistant gate died
# on PWA assets fetched with `credentials: 'omit'` (manifest, service
# worker), and paperless/vscode carve those out. shelly-manager ships
# no manifest and no service worker — wwwroot is app.css, favicon.png
# and lib/ only — so every asset is a same-origin credentialed fetch
# and nothing needs to bypass the gate. It is a Blazor Web app: the
# SignalR circuit at /_blazor upgrades to a websocket carrying the
# session cookie, which the proxy outpost forwards. If the UI ever
# fails to establish its circuit, look there first, not at static
# assets.
- model: authentik_core.application
identifiers:
slug: shelly-manager-gate
state: present
attrs:
name: Shelly Manager (Gate)
slug: shelly-manager-gate
provider: !KeyOf shelly-manager-gate-provider
meta_launch_url: https://shelly-manager.home.renner.uno
meta_description: "Passkey-gated Shelly device manager"
# Only the Shelly Managers group passes the gate.
- model: authentik_policies.policybinding
identifiers:
target: !Find [authentik_core.application, [slug, shelly-manager-gate]]
group: !KeyOf shelly-managers-group
order: 0
state: present
attrs:
enabled: true
negate: false
failure_result: false
- model: authentik_outposts.outpost
identifiers:
name: shelly-manager-gate
state: present
attrs:
type: proxy
service_connection: !KeyOf k8s-conn
providers:
- !KeyOf shelly-manager-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
@@ -8,6 +8,7 @@ resources:
- paperless-gate-ingress.yaml
- paperless-gate-egress.yaml
- vscode-gate-egress.yaml
- shelly-manager-gate-egress.yaml
- outpost-servicemonitors.yaml
- ldap-outpost-cert.yaml
- ldap-outpost-lb.yaml
@@ -0,0 +1,26 @@
---
# Allow the dedicated shelly-manager-gate outpost to egress into the
# home-assistant namespace and reach shelly-manager (:8080). 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 it. Mirrors vscode-gate-egress.yaml.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: shelly-manager-gate-egress
namespace: authentik
spec:
endpointSelector:
matchLabels:
goauthentik.io/outpost-name: shelly-manager-gate
egress:
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": home-assistant
app.kubernetes.io/name: shelly-manager
toPorts:
- ports:
- port: "8080"
protocol: TCP
@@ -23,6 +23,7 @@ resources:
- shelly-manager-network-attachment.yaml
- shelly-manager-deployment.yaml
- shelly-manager-service.yaml
- shelly-manager-gate-service.yaml
- shelly-manager-ingress.yaml
configMapGenerator:
# nginx config for the reverse-proxy sidecar. Hash suffix disabled so
@@ -0,0 +1,23 @@
---
# ExternalName mirror so the home-assistant-namespace shelly-manager Ingress
# can backend to the shelly-manager-gate outpost that Authentik deploys into
# the authentik namespace (cross-namespace Service refs aren't supported in a
# standard Ingress). Mirrors ak-outpost-vscode-gate in
# vscode-gate-service.yaml.
#
# Traffic path:
# browser → traefik (shelly-manager.home.renner.uno)
# → ak-outpost-shelly-manager-gate (ExternalName → authentik ns)
# → outpost validates session + passkey step-up
# → http://shelly-manager.home-assistant.svc:80 (shelly-manager :8080)
apiVersion: v1
kind: Service
metadata:
name: ak-outpost-shelly-manager-gate
namespace: home-assistant
spec:
type: ExternalName
externalName: ak-outpost-shelly-manager-gate.authentik.svc.cluster.local
ports:
- port: 9000
targetPort: 9000
@@ -12,6 +12,15 @@ spec:
# SignalR 15s server-side keepalive keeps alive. If the UI starts
# losing scan-progress updates, raise idleTimeout on the websecure
# entrypoint in kubernetes/infrastructure/traefik/helmrelease.yaml.
#
# Traefik no longer routes to shelly-manager directly. The app has no
# authentication of its own — anything reaching :8080 can rewrite Shelly
# config, push scripts, and read stored device AdminPasswords — so this
# Ingress backends the Authentik shelly-manager-gate proxy outpost
# (ExternalName → authentik ns), which enforces a passkey step-up and
# forwards only after the session is validated. The home-assistant CNP
# allows ONLY that outpost to reach :8080 (see home-assistant-ns.yaml).
# Pattern mirrors vscode-ingress.yaml.
ingressClassName: traefik
rules:
- host: shelly-manager.home.renner.uno
@@ -21,9 +30,9 @@ spec:
pathType: Prefix
backend:
service:
name: shelly-manager
name: ak-outpost-shelly-manager-gate
port:
number: 80
number: 9000
tls:
- secretName: shelly-manager-tls
hosts:
@@ -41,6 +41,26 @@ spec:
- ports:
- port: "8080"
protocol: TCP
# shelly-manager (:8080) is likewise reached only through its own
# Authentik gate outpost. Note this rule and the vscode one are
# port-identical: because this CNP's endpointSelector is {}, a
# `toPorts: 8080` grant here applies to EVERY pod in the namespace that
# listens on 8080, not just the intended one — which is exactly how
# shelly-manager silently rode on the old traefik→:8080 allow written
# for code-server, and why removing that allow in 760e187 took
# shelly-manager's ingress down with it. The `fromEndpoints` selector is
# what keeps these two apart: each gate outpost can only reach its own
# target, enforced on the egress side by the matching CNPs in the
# authentik namespace (vscode-gate-egress.yaml,
# shelly-manager-gate-egress.yaml).
- fromEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": authentik
goauthentik.io/outpost-name: shelly-manager-gate
toPorts:
- ports:
- port: "8080"
protocol: TCP
# Prometheus scrape: kube-prometheus-stack hits HA's /api/prometheus.
- fromEndpoints:
- matchLabels: