Compare commits

...
Author SHA1 Message Date
claude-botandClaude Opus 5.5 5aee6a54ad revert(netpol): no world egress for mosquitto-bootstrap
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Runtime package installs are not to be enabled by policy. The Jobs feed only
the unused legacy password_file and are removed in a separate PR, which must
merge before this one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:33:25 +02:00
claude-botandClaude Opus 5.5 8a53171ad0 fix(netpol): let mosquitto-bootstrap Jobs reach Alpine mirrors
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
The Jobs run apk add curl jq on every execution; without world 443/80 they
fail and block the home-assistant Kustomization. Also correct the stale
renderer egress comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:26:49 +02:00
claude-botandClaude Opus 5.5 299194247e feat(netpol): allow mqtt-account-controller to deliver to edge-1
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
edge-1.iot.renner.uno:8443 is the deliver.esp32 target in mqtt-accounts.yaml
(https+mTLS credential push). Matched by FQDN, not the current 10.2.30.108.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 17:26:00 +02:00
claude-bot 6b6e1e7e04 Merge remote-tracking branch 'origin/main' into worktree-egress-lockdown
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline failed
# Conflicts:
#	kubernetes/infrastructure/network-policies/gitea-ns.yaml
#	kubernetes/infrastructure/network-policies/matrix-ns.yaml
2026-10-24 12:45:03 +02:00
claude-botandClaude Opus 5.5 752fcd5d03 feat(netpol): restrict pod egress to the outside world per workload
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
Drop namespace-wide world egress and give each workload only the external
destinations it needs (FQDN where the host is in our config, port-scoped
world otherwise). Namespaces that had ingress-only policies get an
in-cluster + DNS egress baseline. CNPG instances get Hetzner S3 via one
clusterwide policy.

Out of scope: kube-system, flux-system, rook-ceph, woodpecker-ci,
music-assistant, evcc, openbao.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-24 12:43:38 +02:00
security-bot 55cd083484 chore(image-automation): bump ghcr.io/advplyr/audiobookshelf:2.37.2
ci/woodpecker/cron/e2e Pipeline failed
ci/woodpecker/cron/security-scan Pipeline was successful
Updates managed by image-automation-controller.
2026-10-23 20:49:32 +00:00
security-bot ada3d3d0b4 chore(image-automation): bump ghcr.io/home-assistant/home-assistant:2026.10.3
Updates managed by image-automation-controller.
2026-10-23 19:50:03 +00:00
security-bot 1bd3111912 chore(image-automation): bump ghcr.io/music-assistant/server:2.10.8
Updates managed by image-automation-controller.
2026-10-23 14:49:29 +00:00
27 changed files with 999 additions and 54 deletions
@@ -70,7 +70,7 @@ spec:
mountPath: /ca
containers:
- name: audiobookshelf
image: ghcr.io/advplyr/audiobookshelf:2.37.1 # {"$imagepolicy": "flux-system:audiobookshelf"}
image: ghcr.io/advplyr/audiobookshelf:2.37.2 # {"$imagepolicy": "flux-system:audiobookshelf"}
ports:
- name: http
containerPort: 80
@@ -128,7 +128,7 @@ spec:
memory: 32Mi
containers:
- name: home-assistant
image: ghcr.io/home-assistant/home-assistant:2026.10.2 # {"$imagepolicy": "flux-system:home-assistant"}
image: ghcr.io/home-assistant/home-assistant:2026.10.3 # {"$imagepolicy": "flux-system:home-assistant"}
env:
- name: TZ
value: Europe/Berlin
@@ -52,7 +52,7 @@ spec:
# stdlib only; Flux keeps the tag in lockstep via the shared image
# policy) — no extra dependency.
- name: reconcile-publish-ip
image: ghcr.io/music-assistant/server:2.10.7 # {"$imagepolicy": "flux-system:music-assistant"}
image: ghcr.io/music-assistant/server:2.10.8 # {"$imagepolicy": "flux-system:music-assistant"}
command: ["/app/venv/bin/python", "/scripts/reconcile.py"]
volumeMounts:
- name: data
@@ -62,7 +62,7 @@ spec:
readOnly: true
containers:
- name: music-assistant
image: ghcr.io/music-assistant/server:2.10.7 # {"$imagepolicy": "flux-system:music-assistant"}
image: ghcr.io/music-assistant/server:2.10.8 # {"$imagepolicy": "flux-system:music-assistant"}
env:
- name: LOG_LEVEL
value: info
@@ -87,11 +87,9 @@ spec:
- port: "53"
protocol: UDP
# host/remote-node: kubelet probes + node-local reach (Ceph mons on host net)
# world: image pulls and llama-server's Hugging Face model download
- toEntities:
- host
- remote-node
- world
# ICMP — egress mirror; see ingress block above.
- toEndpoints:
- matchExpressions:
@@ -166,3 +164,76 @@ spec:
- ports:
- port: "8080"
protocol: TCP
---
# Hugging Face model download on cold cache (llama-server, llama-embed, wiki-embed).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: models-egress-huggingface
namespace: ai
spec:
endpointSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- llama-server
- llama-embed
- wiki-embed
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "huggingface.co"
- matchName: "hf.co"
- matchPattern: "*.huggingface.co"
- matchPattern: "*.hf.co"
toPorts:
- ports:
- port: "443"
protocol: TCP
---
# wiki: Anthropic API (distillation) and TypeSafe API (typed judgements).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: wiki-egress-apis
namespace: ai
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: wiki
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "api.anthropic.com"
- matchName: "api.typesafe.ai"
toPorts:
- ports:
- port: "443"
protocol: TCP
@@ -66,11 +66,9 @@ spec:
- port: "53"
protocol: UDP
# host/remote-node: kubelet probes + node-local reach (Ceph mons on host net)
# world: image pulls, OBC S3 endpoint reachable via the cluster's LB IP, migration pipeline API
- toEntities:
- host
- remote-node
- world
# ICMP — egress mirror; see ingress block above.
- toEndpoints:
- matchExpressions:
@@ -94,3 +92,19 @@ spec:
- { type: 135, family: IPv6 }
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
---
# audiobookshelf (metadata providers) and libation (Audible): HTTPS to the internet.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: audiobookshelf-egress-https
namespace: audiobookshelf
spec:
endpointSelector: {}
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
@@ -70,7 +70,6 @@ spec:
- toEntities:
- host
- remote-node
- world
# ICMP — egress mirror.
- toEndpoints:
- matchExpressions:
@@ -94,3 +93,43 @@ spec:
- { type: 135, family: IPv6 }
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
---
# authentik server and worker: SMTP relay (Proton) for mail, HTTPS for
# outbound webhooks, OIDC/social sources and update checks.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: authentik-egress
namespace: authentik
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: authentik
app.kubernetes.io/part-of: authentik
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "smtp.protonmail.ch"
toPorts:
- ports:
- port: "587"
protocol: TCP
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
@@ -0,0 +1,74 @@
# EGRESS ONLY: an ingress section would flip ingress to default-deny and
# break the apiserver -> webhook admission calls.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: default-egress
namespace: cert-manager
spec:
endpointSelector: {}
egress:
# All in-cluster destinations (pods, nodes, kube-apiserver).
- toEntities:
- cluster
# rules.dns lets Cilium learn FQDN mappings.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
---
# Controller: ACME (Let's Encrypt prod + staging), Cloudflare DNS-01 API, and
# DNS-01 self-check against the recursive nameservers.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: cert-manager-controller-egress-world
namespace: cert-manager
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/component: controller
app.kubernetes.io/name: cert-manager
egress:
# rules.dns lets Cilium learn the toFQDNs mapping.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "acme-v02.api.letsencrypt.org"
- matchName: "acme-staging-v02.api.letsencrypt.org"
- matchName: "api.cloudflare.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
# Mirrors dns01RecursiveNameservers in
# kubernetes/infrastructure/cert-manager/helmrelease.yaml.
- toCIDR:
- 1.1.1.1/32
- 8.8.8.8/32
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
@@ -0,0 +1,34 @@
# CNPG instance pods: the barman-cloud plugin sidecar archives WAL and base
# backups to Hetzner S3. Additive only: every namespace holding a CNPG cluster
# also carries a namespace egress policy covering in-cluster traffic, so
# selecting these pods here does not cut anything else.
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
name: cnpg-barman-s3-egress
spec:
endpointSelector:
matchLabels:
cnpg.io/podRole: instance
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "fsn1.your-objectstorage.com"
- matchPattern: "*.fsn1.your-objectstorage.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
@@ -24,10 +24,7 @@ spec:
- host
- remote-node
# ICMP — all standard ICMPv4 + ICMPv6 from any cluster pod. See
# home-assistant-ns.yaml for the rationale. INGRESS ONLY: do NOT
# add an egress section to this CNP — that's what broke attempt
# 66dbe9f (it transitioned this ns from default-allow egress to
# egress-enforced-with-only-ICMP, killing DNS / apiserver / world).
# home-assistant-ns.yaml for the rationale.
- fromEndpoints:
- matchExpressions:
- key: io.kubernetes.pod.namespace
@@ -50,3 +47,39 @@ spec:
- { type: 135, family: IPv6 }
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
egress:
# All in-cluster destinations (pods, nodes, kube-apiserver).
- toEntities:
- cluster
# rules.dns lets Cilium learn FQDN mappings.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
---
# lapi: CAPI + hub index. agent: hub/collection fetch.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: crowdsec-egress-world
namespace: crowdsec
spec:
endpointSelector:
matchLabels:
k8s-app: crowdsec
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
@@ -22,8 +22,7 @@ spec:
- host
- remote-node
# ICMP — all standard ICMPv4 + ICMPv6 from any cluster pod. See
# home-assistant-ns.yaml for the rationale. INGRESS ONLY: do NOT
# add an egress section to this CNP.
# home-assistant-ns.yaml for the rationale.
- fromEndpoints:
- matchExpressions:
- key: io.kubernetes.pod.namespace
@@ -46,3 +45,21 @@ spec:
- { type: 135, family: IPv6 }
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
egress:
# All in-cluster destinations (pods, nodes, kube-apiserver).
- toEntities:
- cluster
# rules.dns lets Cilium learn FQDN mappings.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
@@ -48,4 +48,109 @@ spec:
- toEntities:
- host
- remote-node
---
# external-dns (Cloudflare provider): Cloudflare API.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: external-dns-egress
namespace: apps
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "api.cloudflare.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
---
# external-dns-unifi: UniFi controller API on the UDM (UNIFI_HOST).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: external-dns-unifi-egress
namespace: apps
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: external-dns
app.kubernetes.io/instance: external-dns-unifi
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "udm.renner.uno"
toPorts:
- ports:
- port: "443"
protocol: TCP
---
# gatus: DNS probes against the UDM resolver by IP; HTTP checks target in-cluster names.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: gatus-egress
namespace: apps
spec:
endpointSelector:
matchLabels:
app: gatus
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
---
# ical-proxy: fetches caller-supplied calendar feed URLs.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: ical-proxy-egress
namespace: apps
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: ical-proxy
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
- port: "80"
protocol: TCP
@@ -92,11 +92,27 @@ spec:
- ports:
- port: "8644"
protocol: TCP
# Push mirror to GitHub and other external targets.
- toEntities:
- host
- remote-node
---
# gitea: push mirrors, external webhooks and OIDC/OAuth sources over HTTPS.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: gitea-egress-https
namespace: gitea
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: gitea
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
---
# Traefik serves git.renner.uno / gitea.renner.uno (:3000) and the gitea-ssh
# entrypoint (:2222). Lives beside the baseline so both land in one reconcile.
@@ -143,10 +143,6 @@ spec:
- toEntities:
- host
- remote-node
# World: IoT-VLAN devices, HA Cloud, object storage, and third-party
# integration endpoints on the LAN and internet.
- toEntities:
- world
# ICMP — egress mirror of the ingress ICMP allow above. Lets HA
# send ICMP to any cluster pod (covers the kernel's ICMPv6
# Destination Unreachable when CoreDNS responses land on a
@@ -261,10 +257,7 @@ spec:
# hairpin off the home-assistant-bgp LoadBalancer and are unusable from a
# pod. It reaches HA at home-assistant-bgp.home-assistant.svc.cluster.local.
#
# Only the ingress half is needed here. Unlike the openbao rotator, the trmnl
# namespace has no default-deny EGRESS covering this pod — terminus-egress is
# scoped to the Terminus pods by label and does not select the renderer — so
# there is no source-side rule to add.
# The egress half is trmnl-baseline-egress (toEntities: cluster).
#
# Source scoped to the renderer pod specifically, not the whole trmnl
# namespace: Terminus itself sits in that namespace and has no business
@@ -351,3 +344,142 @@ spec:
- ports:
- port: "5432"
protocol: TCP
---
# home-assistant: IoT-VLAN devices, HA Cloud, and third-party integration
# endpoints on the LAN and internet.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: home-assistant-egress-world
namespace: home-assistant
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: home-assistant
egress:
- toEntities:
- world
---
# shelly-manager: HTTP RPC to Shelly devices on the IoT VLAN.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: shelly-manager-egress
namespace: home-assistant
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: shelly-manager
egress:
- toCIDR:
- 10.2.30.0/24
toPorts:
- ports:
- port: "80"
protocol: TCP
---
# esphome: OTA/API to devices on the IoT VLAN; HTTPS for GitHub, PlatformIO and PyPI during builds.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: esphome-egress
namespace: home-assistant
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: esphome
egress:
- toCIDR:
- 10.2.30.0/24
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
---
# zigbee2mqtt: Zigbee coordinator (SLZB) TCP socket.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: zigbee2mqtt-egress
namespace: home-assistant
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: zigbee2mqtt
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "slzb-zigbee.renner.uno"
toPorts:
- ports:
- port: "6638"
protocol: TCP
---
# mqtt-account-controller: credential delivery to edge-1 (deliver.esp32 in mqtt-accounts.yaml).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: mqtt-account-controller-egress
namespace: home-assistant
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: mqtt-account-controller
egress:
# rules.dns lets Cilium learn the toFQDNs mapping
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "edge-1.iot.renner.uno"
toPorts:
- ports:
- port: "8443"
protocol: TCP
---
# whisper and speech-to-phrase: model downloads over HTTPS.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: speech-models-egress
namespace: home-assistant
spec:
endpointSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- whisper
- speech-to-phrase
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
@@ -33,7 +33,7 @@ spec:
- host
- remote-node
# ICMP — all standard ICMPv4 + ICMPv6 from any cluster pod. See
# home-assistant-ns.yaml for the rationale. INGRESS ONLY.
# home-assistant-ns.yaml for the rationale.
- fromEndpoints:
- matchExpressions:
- key: io.kubernetes.pod.namespace
@@ -56,3 +56,41 @@ spec:
- { type: 135, family: IPv6 }
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
egress:
# All in-cluster destinations (pods, nodes, kube-apiserver).
- toEntities:
- cluster
# rules.dns lets Cilium learn FQDN mappings.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
---
# immich-server and machine-learning: HTTPS to the internet (model downloads, metadata/reverse geocoding, version checks).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: immich-egress-world
namespace: immich
spec:
endpointSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values: [immich-server, immich-machine-learning]
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
@@ -33,7 +33,7 @@ spec:
- host
- remote-node
# ICMP — all standard ICMPv4 + ICMPv6 from any cluster pod. See
# home-assistant-ns.yaml for the rationale. INGRESS ONLY.
# home-assistant-ns.yaml for the rationale.
- fromEndpoints:
- matchExpressions:
- key: io.kubernetes.pod.namespace
@@ -56,3 +56,43 @@ spec:
- { type: 135, family: IPv6 }
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
egress:
# All in-cluster destinations (pods, nodes, kube-apiserver).
- toEntities:
- cluster
# rules.dns lets Cilium learn FQDN mappings.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
---
# Token job pods install tooling at runtime (apt, dl.k8s.io).
# Selected by the job-controller `job-name` label, so the influxdb StatefulSet pod gets no world egress.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: influxdb-token-jobs-egress-world
namespace: influxdb
spec:
endpointSelector:
matchExpressions:
- key: job-name
operator: Exists
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
- port: "80"
protocol: TCP
@@ -9,6 +9,7 @@ resources:
- audiobookshelf-ns.yaml
- home-assistant-ns.yaml
- spegel-ns.yaml
- cert-manager-ns.yaml
- crowdsec-ns.yaml
- database-ns.yaml
- immich-ns.yaml
@@ -37,4 +38,5 @@ resources:
- renovate-ns.yaml
- hermes-ns.yaml
- allow-cilium-health.yaml
- cnpg-barman-s3.yaml
- kube-system-hubble-relay.yaml
@@ -33,7 +33,7 @@ spec:
- host
- remote-node
# ICMP — all standard ICMPv4 + ICMPv6 from any cluster pod. See
# home-assistant-ns.yaml for the rationale. INGRESS ONLY.
# home-assistant-ns.yaml for the rationale.
- fromEndpoints:
- matchExpressions:
- key: io.kubernetes.pod.namespace
@@ -58,8 +58,7 @@ spec:
- { type: 143, family: IPv6 }
---
# Egress for the app pods (synapse, mas, element-web),
# selected by part-of so the mx-pg CNPG pods keep unrestricted egress for
# the barman-cloud WAL archive to Hetzner (same trade as trmnl-ns.yaml).
# selected by part-of; the mx-pg CNPG pods are covered by mx-pg-egress.
#
# Nothing here reaches the internet: that is the network half of
# "federation off". The one exception is synapse-push-egress below.
@@ -187,3 +186,32 @@ spec:
- ports:
- port: "8443"
protocol: TCP
---
# mx-pg CNPG pods: in-cluster + DNS. Unselected by the app egress policies above;
# S3 backup egress comes from the clusterwide CNPG policy.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: mx-pg-egress
namespace: matrix
spec:
endpointSelector:
matchLabels:
cnpg.io/cluster: mx-pg
egress:
- toEntities:
- cluster
# rules.dns lets Cilium learn FQDN mappings.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
@@ -34,7 +34,7 @@ spec:
- host
- remote-node
# ICMP — all standard ICMPv4 + ICMPv6 from any cluster pod. See
# home-assistant-ns.yaml for the rationale. INGRESS ONLY.
# home-assistant-ns.yaml for the rationale.
- fromEndpoints:
- matchExpressions:
- key: io.kubernetes.pod.namespace
@@ -57,6 +57,24 @@ spec:
- { type: 135, family: IPv6 }
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
egress:
# All in-cluster destinations (pods, nodes, kube-apiserver).
- toEntities:
- cluster
# rules.dns lets Cilium learn FQDN mappings.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
---
# Hermes Agent (hermes ns) reads metrics, alerts and logs: the Prometheus,
# Alertmanager and Loki HTTP APIs, scoped to those pods and ports.
@@ -84,3 +102,120 @@ spec:
protocol: TCP
- port: "3100"
protocol: TCP
---
# Alertmanager: SMTP submission (smtp_smarthost in helmrelease-prometheus.yaml). Only receiver is email.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: alertmanager-egress-world
namespace: monitoring
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: alertmanager
egress:
# rules.dns lets Cilium learn the toFQDNs mapping.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "smtp.protonmail.ch"
toPorts:
- ports:
- port: "587"
protocol: TCP
---
# Prober: targets are defined by Probe objects (monitoring-rules), any port.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: blackbox-exporter-egress-world
namespace: monitoring
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: prometheus-blackbox-exporter
egress:
- toEntities:
- world
---
# Grafana: plugin download (grafana.com) + OAuth, and the legacy Prometheus datasource.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: grafana-egress-world
namespace: monitoring
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: grafana
egress:
# rules.dns lets Cilium learn the toFQDNs mapping.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
# Datasource "Prometheus (legacy)".
- toFQDNs:
- matchName: "prometheus.srv.renner.uno"
toPorts:
- ports:
- port: "9090"
protocol: TCP
---
# Prometheus: off-site Uptime Kuma scrape (additionalScrapeConfigs job uptime-kuma). No remoteWrite configured.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: prometheus-egress-world
namespace: monitoring
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: prometheus
egress:
# rules.dns lets Cilium learn the toFQDNs mapping.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "kuma.renner.uno"
toPorts:
- ports:
- port: "443"
protocol: TCP
@@ -24,6 +24,10 @@ spec:
protocol: UDP
- port: "53"
protocol: TCP
# rules.dns lets Cilium learn the toFQDNs mapping
rules:
dns:
- matchPattern: "*"
- toEntities:
- kube-apiserver
- toEndpoints:
@@ -34,9 +38,9 @@ spec:
- ports:
- port: "8200"
protocol: TCP
# NetBird management API (netbird.renner.uno).
- toEntities:
- world
# NetBird management API.
- toFQDNs:
- matchName: "netbird.renner.uno"
toPorts:
- ports:
- port: "443"
@@ -88,7 +88,6 @@ spec:
- toEntities:
- host
- remote-node
- world
# ICMP — egress mirror.
- toEndpoints:
- matchExpressions:
@@ -268,3 +267,49 @@ spec:
- ports:
- port: "8000"
protocol: TCP
---
# protonmail-bridge: Proton API (HTTPS) and update/status endpoints (HTTP).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: protonmail-bridge-egress
namespace: paperless
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: protonmail-bridge
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
- port: "80"
protocol: TCP
---
# syncthing: relays/discovery (443), STUN (3478), peer sync (22000), local discovery (21027).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: syncthing-egress
namespace: paperless
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: syncthing
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP
- port: "3478"
protocol: UDP
- port: "22000"
protocol: TCP
- port: "22000"
protocol: UDP
- port: "21027"
protocol: UDP
@@ -53,14 +53,20 @@ spec:
protocol: UDP
- port: "53"
protocol: TCP
# rules.dns lets Cilium learn the toFQDNs mapping
rules:
dns:
- matchPattern: "*"
# gitea.renner.uno via Traefik.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": ingress
app.kubernetes.io/name: traefik
# api.mistral.ai, api.anthropic.com, api.typesafe.ai.
- toEntities:
- world
# Model APIs.
- toFQDNs:
- matchName: "api.mistral.ai"
- matchName: "api.anthropic.com"
- matchName: "api.typesafe.ai"
toPorts:
- ports:
- port: "443"
@@ -54,12 +54,10 @@ spec:
- ports:
- port: "53"
protocol: UDP
# host/remote-node: kube-apiserver (leader election Lease) + cross-node peers;
# world: fallback pulls to docker.io/quay.io/ghcr.io/registry.k8s.io.
# host/remote-node: kube-apiserver (leader election Lease) + cross-node peers
- toEntities:
- host
- remote-node
- world
# ICMP — egress mirror.
- toEndpoints:
- matchExpressions:
@@ -33,7 +33,7 @@ spec:
- host
- remote-node
# ICMP — all standard ICMPv4 + ICMPv6 from any cluster pod. See
# home-assistant-ns.yaml for the rationale. INGRESS ONLY.
# home-assistant-ns.yaml for the rationale.
- fromEndpoints:
- matchExpressions:
- key: io.kubernetes.pod.namespace
@@ -86,10 +86,9 @@ spec:
---
# Egress allowlist for the Terminus pod only.
#
# Deliberately NOT applied to the tr-pg CNPG pods: selecting them here
# would make their egress restricted too, and the barman-cloud WAL archive
# path to Hetzner object storage is not worth putting behind an FQDN rule
# that fails closed. They keep unrestricted egress, matching immich/authentik.
# Deliberately NOT applied to the other pods: they are covered by
# trmnl-baseline-egress (in-cluster + DNS), and the tr-pg barman-cloud WAL
# archive egress comes from the clusterwide CNPG policy.
#
# Terminus needs exactly three outbound destinations, all HTTPS:
# - trmnl.com → GET /api/models, /api/palettes (MODEL_SYNCHRONIZER,
@@ -152,3 +151,34 @@ spec:
- toEntities:
- host
- remote-node
---
# In-cluster + DNS for every pod except terminus (terminus-egress is its
# allowlist; selecting it here would widen it to all of `cluster`).
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: trmnl-baseline-egress
namespace: trmnl
spec:
endpointSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: NotIn
values: [terminus]
egress:
- toEntities:
- cluster
# rules.dns lets Cilium learn FQDN mappings.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
@@ -40,14 +40,18 @@ spec:
protocol: UDP
- port: "53"
protocol: TCP
# rules.dns lets Cilium learn the toFQDNs mapping
rules:
dns:
- matchPattern: "*"
# EndpointMonitor watch, Ingress reads, and the startup GET of the config
# Secret.
- toEntities:
- kube-apiserver
# api.uptimerobot.com. Not reached at all until an EndpointMonitor exists:
# the controller makes no API call at startup.
- toEntities:
- world
- toFQDNs:
- matchName: "api.uptimerobot.com"
toPorts:
- ports:
- port: "443"
@@ -19,7 +19,7 @@ spec:
- host
- remote-node
# ICMP — all standard ICMPv4 + ICMPv6 from any cluster pod. See
# home-assistant-ns.yaml for the rationale. INGRESS ONLY.
# home-assistant-ns.yaml for the rationale.
- fromEndpoints:
- matchExpressions:
- key: io.kubernetes.pod.namespace
@@ -42,6 +42,32 @@ spec:
- { type: 135, family: IPv6 }
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
egress:
# All in-cluster destinations (pods, nodes, kube-apiserver).
- toEntities:
- cluster
# rules.dns lets Cilium learn the toFQDNs mapping.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
# Hetzner S3: velero, node-agent and kopia maintenance jobs.
- toFQDNs:
- matchName: "fsn1.your-objectstorage.com"
- matchPattern: "*.fsn1.your-objectstorage.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
---
# backup-verify CronJob: kube-apiserver, DNS, Hetzner Cloud API, SSH to its VM.
apiVersion: cilium.io/v2
@@ -8,7 +8,8 @@
# belongs in its own policy with its own selector, as
# `wp-pg-allow-cnpg-operator` below does.
#
# There is no egress section, so egress is unrestricted. Still open (#433).
# Egress: in-cluster + DNS only (server, agent, wp-pg). The CNPG S3 backup
# egress comes from the clusterwide CNPG policy.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
@@ -36,7 +37,6 @@ spec:
- host
- remote-node
# ICMP — from any cluster pod. See home-assistant-ns.yaml for the rationale.
# INGRESS ONLY: do NOT add an egress section to this CNP.
#
# Needed here specifically because wp-pg-allow-cnpg-operator below attaches
# an L4 rule (toPorts :8000) to the wp-pg pods, and Cilium blocks ICMP to an
@@ -79,6 +79,24 @@ spec:
# instance manager. It lands in stage 2 as its own policy selecting the
# server pod, the way woodpecker-allow-rotators already scopes its :8000
# rule in apps/woodpecker/networkpolicy.yaml.
egress:
# All in-cluster destinations (pods, nodes, kube-apiserver).
- toEntities:
- cluster
# rules.dns lets Cilium learn FQDN mappings.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
---
# The CNPG operator polls each instance's status endpoint over HTTPS
# (https://<pod-ip>:8000/pg/status) to populate Cluster status. Without it the
@@ -85,13 +85,31 @@ spec:
- ports:
- port: "53"
protocol: UDP
# world covers two things WordPress genuinely needs outbound: the
# wordpress.org update/plugin API, and the Hetzner Object Storage
# endpoint the MariaDB Backup writes to.
- toEntities:
- host
- remote-node
- world
# Hetzner Object Storage for the MariaDB backup CronJob pods (no stable
# label, hence namespace-wide). rules.dns lets Cilium learn the mapping.
- toEndpoints:
- matchLabels:
"k8s:io.kubernetes.pod.namespace": kube-system
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
rules:
dns:
- matchPattern: "*"
- toFQDNs:
- matchName: "fsn1.your-objectstorage.com"
- matchPattern: "*.fsn1.your-objectstorage.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
# ICMP — egress mirror.
- toEndpoints:
- matchExpressions:
@@ -115,3 +133,21 @@ spec:
- { type: 135, family: IPv6 }
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
---
# wordpress: wordpress.org update/plugin API and other HTTPS fetches.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: wordpress-egress-https
namespace: wordpress
spec:
endpointSelector:
matchLabels:
app.kubernetes.io/name: wordpress
egress:
- toEntities:
- world
toPorts:
- ports:
- port: "443"
protocol: TCP