feat(netpol): restrict pod egress to the outside world per workload #1432

Merged
k.renner merged 5 commits from claude/egress-lockdown into main 2026-10-25 14:50:34 +00:00
Member

Locks down pod egress to Cilium's world entity (internet + LAN). Basis: 30 min Hubble capture of new outbound flows on all nodes plus manifest/config review.

Merge order: the mosquitto-bootstrap Jobs run apk add at runtime and lose internet access here. They only feed the unused legacy broker password_file and are removed in a separate PR (mosquitto legacy auth removal), which must merge first.

What changes

  • Namespace-wide world removed in ai, apps, audiobookshelf, authentik, gitea, home-assistant, paperless, spegel, wordpress. Cilium unions allows, so the ns-wide world rule made every narrower per-pod rule a no-op.
  • Egress baseline added (cluster entity + DNS with L7 rule) where policies were ingress-only and egress was fully open: cert-manager (new file, egress only), crowdsec, database, immich, influxdb, monitoring, velero, woodpecker, matrix (mx-pg only), trmnl (all but terminus).
  • Per-workload world egress: FQDN where the host is in our config, port-scoped world where the app picks its own destinations.
  • cnpg-barman-s3-egress (CCNP): all CNPG instances → fsn1.your-objectstorage.com:443.
  • FQDN tightening: review-bot (Anthropic/Mistral/TypeSafe), uptime-monitor (UptimeRobot), netbird-service-sync (netbird.renner.uno).
Workload Now allowed external
llama-server/embed, wiki-embed huggingface.co, hf.co (+subdomains) :443
wiki api.anthropic.com, api.typesafe.ai :443
external-dns / external-dns-unifi api.cloudflare.com / udm.renner.uno :443
gatus world :53
ical-proxy world :443/:80
audiobookshelf, libation world :443
authentik server/worker smtp.protonmail.ch:587, world :443
cert-manager controller LE prod/staging, api.cloudflare.com :443; 1.1.1.1/8.8.8.8 :53
crowdsec lapi/agent world :443
gitea world :443
home-assistant world, all ports (unchanged)
shelly-manager 10.2.30.0/24 :80
esphome 10.2.30.0/24 all; world :443
zigbee2mqtt slzb-zigbee.renner.uno :6638
mqtt-account-controller edge-1.iot.renner.uno :8443 (deliver.esp32 credential push)
whisper, speech-to-phrase world :443
immich server/ML world :443
influxdb token jobs world :443/:80
alertmanager smtp.protonmail.ch:587
blackbox-exporter world, all ports
grafana world :443; prometheus.srv.renner.uno:9090
prometheus kuma.renner.uno:443
paperless world :443 (existing)
protonmail-bridge world :443/:80
syncthing world :443, 3478/udp, 22000 tcp+udp, 21027/udp
velero (all pods), wordpress (ns-wide), CNPG instances Hetzner S3 :443
wordpress world :443
everything else in the touched namespaces none

Deliberately cut — watch after merge

  • trmnl-renderer → Google (not needed).
  • authentik LDAP/RADIUS outposts → update check.
  • spegel and openbao-adjacent pods had world without a seen need (openbao itself is NOT changed here).

Out of scope (need their own audit)

kube-system, flux-system, rook-ceph, woodpecker-ci, music-assistant, evcc, openbao (several rotators reach external APIs via the ns world rule; needs per-rotator audit).

Validation

  • kubectl apply --dry-run=server on every changed file; kubectl kustomize builds.
  • Not exercised live. After reconcile, check hubble observe --verdict DROPPED per namespace, and that CNPG WAL archiving, the next velero/kopia run, cert renewal and alert mail still work.

🤖 Generated with Claude Code

Locks down pod egress to Cilium's `world` entity (internet + LAN). Basis: 30 min Hubble capture of new outbound flows on all nodes plus manifest/config review. **Merge order:** the mosquitto-bootstrap Jobs run `apk add` at runtime and lose internet access here. They only feed the unused legacy broker password_file and are removed in a separate PR (mosquitto legacy auth removal), which must merge first. ## What changes - **Namespace-wide `world` removed** in ai, apps, audiobookshelf, authentik, gitea, home-assistant, paperless, spegel, wordpress. Cilium unions allows, so the ns-wide world rule made every narrower per-pod rule a no-op. - **Egress baseline added** (`cluster` entity + DNS with L7 rule) where policies were ingress-only and egress was fully open: cert-manager (new file, egress only), crowdsec, database, immich, influxdb, monitoring, velero, woodpecker, matrix (mx-pg only), trmnl (all but terminus). - **Per-workload world egress**: FQDN where the host is in our config, port-scoped `world` where the app picks its own destinations. - **`cnpg-barman-s3-egress`** (CCNP): all CNPG instances → `fsn1.your-objectstorage.com:443`. - **FQDN tightening**: review-bot (Anthropic/Mistral/TypeSafe), uptime-monitor (UptimeRobot), netbird-service-sync (netbird.renner.uno). | Workload | Now allowed external | |---|---| | llama-server/embed, wiki-embed | huggingface.co, hf.co (+subdomains) :443 | | wiki | api.anthropic.com, api.typesafe.ai :443 | | external-dns / external-dns-unifi | api.cloudflare.com / udm.renner.uno :443 | | gatus | world :53 | | ical-proxy | world :443/:80 | | audiobookshelf, libation | world :443 | | authentik server/worker | smtp.protonmail.ch:587, world :443 | | cert-manager controller | LE prod/staging, api.cloudflare.com :443; 1.1.1.1/8.8.8.8 :53 | | crowdsec lapi/agent | world :443 | | gitea | world :443 | | home-assistant | world, all ports (unchanged) | | shelly-manager | 10.2.30.0/24 :80 | | esphome | 10.2.30.0/24 all; world :443 | | zigbee2mqtt | slzb-zigbee.renner.uno :6638 | | mqtt-account-controller | edge-1.iot.renner.uno :8443 (deliver.esp32 credential push) | | whisper, speech-to-phrase | world :443 | | immich server/ML | world :443 | | influxdb token jobs | world :443/:80 | | alertmanager | smtp.protonmail.ch:587 | | blackbox-exporter | world, all ports | | grafana | world :443; prometheus.srv.renner.uno:9090 | | prometheus | kuma.renner.uno:443 | | paperless | world :443 (existing) | | protonmail-bridge | world :443/:80 | | syncthing | world :443, 3478/udp, 22000 tcp+udp, 21027/udp | | velero (all pods), wordpress (ns-wide), CNPG instances | Hetzner S3 :443 | | wordpress | world :443 | | everything else in the touched namespaces | none | ## Deliberately cut — watch after merge - trmnl-renderer → Google (not needed). - authentik LDAP/RADIUS outposts → update check. - spegel and openbao-adjacent pods had world without a seen need (openbao itself is NOT changed here). ## Out of scope (need their own audit) kube-system, flux-system, rook-ceph, woodpecker-ci, music-assistant, evcc, openbao (several rotators reach external APIs via the ns world rule; needs per-rotator audit). ## Validation - `kubectl apply --dry-run=server` on every changed file; `kubectl kustomize` builds. - Not exercised live. After reconcile, check `hubble observe --verdict DROPPED` per namespace, and that CNPG WAL archiving, the next velero/kopia run, cert renewal and alert mail still work. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
claude-bot added 1 commit 2026-10-24 10:44:05 +00:00
feat(netpol): restrict pod egress to the outside world per workload
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
752fcd5d03
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>
claude-bot added 1 commit 2026-10-24 10:45:14 +00:00
Merge remote-tracking branch 'origin/main' into worktree-egress-lockdown
ci/woodpecker/pr/ci-manifests-test Pipeline failed
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
6b6e1e7e04
# Conflicts:
#	kubernetes/infrastructure/network-policies/gitea-ns.yaml
#	kubernetes/infrastructure/network-policies/matrix-ns.yaml
review-bot requested changes 2026-10-24 10:59:44 +00:00
Dismissed
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit 6b6e1e7e041a322e94b86834f69edda48a6494d1.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Push new commits to get a fresh review.


Summary

This PR removes namespace-wide world egress in nine namespaces. It adds an in-cluster + DNS egress baseline where egress was fully open, and gives each workload only the external destinations it needs, using FQDN rules where the host is known and port-scoped world otherwise. It also adds one clusterwide policy that lets CNPG instance pods reach Hetzner S3. Almost every rule matches what I read in the manifests. I checked the cert-manager self-check (recursive-only), alertmanager and authentik SMTP, the udm.renner.uno and SLZB hostnames, the virtual-hosted velero bucket, terminus and sidekiq sharing one pod, the influx token jobs, and the *.renner.uno names that k8s_gateway answers with the Traefik VIP.

One workload was missed, and the miss breaks the home-assistant rollout. The PR is mergeable, main has not moved past the base 55cd083, and no other PR overlaps it.

🔴 Blocking

  • mosquitto-bootstrap Jobs lose package-repo access, which fails the home-assistant Kustomization.
    • What the Jobs do: kubernetes/apps/home-assistant/mosquitto-bootstrap.yaml:67-68 and the three sibling Jobs in the same file run set -eu and then apk add --no-cache curl jq. The apk add happens before the "secret already exists" check, so it runs on every execution, including the no-op case. Their pods carry the label app.kubernetes.io/name: mosquitto-bootstrap.
    • What changed: home-assistant-ns.yaml:143-145 drops world from the namespace-wide egress. None of the new rules in home-assistant-ns.yaml:350-456 select these pods. I found no other CNP that gives them world.
    • When it fails: whenever the Jobs are recreated. That happens on a cold rebuild, and on any change to the pinned alpine digest, because the Jobs carry kustomize.toolkit.fluxcd.io/force: enabled (:50). Each Job then fails after backoffLimit: 4.
    • Impact:
      • The home-assistant Kustomization runs with wait: true (clusters/homelab/home-assistant.yaml), so a failed Job blocks its health check. zone-configurator, speech-to-phrase and home-assistant-iot depend on it.
      • On a cold rebuild, mosquitto-credentials and the other secrets never get created. The broker cannot start because mosquitto-deployment.yaml references them without optional: true.
    • Fix: grant these pods world on 443/80, as influxdb-token-jobs-egress-world already does for the influx jobs. Alternatively, switch the Jobs to the in-house alpine-jq image that zigbee2mqtt already uses, so nothing is installed at runtime.

⚪ Unverified

  • CNPG recovery from backup on a cold rebuild. cnpg-barman-s3.yaml selects only cnpg.io/podRole: instance, and nothing in the repo uses that label, so I could not confirm it.
    • First, run kubectl get pods -A -l cnpg.io/podRole=instance and check it lists every instance pod.
    • Second, ha-pg-cluster.yaml (like the other clusters) bootstraps with recovery from the barman store. If CNPG's *-full-recovery Job pods do not carry podRole: instance, a cold rebuild cannot download from S3. That would affect authentik, home-assistant and paperless (world removed), and immich, trmnl and woodpecker (new cluster-only baseline). In matrix, mx-pg-egress selects cnpg.io/cluster: mx-pg, so a recovery pod there would be limited to in-cluster traffic and DNS.
    • The comment in cnpg-barman-s3.yaml:1-4 only covers steady-state instance pods.
  • ESPHome flash Jobs. shelly-manager creates these at runtime (shelly-manager-firmware-rbac.yaml), and a build needs PlatformIO and GitHub downloads. The labels come from shelly-manager's code, not from this repo. Unless the Job pods carry app.kubernetes.io/name: esphome, esphome-egress does not cover them, and firmware builds will fail.
  • matter-server. Its default route is eth0 (matter-server/deployment.yaml, comment on --primary-interface). Any DCL certificate or OTA fetches it makes now go through Cilium with no world rule. Whether python-matter-server treats that as fatal is an upstream question.
  • Gitea. Mailer settings and push-mirror transport (SSH :22 or HTTPS) live in the on-PVC app.ini, which I can't read. If Gitea sends mail or uses SSH mirrors, the :443-only rule cuts them.
  • Syncthing relays. Public relay servers usually listen on 22067, which syncthing-egress does not allow. Off-LAN peers that need a relay may fail to connect.

🟡 Suggestions

  • default-deny.yaml:118: the gatus comment says "HTTP checks target in-cluster names". They actually target public *.renner.uno hosts, which k8s_gateway answers with the Traefik VIP. coredns-config/configmap.yaml documents that a resolution failure can fall back to the wildcard WAN-proxy answer, and gatus would then hit world:443 and report a false outage. Fix the comment, or accept that risk explicitly.
  • The PR says it was not exercised live. Consider applying it one namespace at a time, checking hubble observe --verdict DROPPED between steps, instead of all 24 files in one reconcile.

🟢 Nits

  • home-assistant-ns.yaml: the comment on home-assistant-trmnl-renderer-ingress still says the trmnl namespace has no egress policy covering the renderer. trmnl-baseline-egress now covers it (it is allowed via cluster).

Tests

These are manifest-only policy changes, and the repo has no policy behaviour tests. The PR reports kubectl apply --dry-run=server and kustomize build, and no live run. Before merging, I would like to see the Hubble drop check (above) after reconcile, plus three manual runs:

  • one mosquitto-bootstrap Job
  • one CNPG WAL archive
  • one velero/kopia run

Recommendation

Request changes: 1 blocking issue (the home-assistant mosquitto-bootstrap Jobs lose package-repo egress).

🤖 **Automated review** — model `claude-opus-5-5`, commit `6b6e1e7e041a322e94b86834f69edda48a6494d1`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Push new commits to get a fresh review. --- ## Summary This PR removes namespace-wide `world` egress in nine namespaces. It adds an in-cluster + DNS egress baseline where egress was fully open, and gives each workload only the external destinations it needs, using FQDN rules where the host is known and port-scoped `world` otherwise. It also adds one clusterwide policy that lets CNPG instance pods reach Hetzner S3. Almost every rule matches what I read in the manifests. I checked the cert-manager self-check (recursive-only), alertmanager and authentik SMTP, the `udm.renner.uno` and SLZB hostnames, the virtual-hosted velero bucket, terminus and sidekiq sharing one pod, the influx token jobs, and the `*.renner.uno` names that k8s_gateway answers with the Traefik VIP. One workload was missed, and the miss breaks the home-assistant rollout. The PR is mergeable, `main` has not moved past the base `55cd083`, and no other PR overlaps it. ## 🔴 Blocking - **`mosquitto-bootstrap` Jobs lose package-repo access, which fails the `home-assistant` Kustomization.** - **What the Jobs do:** `kubernetes/apps/home-assistant/mosquitto-bootstrap.yaml:67-68` and the three sibling Jobs in the same file run `set -eu` and then `apk add --no-cache curl jq`. The `apk add` happens before the "secret already exists" check, so it runs on every execution, including the no-op case. Their pods carry the label `app.kubernetes.io/name: mosquitto-bootstrap`. - **What changed:** `home-assistant-ns.yaml:143-145` drops `world` from the namespace-wide egress. None of the new rules in `home-assistant-ns.yaml:350-456` select these pods. I found no other CNP that gives them `world`. - **When it fails:** whenever the Jobs are recreated. That happens on a cold rebuild, and on any change to the pinned alpine digest, because the Jobs carry `kustomize.toolkit.fluxcd.io/force: enabled` (`:50`). Each Job then fails after `backoffLimit: 4`. - **Impact:** - The `home-assistant` Kustomization runs with `wait: true` (`clusters/homelab/home-assistant.yaml`), so a failed Job blocks its health check. `zone-configurator`, `speech-to-phrase` and `home-assistant-iot` depend on it. - On a cold rebuild, `mosquitto-credentials` and the other secrets never get created. The broker cannot start because `mosquitto-deployment.yaml` references them without `optional: true`. - **Fix:** grant these pods `world` on 443/80, as `influxdb-token-jobs-egress-world` already does for the influx jobs. Alternatively, switch the Jobs to the in-house `alpine-jq` image that zigbee2mqtt already uses, so nothing is installed at runtime. ## ⚪ Unverified - **CNPG recovery from backup on a cold rebuild.** `cnpg-barman-s3.yaml` selects only `cnpg.io/podRole: instance`, and nothing in the repo uses that label, so I could not confirm it. - First, run `kubectl get pods -A -l cnpg.io/podRole=instance` and check it lists every instance pod. - Second, `ha-pg-cluster.yaml` (like the other clusters) bootstraps with `recovery` from the barman store. If CNPG's `*-full-recovery` Job pods do not carry `podRole: instance`, a cold rebuild cannot download from S3. That would affect authentik, home-assistant and paperless (world removed), and immich, trmnl and woodpecker (new cluster-only baseline). In matrix, `mx-pg-egress` selects `cnpg.io/cluster: mx-pg`, so a recovery pod there would be limited to in-cluster traffic and DNS. - The comment in `cnpg-barman-s3.yaml:1-4` only covers steady-state instance pods. - **ESPHome flash Jobs.** shelly-manager creates these at runtime (`shelly-manager-firmware-rbac.yaml`), and a build needs PlatformIO and GitHub downloads. The labels come from shelly-manager's code, not from this repo. Unless the Job pods carry `app.kubernetes.io/name: esphome`, `esphome-egress` does not cover them, and firmware builds will fail. - **matter-server.** Its default route is `eth0` (`matter-server/deployment.yaml`, comment on `--primary-interface`). Any DCL certificate or OTA fetches it makes now go through Cilium with no `world` rule. Whether python-matter-server treats that as fatal is an upstream question. - **Gitea.** Mailer settings and push-mirror transport (SSH :22 or HTTPS) live in the on-PVC `app.ini`, which I can't read. If Gitea sends mail or uses SSH mirrors, the `:443`-only rule cuts them. - **Syncthing relays.** Public relay servers usually listen on 22067, which `syncthing-egress` does not allow. Off-LAN peers that need a relay may fail to connect. ## 🟡 Suggestions - `default-deny.yaml:118`: the gatus comment says "HTTP checks target in-cluster names". They actually target public `*.renner.uno` hosts, which k8s_gateway answers with the Traefik VIP. `coredns-config/configmap.yaml` documents that a resolution failure can fall back to the wildcard WAN-proxy answer, and gatus would then hit `world:443` and report a false outage. Fix the comment, or accept that risk explicitly. - The PR says it was not exercised live. Consider applying it one namespace at a time, checking `hubble observe --verdict DROPPED` between steps, instead of all 24 files in one reconcile. ## 🟢 Nits - `home-assistant-ns.yaml`: the comment on `home-assistant-trmnl-renderer-ingress` still says the trmnl namespace has no egress policy covering the renderer. `trmnl-baseline-egress` now covers it (it is allowed via `cluster`). ## Tests These are manifest-only policy changes, and the repo has no policy behaviour tests. The PR reports `kubectl apply --dry-run=server` and `kustomize build`, and no live run. Before merging, I would like to see the Hubble drop check (above) after reconcile, plus three manual runs: - one `mosquitto-bootstrap` Job - one CNPG WAL archive - one velero/kopia run ## Recommendation Request changes: 1 blocking issue (the home-assistant `mosquitto-bootstrap` Jobs lose package-repo egress). <!-- pr-review:sha=6b6e1e7e041a322e94b86834f69edda48a6494d1 -->
claude-bot added 1 commit 2026-10-24 15:26:06 +00:00
feat(netpol): allow mqtt-account-controller to deliver to edge-1
ci/woodpecker/pr/ci-manifests-test Pipeline was canceled
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
299194247e
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>
claude-bot added 1 commit 2026-10-24 15:26:51 +00:00
fix(netpol): let mosquitto-bootstrap Jobs reach Alpine mirrors
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
8a53171ad0
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>
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit 8a53171ad057474a04042648b0cbf04f8fee1341.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Push new commits to get a fresh review.


Summary

This is a re-review. Two commits landed since my last review at 6b6e1e7:

  • 2991942 adds mqtt-account-controller-egress, which allows edge-1.iot.renner.uno:8443.
  • 8a53171 adds mosquitto-bootstrap-egress and fixes the outdated comment about the renderer's egress.

State of my earlier points:

  • 🔴 mosquitto-bootstrap Jobs losing access to the Alpine package mirrors: fixed. All four Jobs carry the pod label app.kubernetes.io/name: mosquitto-bootstrap (kubernetes/apps/home-assistant/mosquitto-bootstrap.yaml:56,128,198,270,344). The new policy at home-assistant-ns.yaml:466-483 selects exactly that label and allows world on 443/80, the same way the influx token jobs are handled.
  • 🟡 Wrong comment on the gatus policy: still open (default-deny.yaml:118 is unchanged).
  • 🟡 Apply one namespace at a time: still open. This is about how to roll out, so it stays optional.
  • 🟢 Outdated comment about the renderer's egress: fixed (home-assistant-ns.yaml:260).

The new controller rule matches the repo. mqtt-accounts.yaml:61 names deliver: {esp32: "https://edge-1.iot.renner.uno:8443"}, and the selector at home-assistant-ns.yaml:440-442 matches the Deployment's pod label (mqtt-account-controller.yaml:209). The controller's other targets (broker :8884, shelly-manager-api, HA :8123) are all inside the namespace and stay covered by the namespace-wide policy.

The PR is mergeable. main is still at base 55cd083, and the only other open PRs (#1431, #1347) touch other files.

🟡 Suggestions

  • kubernetes/infrastructure/network-policies/default-deny.yaml:118 (carried over): the gatus comment says "HTTP checks target in-cluster names". They actually target public *.renner.uno hosts, and k8s_gateway answers those with the Traefik VIP. coredns-config/configmap.yaml documents that a resolution failure falls back to the WAN-proxy answer from the wildcard record. When that happens, gatus would try world:443 and report a false outage. Correct the comment, or accept that risk explicitly.

Tests

These are manifest-only policy changes, and the repo has no tests for policy behaviour. The new commits need the same live checks as before:

  • run one mosquitto-bootstrap Job
  • check that mqtt-account-controller delivers to edge-1 with no hubble observe --verdict DROPPED entries for its pod

Recommendation

Looks reasonable: the earlier blocking issue is fixed and the new commits add none. The gatus comment above is the only open suggestion, and it is optional.

🤖 **Automated review** — model `claude-opus-5-5`, commit `8a53171ad057474a04042648b0cbf04f8fee1341`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Push new commits to get a fresh review. --- ## Summary This is a re-review. Two commits landed since my last review at `6b6e1e7`: - `2991942` adds `mqtt-account-controller-egress`, which allows `edge-1.iot.renner.uno:8443`. - `8a53171` adds `mosquitto-bootstrap-egress` and fixes the outdated comment about the renderer's egress. State of my earlier points: - 🔴 `mosquitto-bootstrap` Jobs losing access to the Alpine package mirrors: **fixed**. All four Jobs carry the pod label `app.kubernetes.io/name: mosquitto-bootstrap` (`kubernetes/apps/home-assistant/mosquitto-bootstrap.yaml:56,128,198,270,344`). The new policy at `home-assistant-ns.yaml:466-483` selects exactly that label and allows `world` on 443/80, the same way the influx token jobs are handled. - 🟡 Wrong comment on the gatus policy: **still open** (`default-deny.yaml:118` is unchanged). - 🟡 Apply one namespace at a time: **still open**. This is about how to roll out, so it stays optional. - 🟢 Outdated comment about the renderer's egress: **fixed** (`home-assistant-ns.yaml:260`). The new controller rule matches the repo. `mqtt-accounts.yaml:61` names `deliver: {esp32: "https://edge-1.iot.renner.uno:8443"}`, and the selector at `home-assistant-ns.yaml:440-442` matches the Deployment's pod label (`mqtt-account-controller.yaml:209`). The controller's other targets (broker :8884, `shelly-manager-api`, HA :8123) are all inside the namespace and stay covered by the namespace-wide policy. The PR is mergeable. `main` is still at base `55cd083`, and the only other open PRs (#1431, #1347) touch other files. ## 🟡 Suggestions - `kubernetes/infrastructure/network-policies/default-deny.yaml:118` (carried over): the gatus comment says "HTTP checks target in-cluster names". They actually target public `*.renner.uno` hosts, and k8s_gateway answers those with the Traefik VIP. `coredns-config/configmap.yaml` documents that a resolution failure falls back to the WAN-proxy answer from the wildcard record. When that happens, gatus would try `world:443` and report a false outage. Correct the comment, or accept that risk explicitly. ## Tests These are manifest-only policy changes, and the repo has no tests for policy behaviour. The new commits need the same live checks as before: - run one `mosquitto-bootstrap` Job - check that `mqtt-account-controller` delivers to edge-1 with no `hubble observe --verdict DROPPED` entries for its pod ## Recommendation Looks reasonable: the earlier blocking issue is fixed and the new commits add none. The gatus comment above is the only open suggestion, and it is optional. <!-- pr-review:sha=8a53171ad057474a04042648b0cbf04f8fee1341 -->
claude-bot added 1 commit 2026-10-24 15:33:31 +00:00
revert(netpol): no world egress for mosquitto-bootstrap
ci/woodpecker/pr/ci-manifests-test Pipeline was successful
ci/woodpecker/pr/ci-scripts-test Pipeline was successful
5aee6a54ad
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>
review-bot left a comment
Member

🤖 Automated review — model claude-opus-5-5, commit 5aee6a54adfef02e217c343acd508b2f4806bf02.

Automated review by the review-bot: it approves only when it found nothing red or yellow, and a human still merges. Push new commits to get a fresh review.


Summary

This is a re-review. One commit has landed since my last review at 8a53171: 5aee6a5 deletes mosquitto-bootstrap-egress from home-assistant-ns.yaml. Its commit message says the Jobs "are removed in a separate PR, which must merge before this one."

State of my earlier points:

  • 🔴 mosquitto-bootstrap Jobs lose package-repo egress: open again. I marked it fixed at 8a53171, but 5aee6a5 reverted that fix, and the separate PR the commit message relies on does not exist (details below).
  • 🟡 gatus comment at default-deny.yaml:118: still open. The line is unchanged.
  • 🟡 Roll out one namespace at a time: optional, closed.
  • 🟢 Outdated renderer-egress comment: still fixed (home-assistant-ns.yaml:260).

The PR is mergeable. main is still at the base 55cd083.

🔴 Blocking

  • mosquitto-bootstrap Jobs lose package-repo access again, and the PR they depend on does not exist.
    • No policy covers them. At head, nothing selects pods labelled app.kubernetes.io/name: mosquitto-bootstrap. The namespace-wide policy allows only host and remote-node (home-assistant-ns.yaml:143-145), and none of the per-workload policies after line 345 select these pods.
    • The Jobs still need the internet. All five still run apk add --no-cache curl jq right after set -eu (kubernetes/apps/home-assistant/mosquitto-bootstrap.yaml:68,140,210,282,356). They carry kustomize.toolkit.fluxcd.io/force: enabled (:50 etc.).
    • They are still deployed. mosquitto-bootstrap.yaml is in kubernetes/apps/home-assistant/kustomization.yaml at both head and main.
    • No PR removes them. The only open PRs are #1431 (authentik-gate files only) and #1347 (Renovate, authentik). A search for mosquitto-bootstrap finds only closed PRs (#450, #1191) plus this one. Nothing makes this PR wait for the removal.
    • The Secrets they create are not unused. The commit message says the Jobs feed "only the unused legacy password_file". But mosquitto-deployment.yaml:146-178 reads all four Job-created Secrets with secretKeyRef and no optional: true. On a cold rebuild, or whenever a Secret is missing, the broker cannot start. A failing Job also blocks the home-assistant Kustomization health check, as described in my first review.
    • Fix: either land the PR that removes the Jobs first (it must also deal with these secretKeyRefs), then rebase this PR onto it, or restore mosquitto-bootstrap-egress until that PR exists.

🟡 Suggestions

  • kubernetes/infrastructure/network-policies/default-deny.yaml:118 (carried over): the gatus comment says "HTTP checks target in-cluster names". They actually target public *.renner.uno names, which k8s_gateway answers with the Traefik VIP. If that resolution ever falls back to the WAN answer, gatus would need world:443 and would report a false outage. Correct the comment, or accept that risk explicitly.
  • PR description: the "Now allowed external" table still lists mosquitto-bootstrap Jobs | world :443/:80 (apk add). Since 5aee6a5 that row is wrong. Update it, and link the PR that removes the Jobs once it exists, so the merge order can be checked.

Tests

These are manifest-only policy changes, and the repo has no tests for policy behaviour. Before merging, check one thing for the new commit: on whichever path is chosen, the home-assistant Kustomization must reconcile cleanly. If the Jobs are removed, they must be gone. If the policy is restored, a mosquitto-bootstrap Job must run with no hubble observe --verdict DROPPED entries for its pod.

Recommendation

Request changes: 1 blocking issue. The mosquitto-bootstrap egress fix was reverted, and the PR it now depends on does not exist.

🤖 **Automated review** — model `claude-opus-5-5`, commit `5aee6a54adfef02e217c343acd508b2f4806bf02`. > Automated review by the `review-bot`: it approves only when it found nothing red or yellow, and a human still merges. Push new commits to get a fresh review. --- ## Summary This is a re-review. One commit has landed since my last review at `8a53171`: `5aee6a5` deletes `mosquitto-bootstrap-egress` from `home-assistant-ns.yaml`. Its commit message says the Jobs "are removed in a separate PR, which must merge before this one." State of my earlier points: - 🔴 `mosquitto-bootstrap` Jobs lose package-repo egress: **open again**. I marked it fixed at `8a53171`, but `5aee6a5` reverted that fix, and the separate PR the commit message relies on does not exist (details below). - 🟡 gatus comment at `default-deny.yaml:118`: **still open**. The line is unchanged. - 🟡 Roll out one namespace at a time: optional, closed. - 🟢 Outdated renderer-egress comment: **still fixed** (`home-assistant-ns.yaml:260`). The PR is mergeable. `main` is still at the base `55cd083`. ## 🔴 Blocking - **`mosquitto-bootstrap` Jobs lose package-repo access again, and the PR they depend on does not exist.** - **No policy covers them.** At head, nothing selects pods labelled `app.kubernetes.io/name: mosquitto-bootstrap`. The namespace-wide policy allows only `host` and `remote-node` (`home-assistant-ns.yaml:143-145`), and none of the per-workload policies after line 345 select these pods. - **The Jobs still need the internet.** All five still run `apk add --no-cache curl jq` right after `set -eu` (`kubernetes/apps/home-assistant/mosquitto-bootstrap.yaml:68,140,210,282,356`). They carry `kustomize.toolkit.fluxcd.io/force: enabled` (`:50` etc.). - **They are still deployed.** `mosquitto-bootstrap.yaml` is in `kubernetes/apps/home-assistant/kustomization.yaml` at both head and `main`. - **No PR removes them.** The only open PRs are #1431 (authentik-gate files only) and #1347 (Renovate, authentik). A search for `mosquitto-bootstrap` finds only closed PRs (#450, #1191) plus this one. Nothing makes this PR wait for the removal. - **The Secrets they create are not unused.** The commit message says the Jobs feed "only the unused legacy password_file". But `mosquitto-deployment.yaml:146-178` reads all four Job-created Secrets with `secretKeyRef` and no `optional: true`. On a cold rebuild, or whenever a Secret is missing, the broker cannot start. A failing Job also blocks the `home-assistant` Kustomization health check, as described in my first review. - **Fix:** either land the PR that removes the Jobs first (it must also deal with these `secretKeyRef`s), then rebase this PR onto it, or restore `mosquitto-bootstrap-egress` until that PR exists. ## 🟡 Suggestions - `kubernetes/infrastructure/network-policies/default-deny.yaml:118` (carried over): the gatus comment says "HTTP checks target in-cluster names". They actually target public `*.renner.uno` names, which k8s_gateway answers with the Traefik VIP. If that resolution ever falls back to the WAN answer, gatus would need `world:443` and would report a false outage. Correct the comment, or accept that risk explicitly. - PR description: the "Now allowed external" table still lists `mosquitto-bootstrap Jobs | world :443/:80 (apk add)`. Since `5aee6a5` that row is wrong. Update it, and link the PR that removes the Jobs once it exists, so the merge order can be checked. ## Tests These are manifest-only policy changes, and the repo has no tests for policy behaviour. Before merging, check one thing for the new commit: on whichever path is chosen, the `home-assistant` Kustomization must reconcile cleanly. If the Jobs are removed, they must be gone. If the policy is restored, a `mosquitto-bootstrap` Job must run with no `hubble observe --verdict DROPPED` entries for its pod. ## Recommendation Request changes: 1 blocking issue. The `mosquitto-bootstrap` egress fix was reverted, and the PR it now depends on does not exist. <!-- pr-review:sha=5aee6a54adfef02e217c343acd508b2f4806bf02 -->
k.renner merged commit b341b26673 into main 2026-10-25 14:50:34 +00:00
Sign in to join this conversation.