Compare commits

...
Author SHA1 Message Date
k.renner 30704634b3 Merge pull request 'feat(cilium): export policy drops to the agent log' (#1591) from feat/hubble-policy-drop-log into main
Reviewed-on: #1591
2026-11-07 10:31:25 +00:00
k.renner 43861d34c1 Merge pull request 'feat(coredns): restrict kube-dns-lb ingress to the internal networks' (#1590) from feat/kube-dns-lb-source-cidr into main
Reviewed-on: #1590
2026-11-07 10:31:17 +00:00
k.renner 3252a6f9ee Merge pull request 'feat(paperless): restrict FTP and Syncthing LB ingress to their client networks' (#1589) from feat/paperless-source-cidr into main
Reviewed-on: #1589
2026-11-07 10:31:10 +00:00
k.renner cf80a096a6 Merge pull request 'feat(home-assistant): restrict LB ingress to the client networks' (#1588) from feat/home-assistant-source-cidr into main
Reviewed-on: #1588
2026-11-07 10:31:00 +00:00
k.renner 41e272f188 Merge pull request 'feat(shelly-manager): restrict firmware LB ingress to the IoT network' (#1587) from feat/shelly-firmware-source-cidr into main
Reviewed-on: #1587
2026-11-07 10:30:52 +00:00
k.renner 8dc0986d1d Merge pull request 'feat(mosquitto): restrict LB ingress to the IoT network' (#1586) from feat/mosquitto-source-cidr into main
Reviewed-on: #1586
2026-11-07 10:30:44 +00:00
k.renner edb4aa804c Merge pull request 'feat(monitoring): restrict syslog ingress to the sending networks' (#1585) from feat/syslog-source-cidr into main
Reviewed-on: #1585
2026-11-07 10:30:32 +00:00
claude-botandClaude Opus 5.5 9777e147e8 feat(cilium): export policy drops to the agent log
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
Hubble's static exporter writes every POLICY_DENIED drop event to the
cilium-agent stdout, which Alloy already ships to Loki (1 year
retention). That makes denied traffic reviewable after the fact instead
of only for the minutes the Hubble ring buffer holds.

Filter and field mask were checked against the running 1.20.4 agent
with hubble observe --allowlist / --field-mask. The cilium-config change
rolls the agents one at a time via the reloader annotation.

Refs S28-30

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:24:23 +01:00
claude-botandClaude Opus 5.5 37dc17065d feat(coredns): restrict kube-dns-lb ingress to the internal networks
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
CoreDNS had no policy, so kube-dns-lb answered any source. The new CNP
allows the whole cluster on every port and the internal client networks
on 53/UDP+TCP. Selecting the pods makes them default-deny for ingress.

Refs S28-19

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:19:37 +01:00
claude-botandClaude Opus 5.5 570c93559b feat(paperless): restrict FTP and Syncthing LB ingress to their client networks
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ftp-scanner and syncthing-sync accepted any world source.

- ftp-scanner: IoT and Privat. Over the last 7 days the vsftpd log shows
  uploads from the BR5C scanner via fd02:30:: and the Brother via
  10.2.50.170.
- syncthing-sync: Privat and VPN. Over the last 7 days peers connected
  from 10.2.50.0/24 and fd02:50::/64.

host and remote-node stay allowed. Both LBs are ETP=Local.

Refs S28-19

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:18:11 +01:00
claude-botandClaude Opus 5.5 71ab916ff7 feat(home-assistant): restrict LB ingress to the client networks
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
home-assistant-bgp (:8123) accepted any world source. Allowed now:
Privat, VPN, IoT and Server, per the approved source matrix. Browser and
app access from outside goes through Traefik and is unaffected. The LB is
ETP=Local, so the client address reaches the pod.

Refs S28-19

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:17:06 +01:00
claude-botandClaude Opus 5.5 9aecaacbb2 feat(shelly-manager): restrict firmware LB ingress to the IoT network
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
shelly-manager-firmware (:8082, /fw/ only) accepted any world source.
Shelly devices live on IoT; the LB is ETP=Local, so the client address
reaches the pod.

Refs S28-19

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:16:37 +01:00
claude-botandClaude Opus 5.5 c4f32c7a97 feat(monitoring): restrict syslog ingress to the sending networks
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
alloy-syslog accepted syslog from any world source. Senders seen in
Hubble and Loki over the last 7 days: switches and APs on Management,
SLZB coordinators and edge hosts on IoT, and the UDM, which sources from
its k8s-ingress address 10.2.47.1. The LB is ETP=Local, so those client
addresses reach the pod.

Refs S28-19

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-07 11:14:54 +01:00
6 changed files with 115 additions and 24 deletions
@@ -156,13 +156,39 @@ spec:
k8sServicePort: 7445
# Hubble itself is on in every agent (chart default); the relay aggregates
# the three nodes' flow buffers behind one gRPC endpoint so `hubble observe`
# works without exec into kube-system (REN-487). Renders no change to
# cilium-config or the agent DaemonSet, so the reloader annotation above
# does not roll the CNI. The relay has no client auth; who may reach it is
# network-policies/kube-system-hubble-relay.yaml.
# works without exec into kube-system (REN-487). The relay has no client
# auth; who may reach it is network-policies/kube-system-hubble-relay.yaml.
hubble:
relay:
enabled: true
# Every policy drop goes to the agent's stdout and from there to Loki
# ({namespace="kube-system", container="cilium-agent"} |= "POLICY_DENIED"),
# so denied traffic can be reviewed after the fact. Changes cilium-config,
# so the reloader rolls the agents.
export:
static:
enabled: true
filePath: stdout
allowList:
- '{"verdict":["DROPPED"],"drop_reason_desc":["POLICY_DENIED"],"event_type":[{"type":1}]}'
fieldMask:
- time
- node_name
- verdict
- drop_reason_desc
- traffic_direction
- IP
- l4
- source.namespace
- source.pod_name
- source.identity
- source.workloads
- source_names
- destination.namespace
- destination.pod_name
- destination.identity
- destination.workloads
- destination_names
l2announcements:
enabled: true
externalIPs:
@@ -205,8 +205,11 @@ spec:
matchLabels:
app.kubernetes.io/name: home-assistant
ingress:
- fromEntities:
- world
- fromCIDRSet:
- cidrGroupRef: net-privat
- cidrGroupRef: net-vpn
- cidrGroupRef: net-iot
- cidrGroupRef: net-server
toPorts:
- ports:
- port: "8123"
@@ -225,8 +228,8 @@ spec:
matchLabels:
app.kubernetes.io/name: shelly-manager
ingress:
- fromEntities:
- world
- fromCIDRSet:
- cidrGroupRef: net-iot
toPorts:
- ports:
- port: "8082"
@@ -0,0 +1,40 @@
# CoreDNS is reachable from the LAN through the kube-dns-lb LoadBalancer
# (ETP=Local, client address reaches the pod). Selecting the pods makes
# them default-deny for ingress, so the whole cluster (pods, nodes, kubelet
# probes, Prometheus) is allowed on every port; LAN clients only on 53.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: coredns-ingress
namespace: kube-system
spec:
endpointSelector:
matchLabels:
k8s-app: kube-dns
ingress:
- fromEntities:
- cluster
- fromCIDRSet:
- cidrGroupRef: net-default
- cidrGroupRef: net-management
- cidrGroupRef: net-radius
- cidrGroupRef: net-video
- cidrGroupRef: net-iot
- cidrGroupRef: net-server
- cidrGroupRef: net-k8s-node
- cidrGroupRef: net-k8s-ingress
- cidrGroupRef: net-k8s-pod
- cidrGroupRef: net-privat
- cidrGroupRef: net-phone
- cidrGroupRef: net-access
- cidrGroupRef: net-vpn
- cidrGroupRef: net-guest
- cidrGroupRef: net-multi-transfer
- cidrGroupRef: net-multi-lan
- cidrGroupRef: net-mutti
toPorts:
- ports:
- port: "53"
protocol: UDP
- port: "53"
protocol: TCP
@@ -41,3 +41,4 @@ resources:
- allow-cilium-health.yaml
- cnpg-barman-s3.yaml
- kube-system-hubble-relay.yaml
- kube-system-coredns.yaml
@@ -1,13 +1,6 @@
# Targeted carve-out: alloy-syslog needs world ingress on the syslog
# ports specifically. The Service is ETP=Cluster, so external syslog
# senders (Proxmox, LXC hosts, network gear) hit the LB IP, get DNAT'd
# to an alloy pod, and get SNAT'd to the receiving node's cilium_host
# address. Cilium then sees the source as `world-ipv4` identity (the
# cilium_host IP isn't a known endpoint), NOT `host` — so the broad
# monitoring CNP's `fromEntities: host` rule doesn't catch it.
#
# Scoped via endpointSelector so this only widens ingress on the
# alloy-syslog pod, not the whole namespace.
# Syslog senders reaching the alloy-syslog LB (ETP=Local, client address
# reaches the pod): network gear on Management, the SLZB coordinators on
# IoT, and the UDM itself, which sources from its k8s-ingress address.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
@@ -19,8 +12,10 @@ spec:
app.kubernetes.io/instance: alloy-syslog
app.kubernetes.io/name: alloy
ingress:
- fromEntities:
- world
- fromCIDRSet:
- cidrGroupRef: net-management
- cidrGroupRef: net-iot
- cidrGroupRef: net-k8s-ingress
toPorts:
- ports:
- port: "514"
@@ -112,7 +112,7 @@ spec:
- { type: 136, family: IPv6 }
- { type: 143, family: IPv6 }
---
# Allow FTP access (from scanners on the LAN) to the ftp-scanner pod.
# FTP from the scanners (IoT and Privat) to the ftp-scanner pod.
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
@@ -123,8 +123,24 @@ spec:
matchLabels:
app.kubernetes.io/name: ftp-scanner
ingress:
- fromCIDRSet:
- cidrGroupRef: net-iot
- cidrGroupRef: net-privat
toPorts:
- ports:
- port: "21"
protocol: TCP
- port: "21100"
protocol: TCP
- port: "21101"
protocol: TCP
- port: "21102"
protocol: TCP
- port: "21103"
protocol: TCP
- port: "21104"
protocol: TCP
- fromEntities:
- world
- host
- remote-node
toPorts:
@@ -142,7 +158,7 @@ spec:
- port: "21104"
protocol: TCP
---
# Allow Syncthing peer-to-peer sync from the LAN/world.
# Allow Syncthing peer-to-peer sync from Privat and VPN.
# - 22000 TCP+QUIC: actual sync traffic between peers.
# - 21027 UDP: local-discovery broadcast packets from peers on the LAN.
# UI (8384) is intentionally NOT listed — authentik-gate-sync generates the
@@ -157,8 +173,18 @@ spec:
matchLabels:
app.kubernetes.io/name: syncthing
ingress:
- fromCIDRSet:
- cidrGroupRef: net-privat
- cidrGroupRef: net-vpn
toPorts:
- ports:
- port: "22000"
protocol: TCP
- port: "22000"
protocol: UDP
- port: "21027"
protocol: UDP
- fromEntities:
- world
- host
- remote-node
toPorts: