From f355c4c94ce22575a948ecd0027766ba4a9f1c77 Mon Sep 17 00:00:00 2001 From: Konstantin Renner Date: Fri, 10 Jul 2026 13:29:18 +0200 Subject: [PATCH] shelly-manager: bind HTTP to the pod address, not 0.0.0.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Authentik gate added in #138 only guards the Traefik ingress path. shelly-manager also has a Multus macvlan (net1) on the IoT VLAN for mDNS discovery, it binds 0.0.0.0 (image Dockerfile default ASPNETCORE_URLS=http://0.0.0.0:8080), and macvlan traffic bypasses Cilium policy entirely. So the unauthenticated admin UI was reachable directly on the IoT VLAN, past the gate. Verified 2026-07-10 from edge-1 (on v30iot): GET http://10.2.30.122:8080/ -> 200, full Shelly Manager UI An untrusted IoT client could rewrite Shelly device config, push scripts, and read stored device admin passwords — exactly what the gate was meant to prevent. The macvlan predates the gate; #138 closed the front door and left this side door open. Pin Kestrel to the pod's eth0 address via the downward API (ASPNETCORE_URLS=http://$(POD_IP):8080, POD_IP=status.podIP) so :8080 is served only on the cluster pod CIDR, not on net1. Everything that should reach the app still does — the IPv4 ClusterIP Service, the Authentik outpost through it, and the kubelet httpGet probes all target the pod IP. The Service is single-stack IPv4, so no IPv6 consumer is lost. mDNS discovery is unaffected: the resolver is a separate ZeroconfResolver socket set that binds its own multicast sockets on net1, independent of Kestrel (src/ShellyManager.Web/Shelly/MdnsResolver.cs). net1 keeps L2 presence for `_shelly._tcp.local.` browsing; it just no longer carries the HTTP server. Also corrects the stale "static IP" NAD comment — net1 is a DHCP lease since #135. Verified: kubectl kustomize builds; rendered env has POD_IP (downward API) and ASPNETCORE_URLS=http://$(POD_IP):8080; probes still httpGet the named port, which resolves to the pod IP Kestrel now binds. Post-merge checks to run: discovery still lists all devices, GET to net1:8080 from the IoT VLAN now fails, gate still serves via Traefik, pod stays Ready. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../shelly-manager-deployment.yaml | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/kubernetes/apps/home-assistant/shelly-manager-deployment.yaml b/kubernetes/apps/home-assistant/shelly-manager-deployment.yaml index 619fb362..5025b34a 100644 --- a/kubernetes/apps/home-assistant/shelly-manager-deployment.yaml +++ b/kubernetes/apps/home-assistant/shelly-manager-deployment.yaml @@ -17,11 +17,23 @@ spec: labels: app.kubernetes.io/name: shelly-manager annotations: - # Multus attaches `net1` on v30iot with a static IP (see + # Multus attaches `net1` on v30iot with a DHCP-leased address (see # shelly-manager-network-attachment.yaml). The mDNS resolver # inside the app needs L2 presence on the IoT VLAN to browse # for `_shelly._tcp.local.` — multicast doesn't cross the L3 # boundary between the cluster pod CIDR and v30iot. + # + # But net1 must carry ONLY multicast, not the admin HTTP server. + # The app has no auth of its own; its ingress is gated by Authentik + # (shelly-manager-gate). That gate only guards the Traefik path — + # macvlan traffic bypasses Cilium policy entirely, so a `0.0.0.0` + # bind exposes the unauthenticated UI on :8080 directly to every + # device on the IoT VLAN (verified 2026-07-10: GET http://:8080/ from the VLAN returned 200 and the full UI). See the + # ASPNETCORE_URLS binding below, which pins Kestrel to the pod + # (eth0) address so :8080 is not reachable over net1. The mDNS + # resolver is a separate ZeroconfResolver socket set and is + # unaffected. k8s.v1.cni.cncf.io/networks: shelly-manager-iot spec: # eth0 (Cilium-managed pod network) is still the primary — it @@ -44,6 +56,22 @@ spec: env: - name: TZ value: Europe/Berlin + # Pin Kestrel to the pod's eth0 (cluster CIDR) address, not + # 0.0.0.0. The image's Dockerfile default is + # ASPNETCORE_URLS=http://0.0.0.0:8080, which also binds the + # macvlan net1 interface on the IoT VLAN — exposing the + # unauthenticated UI to that untrusted segment past the + # Authentik gate. POD_IP is the eth0 IPv4 (status.podIP); the + # Service, the Authentik outpost, and the kubelet httpGet + # probes all reach the app on that address. mDNS discovery is + # unaffected: ZeroconfResolver binds its own multicast sockets + # on net1 independently of Kestrel. + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + - name: ASPNETCORE_URLS + value: "http://$(POD_IP):8080" volumeMounts: - name: data mountPath: /data -- 2.54.0