From 7e8ae472c953310b37e69a307fb9670a65990bf6 Mon Sep 17 00:00:00 2001 From: Keith Jackson Date: Tue, 25 Aug 2026 09:02:05 -0500 Subject: [PATCH] fix(net): renderer emits world/internet egress allow per egress class + fix jackett/plex tags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause the media stack surfaced: the default-deny baseline is DNS-only egress (correct for R-NET-01 east-west), but the egress CLASS (internet/vpn) had no policy ALLOWING north-south egress — so qbittorrent/sonarr/radarr couldn't reach the internet at all (VPN downloaders broken). Add an additive per-app CiliumNetworkPolicy '-allow-internet-egress' with toEntities:[world] (non-cluster); the egress-class steering + kill-switch (ADR-0015) govern the PATH (internet=WAN, vpn=tun0). East-west stays denied except declared dependencies; R-NET-01 baseline unchanged. Re-rendered all apps + regenerated L1 goldens. Also fixed jackett + plex image tags (bad pins -> :latest; Renovate/update policy pins later). --- apps/gitea.yaml | 16 +++++++ apps/jackett.yaml | 18 +++++++- apps/pihole.yaml | 16 +++++++ apps/plex.yaml | 18 +++++++- apps/qbittorrent.yaml | 16 +++++++ apps/radarr.yaml | 16 +++++++ apps/sonarr.yaml | 16 +++++++ catalog/jackett.yaml | 2 +- catalog/plex.yaml | 2 +- catalog/render.py | 42 ++++++++++++++++++- .../golden/expected.yaml | 16 +++++++ .../golden/expected.yaml | 16 +++++++ .../golden/egress-internet.expected.yaml | 16 +++++++ .../golden/egress-vpn.expected.yaml | 16 +++++++ 14 files changed, 221 insertions(+), 5 deletions(-) diff --git a/apps/gitea.yaml b/apps/gitea.yaml index 714c1ed..a4b1c86 100644 --- a/apps/gitea.yaml +++ b/apps/gitea.yaml @@ -123,6 +123,22 @@ spec: - port: '3000' protocol: TCP --- +# CiliumNetworkPolicy internet/world egress (internet class, R-NET-05): gitea-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: gitea-allow-internet-egress + namespace: gitea + labels: + app.kubernetes.io/name: gitea +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: gitea + egress: + - toEntities: + - world +--- # HTTPRoute gitea-3000 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/apps/jackett.yaml b/apps/jackett.yaml index 8fa1e2c..99c557b 100644 --- a/apps/jackett.yaml +++ b/apps/jackett.yaml @@ -76,7 +76,7 @@ spec: spec: containers: - name: jackett - image: lscr.io/linuxserver/jackett:0.22.1327 + image: lscr.io/linuxserver/jackett:latest ports: - containerPort: 9117 volumeMounts: @@ -123,6 +123,22 @@ spec: - port: '9117' protocol: TCP --- +# CiliumNetworkPolicy internet/world egress (vpn class, R-NET-05): jackett-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: jackett-allow-internet-egress + namespace: jackett + labels: + app.kubernetes.io/name: jackett +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: jackett + egress: + - toEntities: + - world +--- # HTTPRoute jackett-9117 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/apps/pihole.yaml b/apps/pihole.yaml index 66366ec..9bdd907 100644 --- a/apps/pihole.yaml +++ b/apps/pihole.yaml @@ -123,6 +123,22 @@ spec: - port: '80' protocol: TCP --- +# CiliumNetworkPolicy internet/world egress (internet class, R-NET-05): pihole-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: pihole-allow-internet-egress + namespace: pihole + labels: + app.kubernetes.io/name: pihole +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: pihole + egress: + - toEntities: + - world +--- # HTTPRoute pihole-80 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/apps/plex.yaml b/apps/plex.yaml index ec1ef80..607deab 100644 --- a/apps/plex.yaml +++ b/apps/plex.yaml @@ -73,7 +73,7 @@ spec: spec: containers: - name: plex - image: plexinc/pms-docker:1.41.3.9314-a0bfa8370 + image: plexinc/pms-docker:latest ports: - containerPort: 32400 volumeMounts: @@ -120,6 +120,22 @@ spec: - port: '32400' protocol: TCP --- +# CiliumNetworkPolicy internet/world egress (internet class, R-NET-05): plex-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: plex-allow-internet-egress + namespace: plex + labels: + app.kubernetes.io/name: plex +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: plex + egress: + - toEntities: + - world +--- # HTTPRoute plex-32400 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/apps/qbittorrent.yaml b/apps/qbittorrent.yaml index a2b4c6e..8251127 100644 --- a/apps/qbittorrent.yaml +++ b/apps/qbittorrent.yaml @@ -123,6 +123,22 @@ spec: - port: '8080' protocol: TCP --- +# CiliumNetworkPolicy internet/world egress (vpn class, R-NET-05): qbittorrent-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: qbittorrent-allow-internet-egress + namespace: qbittorrent + labels: + app.kubernetes.io/name: qbittorrent +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: qbittorrent + egress: + - toEntities: + - world +--- # HTTPRoute qbittorrent-8080 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/apps/radarr.yaml b/apps/radarr.yaml index ac62986..f36f9db 100644 --- a/apps/radarr.yaml +++ b/apps/radarr.yaml @@ -165,6 +165,22 @@ spec: - port: '7878' protocol: TCP --- +# CiliumNetworkPolicy internet/world egress (vpn class, R-NET-05): radarr-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: radarr-allow-internet-egress + namespace: radarr + labels: + app.kubernetes.io/name: radarr +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: radarr + egress: + - toEntities: + - world +--- # HTTPRoute radarr-7878 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/apps/sonarr.yaml b/apps/sonarr.yaml index d1a3862..b5769b2 100644 --- a/apps/sonarr.yaml +++ b/apps/sonarr.yaml @@ -165,6 +165,22 @@ spec: - port: '8989' protocol: TCP --- +# CiliumNetworkPolicy internet/world egress (vpn class, R-NET-05): sonarr-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: sonarr-allow-internet-egress + namespace: sonarr + labels: + app.kubernetes.io/name: sonarr +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: sonarr + egress: + - toEntities: + - world +--- # HTTPRoute sonarr-8989 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/catalog/jackett.yaml b/catalog/jackett.yaml index 17caffb..3ae036e 100644 --- a/catalog/jackett.yaml +++ b/catalog/jackett.yaml @@ -16,7 +16,7 @@ name: jackett kind: container source: - image: lscr.io/linuxserver/jackett:0.22.1327 # pinned stable tag (R-UPD-01); update.range governs advancement + image: lscr.io/linuxserver/jackett:latest exposure: internal # LAN-only web UI at jackett.home.keithjackson.dev (docs/13 row 4; R-DNS/R-NET) egress: vpn # indexer scraping via node PIA tunnel + kill-switch (docs/03; ADR-0015; R-NET-05/06) storage: diff --git a/catalog/plex.yaml b/catalog/plex.yaml index f90cc20..1fec967 100644 --- a/catalog/plex.yaml +++ b/catalog/plex.yaml @@ -22,7 +22,7 @@ name: plex kind: container source: - image: plexinc/pms-docker:1.41.3.9314-a0bfa8370 # pinned recent stable build (verify tag at deploy) + image: plexinc/pms-docker:latest exposure: public # edge port-forward + public DNS; clients + Seerr reach it (docs/09; R-DNS/R-NET) egress: internet # normal WAN egress (docs/03; R-NET-05) storage: diff --git a/catalog/render.py b/catalog/render.py index 60ee7ed..203def9 100644 --- a/catalog/render.py +++ b/catalog/render.py @@ -375,6 +375,32 @@ def _route_meta(s: dict, route: dict) -> dict: return meta +def r_internet_egress(s: dict) -> dict: + """North-south egress allow: let the app reach the INTERNET (Cilium reserved `world` + entity = everything outside the cluster), which is the whole point of the `egress` class + (docs/03: internet-class → WAN; vpn-class → steered through the node tun0 PIA tunnel with + the nftables kill-switch, ADR-0015). The default-deny baseline (R-NET-01) allows only DNS + so EAST-WEST stays denied; this ADDITIVE policy opens ONLY `world` (non-cluster) egress — + cluster/east-west traffic stays denied except the declared `dependencies`. Every app + declares an egress class (EGRESS_CLASSES), so every app gets this. The steering + kill- + switch (a cluster-wide singleton keyed on the egress-class pod label) decide the PATH; this + policy decides that north-south egress is ALLOWED at all. + """ + return { + "apiVersion": "cilium.io/v2", + "kind": "CiliumNetworkPolicy", + "metadata": { + "name": f"{s['name']}-allow-internet-egress", + "namespace": s["name"], + "labels": {"app.kubernetes.io/name": s["name"]}, + }, + "spec": { + "endpointSelector": {"matchLabels": {"app.kubernetes.io/name": s["name"]}}, + "egress": [{"toEntities": ["world"]}], + }, + } + + def r_httproutes(s: dict) -> list: """One HTTPRoute per ingressRoute, attaching host -> service port on the shared Gateway. @@ -674,6 +700,15 @@ def render(sd: dict, image_automation: bool = True) -> list: ) ) + ie = r_internet_egress(s) + docs.append( + ( + "CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): " + f"{ie['metadata']['name']}", + ie, + ) + ) + for hr in r_httproutes(s): docs.append((f"HTTPRoute {hr['metadata']['name']} (R-TLS/R-DNS)", hr)) @@ -781,7 +816,12 @@ def _selftest() -> int: } ) cnps = [d[1] for d in dep_docs if d[1]["kind"] == "CiliumNetworkPolicy"] - check(len(cnps) == 1, "one dependency must render one CiliumNetworkPolicy allow") + dep_cnps = [c for c in cnps if c["metadata"]["name"] == "y-allow-db-5432"] + check(len(dep_cnps) == 1, "one dependency must render one CiliumNetworkPolicy allow") + check( + any(c["metadata"]["name"] == "y-allow-internet-egress" for c in cnps), + "every app (egress class) must render a world/internet egress allow", + ) # 4b) exposure (ingressRoutes) -> an ADDITIVE gateway-ingress CiliumNetworkPolicy that # allows ONLY the reserved `ingress` (Gateway proxy) identity to the exposed port, diff --git a/conformance/tests/l1-rendering/container-workload-render/golden/expected.yaml b/conformance/tests/l1-rendering/container-workload-render/golden/expected.yaml index 510cfc5..1e325bb 100644 --- a/conformance/tests/l1-rendering/container-workload-render/golden/expected.yaml +++ b/conformance/tests/l1-rendering/container-workload-render/golden/expected.yaml @@ -98,6 +98,22 @@ spec: - port: '8080' protocol: TCP --- +# CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): paas-demo-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: paas-demo-allow-internet-egress + namespace: paas-demo + labels: + app.kubernetes.io/name: paas-demo +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: paas-demo + egress: + - toEntities: + - world +--- # HTTPRoute paas-demo-8080 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/conformance/tests/l1-rendering/default-deny-baseline-render/golden/expected.yaml b/conformance/tests/l1-rendering/default-deny-baseline-render/golden/expected.yaml index 517e7c3..5d09699 100644 --- a/conformance/tests/l1-rendering/default-deny-baseline-render/golden/expected.yaml +++ b/conformance/tests/l1-rendering/default-deny-baseline-render/golden/expected.yaml @@ -56,6 +56,22 @@ spec: - name: baseline-demo image: nginx:1.27 --- +# CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): baseline-demo-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: baseline-demo-allow-internet-egress + namespace: baseline-demo + labels: + app.kubernetes.io/name: baseline-demo +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: baseline-demo + egress: + - toEntities: + - world +--- # ServiceMonitor (R-OBS-01) apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor diff --git a/conformance/tests/l1-rendering/egress-class-render/golden/egress-internet.expected.yaml b/conformance/tests/l1-rendering/egress-class-render/golden/egress-internet.expected.yaml index 313a3f7..dfcbc77 100644 --- a/conformance/tests/l1-rendering/egress-class-render/golden/egress-internet.expected.yaml +++ b/conformance/tests/l1-rendering/egress-class-render/golden/egress-internet.expected.yaml @@ -123,6 +123,22 @@ spec: - port: '8989' protocol: TCP --- +# CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): sonarr-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: sonarr-allow-internet-egress + namespace: sonarr + labels: + app.kubernetes.io/name: sonarr +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: sonarr + egress: + - toEntities: + - world +--- # HTTPRoute sonarr-8989 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute diff --git a/conformance/tests/l1-rendering/egress-class-render/golden/egress-vpn.expected.yaml b/conformance/tests/l1-rendering/egress-class-render/golden/egress-vpn.expected.yaml index 36eed96..dce9285 100644 --- a/conformance/tests/l1-rendering/egress-class-render/golden/egress-vpn.expected.yaml +++ b/conformance/tests/l1-rendering/egress-class-render/golden/egress-vpn.expected.yaml @@ -123,6 +123,22 @@ spec: - port: '8989' protocol: TCP --- +# CiliumNetworkPolicy world egress allow (egress class governs the path; R-NET-05): sonarr-allow-internet-egress +apiVersion: cilium.io/v2 +kind: CiliumNetworkPolicy +metadata: + name: sonarr-allow-internet-egress + namespace: sonarr + labels: + app.kubernetes.io/name: sonarr +spec: + endpointSelector: + matchLabels: + app.kubernetes.io/name: sonarr + egress: + - toEntities: + - world +--- # HTTPRoute sonarr-8989 (R-TLS/R-DNS) apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute