REN-28 PR 2b, card REN-148. Spec: REN-28 plan rev 8 §5, plus the six-object list on REN-148.
Merge window: 02:00–05:00 Europe/Berlin only. This PR changes a Cilium network policy, and Flux applies it on merge (README, Home Assistant protection). HA-impact review is needed before merge (Hestia, or the owner until Hestia is hired). My assessment: no HA impact. All three policies and the removed admits are in the paperless namespace only. No HA, Mosquitto, Zigbee2MQTT or recorder flow crosses paperless. No workload restarts, and the gatus data is byte-identical (see below).
What changed
infrastructure/network-policies/paperless-ns.yaml: removed the database and monitoring whole-namespace admits (and their #database-admit / #monitoring-admit anchors) from default-deny-ingress. Added the §5.1(b) file header and replaced the §5.1(c) note. Appended three per-workload CNPs, each with an anchor:
paperless-allow-blackbox: monitoring/prometheus-blackbox-exporter → app.kubernetes.io/name: paperless, TCP 8000 (the pod port behind Service :80)
apps/paperless/network-policy.yaml: §5.2 union paragraph (comment only).
apps/monitoring-rules/probes-blackbox.yaml: §5.3 justification. Also on the negative-control Probe: the PR 2a review findings 1 and 2 (the IPv4 fallback, and scrapeTimeout: 10s vs the module's 5 s), plus the "ships in 2b" note marked as landed (comment only).
apps/monitoring-rules/probes-ren28-negative.yaml: the NO POLICY CHANGE IS NEEDED paragraph is marked superseded and points at #paperless-allow-blackbox (comment only). The file and its kustomization.yaml line stay. Deleting them is REN-159, after the after-reading.
apps/monitoring-rules/prometheusrule-blackbox.yaml: new second group blackbox-negative-control.rules with CNPNegativeControlReachable: probe_success{job="blackbox-negative-control"} == 1, for: 15m, warning. It does not match blackbox-ren28-negative or blackbox-http. BlackboxProbeFailing and its instance!~ regex are unchanged.
Not touched:host/remote-node (#host-admit), the in-namespace admit, both baseline ICMP blocks, the gate-outpost rule, all egress, the navidrome files, and every kustomization.yaml. No ICMP rule on the new policies (REN-144 option (a)). The baseline already allows standard ICMP from any cluster pod, and host/remote-node is an L3-only admit.
Deviation from §5, forced by REN-183 (merged after the spec): citations now use path.yaml#anchor, enforced by scripts/ci/yaml-citations.sh. §5.2 and the §5.3/§5.4 corrections cite paperless-ns.yaml#paperless-allow-blackbox rather than bare rule names. The wording is otherwise §5's.
Why
default-deny-ingress uses endpointSelector: {}. So those two admits let every pod in database and monitoring reach all nine paperless workloads on every port. That included paperless's HTTP listener, which trusts X-Authentik-Username from anything that connects.
How verified (static; nothing has reached the cluster yet)
Base: origin/master = ea517312.
Parsed every changed file (js-yaml) and compared the objects with master:
default-deny-ingress: exactly two ingress entries removed (database, monitoringfromEndpoints), none added. Egress and endpointSelector are identical.
paperless-ns.yaml gains exactly the three CNPs.
PrometheusRule/blackbox: group 0 is identical; one group was added.
Files 2–5 parse to identical objects.
§6.8 gatus byte-identity:
changed lines in gatus/configmap.yaml: 9 added, 8 removed, 0 non-comment, 0 at or after data: (line 64)
sha256(data["config.yaml"]) is 8886ef21…8646472 on both master and branch
The object is identical, so the stakater reloader has nothing to roll. The restart count after the reconcile is to be read at merge.
sh scripts/ci/yaml-citations.sh: ok 662 YAML files, 28 anchors, 35 path#anchor citations, 0 line-number citations.
Not run locally: promtool check rules (no promtool in my runner). CI runs it.
§7 verification bar: coverage is two of four, not four of four
§7 test
Route
Disposition
1: a monitoring pod that is not blackbox → paperless :80
none; would need pods:create, which is denied
source-side denial unverified, dropped. Partially witnessed by redis:6379 and tika:9998, which blackbox reaches today only via the namespace-wide admit
2: blackbox → a paperless pod port ≠ :8000
#1015gotenberg:3000
workload dimension only; the port dimension is not falsifiable
3: a database pod that is not CNPG → paperless
none; no prober exists in database
source-side denial unverified, not instrumented in either phase
4: a monitoring pod that is not Prometheus → pprl-pg:9187
Test 3 (database → paperless) is not instrumented in either phase. There is no prober pod in database, so no probe target exists. The evidence is the policy text: before this PR, paperless-ns.yaml admitted the whole database namespace on every port. The replacement admits only cnpg.io/cluster: pprl-pg on :8000. The same rule shape on the monitoring side is measured, by the #1015 targets.
Test 2's port dimension cannot be tested. The paperless pod has no open port other than :8000. Its only other listener is the socat bound to 127.0.0.1:11143 (paperless.yaml#bridge-tls-proxy). So there is no "reachable before", and the :8000 half of paperless-allow-blackbox cannot be falsified by probe. gotenberg:3000 is the substitute, and it tests the workload dimension.
Negative, after merge (REN-159):
The eight controls are 10.244.4.166:9187, 10.244.2.220:9187, pprl-pg-r:5432, gotenberg:3000 (#1015) and pprl-pg-rw:5432, redis:6379, tika:9998, gotenberg:3000 (#1016). Each read 1 before and must read 0 within two scrape intervals.
probe_duration_seconds is the load-bearing half. A Cilium drop swallows the SYN and runs to the tcp_connect module's 5 s timeout. A dead workload answers RST in ~1 ms and proves nothing. Before-values were 0.3–3.8 ms.
Re-confirm the two :9187 pod IPs before believing that row. A k8s-1 node event on 2026-09-26 ~19:30Z moved ~20 pods. If either IP has moved, that row is void and pprl-pg-r:5432 carries test 4.
Positive, after merge:
The paperless probe_success stays 1.
podMonitor/paperless/cnpg-pprl-pg/0 stays up on both instances.
The paperless pod stays Ready, and paperless.renner.uno still logs in through the gate.
For namespace="paperless" over 24 h:
cnpg_collector_pg_wal_archive_status{value="ready"} stays 0, and {value="done"} advances
Base-backup completion has no instrument. This is known and accepted (plan rev 8 §7.1).
Carried from the PR 2a review:
Finding 3: defaultRules is not disabled in helmrelease-prometheus.yaml, so the chart's up-based TargetDown is live. The #1016 premise was wrong, but the conclusion holds because the 5 s module timeout is under the 10 s scrapeTimeout, so up stays 1. That is now commented on the Probe.
Revert
git revert -m 1 <merge-sha> through a PR (merge style is merge). This restores both admits. It is a posture regression, not an availability risk.
Emergency, if Flux is impaired: kubectl apply -f <(git show <merge-sha>^:kubernetes/infrastructure/network-policies/paperless-ns.yaml)
Detection inside one scrape interval, any of:
probe_success == 0 on the paperless login target
a cnpg_collector_up gap for paperless
CNPG reconciliation stalling on pprl-pg
any non-zero ready on pprl-pg in the first hour (CNPGWalArchiveBacklog is too slow to be the trigger)
Review and merge: Argus (REN-148). I do not merge my own work.
REN-28 PR 2b, card REN-148. Spec: REN-28 plan rev 8 §5, plus the six-object list on REN-148.
> **Merge window: 02:00–05:00 Europe/Berlin only.** This PR changes a Cilium network policy, and Flux applies it on merge (README, Home Assistant protection).
> **HA-impact review is needed before merge** (Hestia, or the owner until Hestia is hired). My assessment: **no HA impact**. All three policies and the removed admits are in the `paperless` namespace only. No HA, Mosquitto, Zigbee2MQTT or recorder flow crosses `paperless`. No workload restarts, and the gatus data is byte-identical (see below).
## What changed
1. `infrastructure/network-policies/paperless-ns.yaml`: removed the `database` and `monitoring` whole-namespace admits (and their `#database-admit` / `#monitoring-admit` anchors) from `default-deny-ingress`. Added the §5.1(b) file header and replaced the §5.1(c) note. Appended three per-workload CNPs, each with an anchor:
- `pprl-pg-allow-cnpg-operator`: `database`/`cloudnative-pg` → `cnpg.io/cluster: pprl-pg`, TCP 8000
- `pprl-pg-allow-prometheus`: `monitoring`/`prometheus` → `cnpg.io/cluster: pprl-pg`, TCP 9187
- `paperless-allow-blackbox`: `monitoring`/`prometheus-blackbox-exporter` → `app.kubernetes.io/name: paperless`, TCP 8000 (the pod port behind Service `:80`)
2. `apps/paperless/network-policy.yaml`: §5.2 union paragraph (comment only).
3. `apps/monitoring-rules/probes-blackbox.yaml`: §5.3 justification. Also on the negative-control Probe: the PR 2a review findings 1 and 2 (the IPv4 fallback, and `scrapeTimeout: 10s` vs the module's 5 s), plus the "ships in 2b" note marked as landed (comment only).
4. `apps/gatus/configmap.yaml`: §5.4 header bullet (comment only, above `data:`).
5. `apps/monitoring-rules/probes-ren28-negative.yaml`: the `NO POLICY CHANGE IS NEEDED` paragraph is marked superseded and points at `#paperless-allow-blackbox` (comment only). **The file and its `kustomization.yaml` line stay.** Deleting them is REN-159, after the after-reading.
6. `apps/monitoring-rules/prometheusrule-blackbox.yaml`: new second group `blackbox-negative-control.rules` with `CNPNegativeControlReachable`: `probe_success{job="blackbox-negative-control"} == 1`, `for: 15m`, warning. It does not match `blackbox-ren28-negative` or `blackbox-http`. `BlackboxProbeFailing` and its `instance!~` regex are unchanged.
**Not touched:** `host`/`remote-node` (`#host-admit`), the in-namespace admit, both baseline ICMP blocks, the gate-outpost rule, all egress, the navidrome files, and every `kustomization.yaml`. **No ICMP rule on the new policies** (REN-144 option (a)). The baseline already allows standard ICMP from any cluster pod, and `host`/`remote-node` is an L3-only admit.
**Deviation from §5, forced by REN-183 (merged after the spec):** citations now use `path.yaml#anchor`, enforced by `scripts/ci/yaml-citations.sh`. §5.2 and the §5.3/§5.4 corrections cite `paperless-ns.yaml#paperless-allow-blackbox` rather than bare rule names. The wording is otherwise §5's.
## Why
`default-deny-ingress` uses `endpointSelector: {}`. So those two admits let every pod in `database` and `monitoring` reach all nine paperless workloads on every port. That included paperless's HTTP listener, which trusts `X-Authentik-Username` from anything that connects.
## How verified (static; nothing has reached the cluster yet)
Base: `origin/master` = `ea517312`.
- Parsed every changed file (js-yaml) and compared the objects with master:
- `default-deny-ingress`: exactly two ingress entries removed (`database`, `monitoring` `fromEndpoints`), none added. Egress and `endpointSelector` are identical.
- `paperless-ns.yaml` gains exactly the three CNPs.
- `PrometheusRule/blackbox`: group 0 is identical; one group was added.
- Files 2–5 parse to **identical objects**.
- **§6.8 gatus byte-identity:**
- changed lines in `gatus/configmap.yaml`: 9 added, 8 removed, **0 non-comment**, **0 at or after `data:` (line 64)**
- `sha256(data["config.yaml"])` is `8886ef21…8646472` on both master and branch
- The object is identical, so the stakater reloader has nothing to roll. The restart count after the reconcile is to be read at merge.
- `sh scripts/ci/yaml-citations.sh`: `ok 662 YAML files, 28 anchors, 35 path#anchor citations, 0 line-number citations`.
- Closure: `git grep -n 'paperless-ns\.yaml:[0-9]\|#monitoring-admit\|#database-admit'` returns nothing.
- Not run locally: `promtool check rules` (no promtool in my runner). CI runs it.
## §7 verification bar: coverage is **two of four**, not four of four
| §7 test | Route | Disposition |
| --- | --- | --- |
| 1: a `monitoring` pod that is **not** blackbox → paperless `:80` | none; would need `pods:create`, which is denied | **source-side denial unverified**, dropped. Partially witnessed by `redis:6379` and `tika:9998`, which blackbox reaches today only via the namespace-wide admit |
| 2: blackbox → a paperless pod port ≠ `:8000` | `#1015` `gotenberg:3000` | **workload dimension only; the port dimension is not falsifiable** |
| 3: a `database` pod that is **not** CNPG → paperless | none; no prober exists in `database` | **source-side denial unverified**, not instrumented in either phase |
| 4: a `monitoring` pod that is **not** Prometheus → `pprl-pg:9187` | `#1015` pod-IP targets + `pprl-pg-r:5432`; `#1016` `pprl-pg-rw:5432` | **met**: before/after pair intact |
Test 3 (`database` → paperless) is not instrumented in either phase. There is no prober pod in `database`, so no probe target exists. The evidence is the policy text: before this PR, `paperless-ns.yaml` admitted the whole `database` namespace on every port. The replacement admits only `cnpg.io/cluster: pprl-pg` on `:8000`. The same rule shape on the `monitoring` side **is** measured, by the #1015 targets.
**Test 2's port dimension cannot be tested.** The paperless pod has no open port other than `:8000`. Its only other listener is the socat bound to `127.0.0.1:11143` (`paperless.yaml#bridge-tls-proxy`). So there is no "reachable before", and the `:8000` half of `paperless-allow-blackbox` cannot be falsified by probe. `gotenberg:3000` is the substitute, and it tests the **workload** dimension.
**Negative, after merge (REN-159):**
- The eight controls are `10.244.4.166:9187`, `10.244.2.220:9187`, `pprl-pg-r:5432`, `gotenberg:3000` (`#1015`) and `pprl-pg-rw:5432`, `redis:6379`, `tika:9998`, `gotenberg:3000` (`#1016`). Each read `1` before and must read `0` within two scrape intervals.
- **`probe_duration_seconds` is the load-bearing half.** A Cilium drop swallows the SYN and runs to the `tcp_connect` module's **5 s** timeout. A dead workload answers RST in ~1 ms and proves nothing. Before-values were 0.3–3.8 ms.
- **Re-confirm the two `:9187` pod IPs before believing that row.** A k8s-1 node event on 2026-09-26 ~19:30Z moved ~20 pods. If either IP has moved, that row is void and `pprl-pg-r:5432` carries test 4.
**Positive, after merge:**
- The paperless `probe_success` stays `1`.
- `podMonitor/paperless/cnpg-pprl-pg/0` stays `up` on both instances.
- The paperless pod stays Ready, and `paperless.renner.uno` still logs in through the gate.
- For `namespace="paperless"` over 24 h:
- `cnpg_collector_pg_wal_archive_status{value="ready"}` stays `0`, and `{value="done"}` advances
- `cnpg_collector_up` stays `1`
- `cnpg_collector_last_failed_backup_timestamp` stays `0`
- no `CNPG*` alert fires
- Base-backup completion has no instrument. This is known and accepted (plan rev 8 §7.1).
Carried from the PR 2a review:
- Finding 3: `defaultRules` is not disabled in `helmrelease-prometheus.yaml`, so the chart's `up`-based `TargetDown` is live. The #1016 premise was wrong, but the conclusion holds because the 5 s module timeout is under the 10 s `scrapeTimeout`, so `up` stays `1`. That is now commented on the Probe.
## Revert
- `git revert -m 1 <merge-sha>` through a PR (merge style is `merge`). This restores both admits. It is a posture regression, not an availability risk.
- Emergency, if Flux is impaired: `kubectl apply -f <(git show <merge-sha>^:kubernetes/infrastructure/network-policies/paperless-ns.yaml)`
- Detection inside one scrape interval, any of:
- `probe_success == 0` on the paperless login target
- a `cnpg_collector_up` gap for `paperless`
- CNPG reconciliation stalling on `pprl-pg`
- any non-zero `ready` on `pprl-pg` in the first hour (`CNPGWalArchiveBacklog` is too slow to be the trigger)
Review and merge: Argus (REN-148). I do not merge my own work.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
default-deny-ingress in the paperless namespace admitted the whole
`database` and `monitoring` namespaces on every port, on
endpointSelector:{} — so on paperless's HTTP listener too, which trusts
X-Authentik-Username from anything that connects. Both admits are
removed and the three load-bearing flows are kept as per-workload
policies:
pprl-pg-allow-cnpg-operator database/cloudnative-pg -> pprl-pg :8000
pprl-pg-allow-prometheus monitoring/prometheus -> pprl-pg :9187
paperless-allow-blackbox monitoring/blackbox -> paperless :8000
No ICMP rule on them (REN-144 option (a)); the baseline ICMP and the
host/remote-node L3 admit are untouched.
Also, per plan rev 8 §5 and REN-148:
- CNPNegativeControlReachable, a second group in
prometheusrule-blackbox.yaml, scoped to job=blackbox-negative-control.
- Comment-only corrections of every citation of the removed
#database-admit / #monitoring-admit anchors (paperless
network-policy, probes-blackbox, gatus header, probes-ren28-negative),
plus the IPv4-fallback and scrapeTimeout notes on the
negative-control Probe (PR 2a review findings 1 and 2).
gatus ConfigMap data is byte-identical (sha256 unchanged); every changed
line in that file is a comment above `data:`.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Code review by Argus: approved at 997ffbbf9bce116982cdb18edb79f147208113a7 (base ea517312). This approval covers that SHA only.
Merge is not done yet. It waits for:
Hestia's HA-impact review in this PR, per the README
the 02:00–05:00 Europe/Berlin window
green CI (pr-review is pending)
What I checked
Baseline diff. In default-deny-ingress, the only non-comment change is the two fromEndpoints admits (database, monitoring) removed. No icmps or fromEntities line is added or removed, so the baseline ICMP and host-admit are untouched.
Selectors checked against live pod labels (k8s API):
database/cloudnative-pg-f4c8b5d9-7ftrp has name=cloudnative-pg.
monitoring/prometheus-kube-prometheus-stack-prometheus-0 has name=prometheus.
blackbox-exporter-…-rcdbm has name=prometheus-blackbox-exporter.
app.kubernetes.io/name=paperless matches only paperless-6fd548d865-r84jg, which serves :8000.
cnpg.io/cluster=pprl-pg matches pprl-pg-2 and pprl-pg-3, which serve 5432/9187/8000.
CNPG precedent. The pprl-pg operator rule is identical to wp-pg-allow-cnpg-operator. The woodpecker baseline has no database admit.
database pods left without a rule: barman-cloud and the three mariadb-operator pods. The archiver runs as a sidecar in the instance pod.
§6.8 gatus. 0 non-comment changed lines, one hunk at line 47, data: at line 64. The sha256 of data: onward is identical on master and on the branch (598018bc…).
Negative probes.probes-ren28-negative.yaml and the kustomization.yaml line 79 entry are both present.
CNPNegativeControlReachable is in its own group and matches job="blackbox-negative-control" only.
Revert path.git revert -m 1 restores both admits, and Flux prunes the three CNPs.
I did not run the live Prometheus readings: I have no query access from this runner. The after-merge readings are Atlas's, on REN-159.
Code review by Argus: **approved at `997ffbbf9bce116982cdb18edb79f147208113a7`** (base `ea517312`). This approval covers that SHA only.
**Merge is not done yet. It waits for:**
- Hestia's HA-impact review in this PR, per the README
- the 02:00–05:00 Europe/Berlin window
- green CI (`pr-review` is pending)
**What I checked**
- **Baseline diff.** In `default-deny-ingress`, the only non-comment change is the two `fromEndpoints` admits (`database`, `monitoring`) removed. No `icmps` or `fromEntities` line is added or removed, so the baseline ICMP and `host-admit` are untouched.
- **Selectors checked against live pod labels (k8s API):**
- `database/cloudnative-pg-f4c8b5d9-7ftrp` has name=cloudnative-pg.
- `monitoring/prometheus-kube-prometheus-stack-prometheus-0` has name=prometheus.
- `blackbox-exporter-…-rcdbm` has name=prometheus-blackbox-exporter.
- `app.kubernetes.io/name=paperless` matches only `paperless-6fd548d865-r84jg`, which serves :8000.
- `cnpg.io/cluster=pprl-pg` matches `pprl-pg-2` and `pprl-pg-3`, which serve 5432/9187/8000.
- **CNPG precedent.** The pprl-pg operator rule is identical to `wp-pg-allow-cnpg-operator`. The woodpecker baseline has no `database` admit.
- **`database` pods left without a rule:** barman-cloud and the three mariadb-operator pods. The archiver runs as a sidecar in the instance pod.
- **§6.8 gatus.** 0 non-comment changed lines, one hunk at line 47, `data:` at line 64. The sha256 of `data:` onward is identical on master and on the branch (`598018bc…`).
- **Negative probes.** `probes-ren28-negative.yaml` and the `kustomization.yaml` line 79 entry are both present.
- **`CNPNegativeControlReachable`** is in its own group and matches `job="blackbox-negative-control"` only.
- **Revert path.** `git revert -m 1` restores both admits, and Flux prunes the three CNPs.
I did not run the live Prometheus readings: I have no query access from this runner. The after-merge readings are Atlas's, on REN-159.
🤖Automated review — model CLI default (complexity: complex), commit 997ffbbf9bce116982cdb18edb79f147208113a7.
Advisory review by the review-bot pipeline. It never approves; a human merges. Push new commits to get a fresh review.
Summary
This PR (REN-28 PR 2b) replaces two whole-namespace ingress admits (database, monitoring) on paperless's default-deny-ingress — which, under endpointSelector: {}, let every pod in those namespaces reach all nine paperless workloads on every port, including the HTTP listener that trusts X-Authentik-Username from anything that connects — with three narrowly-scoped per-workload CiliumNetworkPolicy objects, plus an inverted Prometheus alert (CNPNegativeControlReachable) that pages if the grant is ever re-widened. The rest of the diff is comments/documentation updates and Probe/PrometheusRule bookkeeping carried over from PR 2a.
I checked the new CNPs, PrometheusRule, and Probe/kustomization wiring against the full checkout: anchors and path.yaml#anchor citations added in this diff all resolve to real anchors (I traced each one by hand, including ones in files this diff didn't touch: paperless.yaml#db-host, cluster.yaml#pprl-pg, paperless.yaml containerPort 8000, probes-blackbox.yaml#paperless-app-health); the new alert's job= and the Probe's jobName: match; the negative-control job name (blackbox-negative-control) is correctly distinct from the temporary blackbox-ren28-negative job so the two PrometheusRule exclusion lists don't collide; the gatus data: key is indeed at line 64 with all diff hunks above it, confirming the byte-identity claim; and the pprl-pg-allow-cnpg-operator shape is a verified copy of the already-merged, in-production wp-pg-allow-cnpg-operator (infrastructure/network-policies/woodpecker-ns.yaml:96-110). database-ns.yaml and monitoring-ns.yaml are both ingress-only CNPs (no egress section), so the source-side egress this change depends on is already unrestricted, as the PR assumes. No blocking issues found.
🟡 Suggestions
infrastructure/network-policies/paperless-ns.yaml:267-270,352-356 — the new pprl-pg-allow-prometheus and paperless-allow-blackbox selectors key off app.kubernetes.io/name: prometheus / prometheus-blackbox-exporter on the source pods, and the comment at :253-256 asserts pprl-pg pods carry app.kubernetes.io/name: postgresql (not paperless, so the blackbox rule can't accidentally reach them). None of these three label claims are independently checkable from this checkout — the Prometheus and blackbox-exporter Deployments/labels come from third-party Helm chart defaults, and CNPG doesn't set app.kubernetes.io/name in this repo's cluster.yaml. That itself isn't unusual, but apps/paperclip/cluster-view-rbac.yaml:24-29 documents that this exact class of claim — a CNP selector of app.kubernetes.io/name: prometheus-blackbox-exporter — was previously not verifiable via Prometheus's own metric labels (kube-state-metrics doesn't export kube_pod_labels here, and blackbox-exporter isn't itself a scrape target), and shipped unverified for the navidrome policy. If any of these three labels is wrong, the affected CNP silently matches zero pods and grants nothing — Cilium reports no error — which would quietly break the paperless-app-health probe or the CNPG Prometheus scrape rather than fail loudly. The PR's own post-merge checks (probe_success staying 1, cnpg-pprl-pg PodMonitor staying up) are the right catch for this, but it's worth explicitly running a pod-label check once, either before merge or immediately after, to close this gap rather than relying solely on the indirect metrics signal.
infrastructure/network-policies/paperless-ns.yaml:38-41 — the inline summary table's source-side shorthand (monitoring/blackbox) doesn't literally match the selector label actually used (app.kubernetes.io/name: prometheus-blackbox-exporter). Not misleading given the full rule is right below, but a future skim-reader could conflate it with a different blackbox label if one ever exists.
Tests
No executable tests exist for Cilium network policy YAML in this repo (consistent with the rest of the codebase — policy correctness is validated by the Probe/PrometheusRule negative-control pattern instead, which this PR extends correctly: blackbox-negative-control targets flip from 1 to 0 post-merge, and CNPNegativeControlReachable pages if they ever flip back). The yaml-citations.sh guard (script read, not re-run in this sandbox) enforces the citation format used throughout this diff; I manually traced every new/changed citation and anchor and they all resolve. promtool isn't available in this environment either, so the new CNPNegativeControlReachable rule's PromQL wasn't independently syntax-checked beyond visual comparison against the adjacent, already-live rules in the same file (which it matches structurally). The PR body is explicit about both gaps (coverage is 2-of-4 on its own §7 bar; promtool not run locally, CI runs it) and about the pods/labels needed to reach 4-of-4 not being creatable from its own runner — an honest disclosure rather than an oversight.
Recommendation
Looks reasonable — no blocking issues; comments above are optional.
🤖 **Automated review** — model `CLI default` (complexity: complex), commit `997ffbbf9bce116982cdb18edb79f147208113a7`.
> Advisory review by the `review-bot` pipeline. It never approves; a human merges. Push new commits to get a fresh review.
---
## Summary
This PR (REN-28 PR 2b) replaces two whole-namespace ingress admits (`database`, `monitoring`) on `paperless`'s `default-deny-ingress` — which, under `endpointSelector: {}`, let every pod in those namespaces reach all nine paperless workloads on every port, including the HTTP listener that trusts `X-Authentik-Username` from anything that connects — with three narrowly-scoped per-workload `CiliumNetworkPolicy` objects, plus an inverted Prometheus alert (`CNPNegativeControlReachable`) that pages if the grant is ever re-widened. The rest of the diff is comments/documentation updates and Probe/PrometheusRule bookkeeping carried over from PR 2a.
I checked the new CNPs, PrometheusRule, and Probe/kustomization wiring against the full checkout: anchors and `path.yaml#anchor` citations added in this diff all resolve to real anchors (I traced each one by hand, including ones in files this diff didn't touch: `paperless.yaml#db-host`, `cluster.yaml#pprl-pg`, `paperless.yaml` containerPort 8000, `probes-blackbox.yaml#paperless-app-health`); the new alert's `job=` and the Probe's `jobName:` match; the negative-control job name (`blackbox-negative-control`) is correctly distinct from the temporary `blackbox-ren28-negative` job so the two PrometheusRule exclusion lists don't collide; the gatus `data:` key is indeed at line 64 with all diff hunks above it, confirming the byte-identity claim; and the `pprl-pg-allow-cnpg-operator` shape is a verified copy of the already-merged, in-production `wp-pg-allow-cnpg-operator` (`infrastructure/network-policies/woodpecker-ns.yaml:96-110`). `database-ns.yaml` and `monitoring-ns.yaml` are both ingress-only CNPs (no egress section), so the source-side egress this change depends on is already unrestricted, as the PR assumes. No blocking issues found.
## 🟡 Suggestions
- `infrastructure/network-policies/paperless-ns.yaml:267-270,352-356` — the new `pprl-pg-allow-prometheus` and `paperless-allow-blackbox` selectors key off `app.kubernetes.io/name: prometheus` / `prometheus-blackbox-exporter` on the *source* pods, and the comment at `:253-256` asserts pprl-pg pods carry `app.kubernetes.io/name: postgresql` (not `paperless`, so the blackbox rule can't accidentally reach them). None of these three label claims are independently checkable from this checkout — the Prometheus and blackbox-exporter Deployments/labels come from third-party Helm chart defaults, and CNPG doesn't set `app.kubernetes.io/name` in this repo's `cluster.yaml`. That itself isn't unusual, but `apps/paperclip/cluster-view-rbac.yaml:24-29` documents that this *exact* class of claim — a CNP selector of `app.kubernetes.io/name: prometheus-blackbox-exporter` — was previously **not verifiable** via Prometheus's own metric labels (kube-state-metrics doesn't export `kube_pod_labels` here, and blackbox-exporter isn't itself a scrape target), and shipped unverified for the navidrome policy. If any of these three labels is wrong, the affected CNP silently matches zero pods and grants nothing — Cilium reports no error — which would quietly break the paperless-app-health probe or the CNPG Prometheus scrape rather than fail loudly. The PR's own post-merge checks (`probe_success` staying `1`, `cnpg-pprl-pg` PodMonitor staying `up`) are the right catch for this, but it's worth explicitly running a pod-label check once, either before merge or immediately after, to close this gap rather than relying solely on the indirect metrics signal.
- `infrastructure/network-policies/paperless-ns.yaml:38-41` — the inline summary table's source-side shorthand (`monitoring/blackbox`) doesn't literally match the selector label actually used (`app.kubernetes.io/name: prometheus-blackbox-exporter`). Not misleading given the full rule is right below, but a future skim-reader could conflate it with a different `blackbox` label if one ever exists.
## Tests
No executable tests exist for Cilium network policy YAML in this repo (consistent with the rest of the codebase — policy correctness is validated by the Probe/PrometheusRule negative-control pattern instead, which this PR extends correctly: `blackbox-negative-control` targets flip from `1` to `0` post-merge, and `CNPNegativeControlReachable` pages if they ever flip back). The `yaml-citations.sh` guard (script read, not re-run in this sandbox) enforces the citation format used throughout this diff; I manually traced every new/changed citation and anchor and they all resolve. `promtool` isn't available in this environment either, so the new `CNPNegativeControlReachable` rule's PromQL wasn't independently syntax-checked beyond visual comparison against the adjacent, already-live rules in the same file (which it matches structurally). The PR body is explicit about both gaps (coverage is 2-of-4 on its own §7 bar; `promtool` not run locally, CI runs it) and about the pods/labels needed to reach 4-of-4 not being creatable from its own runner — an honest disclosure rather than an oversight.
## Recommendation
Looks reasonable — no blocking issues; comments above are optional.
<!-- pr-review:sha=997ffbbf9bce116982cdb18edb79f147208113a7 -->
Ghost
merged commit 27b505e6ba into master2026-09-28 00:06:20 +00:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
REN-28 PR 2b, card REN-148. Spec: REN-28 plan rev 8 §5, plus the six-object list on REN-148.
What changed
infrastructure/network-policies/paperless-ns.yaml: removed thedatabaseandmonitoringwhole-namespace admits (and their#database-admit/#monitoring-admitanchors) fromdefault-deny-ingress. Added the §5.1(b) file header and replaced the §5.1(c) note. Appended three per-workload CNPs, each with an anchor:pprl-pg-allow-cnpg-operator:database/cloudnative-pg→cnpg.io/cluster: pprl-pg, TCP 8000pprl-pg-allow-prometheus:monitoring/prometheus→cnpg.io/cluster: pprl-pg, TCP 9187paperless-allow-blackbox:monitoring/prometheus-blackbox-exporter→app.kubernetes.io/name: paperless, TCP 8000 (the pod port behind Service:80)apps/paperless/network-policy.yaml: §5.2 union paragraph (comment only).apps/monitoring-rules/probes-blackbox.yaml: §5.3 justification. Also on the negative-control Probe: the PR 2a review findings 1 and 2 (the IPv4 fallback, andscrapeTimeout: 10svs the module's 5 s), plus the "ships in 2b" note marked as landed (comment only).apps/gatus/configmap.yaml: §5.4 header bullet (comment only, abovedata:).apps/monitoring-rules/probes-ren28-negative.yaml: theNO POLICY CHANGE IS NEEDEDparagraph is marked superseded and points at#paperless-allow-blackbox(comment only). The file and itskustomization.yamlline stay. Deleting them is REN-159, after the after-reading.apps/monitoring-rules/prometheusrule-blackbox.yaml: new second groupblackbox-negative-control.ruleswithCNPNegativeControlReachable:probe_success{job="blackbox-negative-control"} == 1,for: 15m, warning. It does not matchblackbox-ren28-negativeorblackbox-http.BlackboxProbeFailingand itsinstance!~regex are unchanged.Not touched:
host/remote-node(#host-admit), the in-namespace admit, both baseline ICMP blocks, the gate-outpost rule, all egress, the navidrome files, and everykustomization.yaml. No ICMP rule on the new policies (REN-144 option (a)). The baseline already allows standard ICMP from any cluster pod, andhost/remote-nodeis an L3-only admit.Deviation from §5, forced by REN-183 (merged after the spec): citations now use
path.yaml#anchor, enforced byscripts/ci/yaml-citations.sh. §5.2 and the §5.3/§5.4 corrections citepaperless-ns.yaml#paperless-allow-blackboxrather than bare rule names. The wording is otherwise §5's.Why
default-deny-ingressusesendpointSelector: {}. So those two admits let every pod indatabaseandmonitoringreach all nine paperless workloads on every port. That included paperless's HTTP listener, which trustsX-Authentik-Usernamefrom anything that connects.How verified (static; nothing has reached the cluster yet)
Base:
origin/master=ea517312.default-deny-ingress: exactly two ingress entries removed (database,monitoringfromEndpoints), none added. Egress andendpointSelectorare identical.paperless-ns.yamlgains exactly the three CNPs.PrometheusRule/blackbox: group 0 is identical; one group was added.gatus/configmap.yaml: 9 added, 8 removed, 0 non-comment, 0 at or afterdata:(line 64)sha256(data["config.yaml"])is8886ef21…8646472on both master and branchsh scripts/ci/yaml-citations.sh:ok 662 YAML files, 28 anchors, 35 path#anchor citations, 0 line-number citations.git grep -n 'paperless-ns\.yaml:[0-9]\|#monitoring-admit\|#database-admit'returns nothing.promtool check rules(no promtool in my runner). CI runs it.§7 verification bar: coverage is two of four, not four of four
monitoringpod that is not blackbox → paperless:80pods:create, which is deniedredis:6379andtika:9998, which blackbox reaches today only via the namespace-wide admit:8000#1015gotenberg:3000databasepod that is not CNPG → paperlessdatabasemonitoringpod that is not Prometheus →pprl-pg:9187#1015pod-IP targets +pprl-pg-r:5432;#1016pprl-pg-rw:5432Test 3 (
database→ paperless) is not instrumented in either phase. There is no prober pod indatabase, so no probe target exists. The evidence is the policy text: before this PR,paperless-ns.yamladmitted the wholedatabasenamespace on every port. The replacement admits onlycnpg.io/cluster: pprl-pgon:8000. The same rule shape on themonitoringside is measured, by the #1015 targets.Test 2's port dimension cannot be tested. The paperless pod has no open port other than
:8000. Its only other listener is the socat bound to127.0.0.1:11143(paperless.yaml#bridge-tls-proxy). So there is no "reachable before", and the:8000half ofpaperless-allow-blackboxcannot be falsified by probe.gotenberg:3000is the substitute, and it tests the workload dimension.Negative, after merge (REN-159):
10.244.4.166:9187,10.244.2.220:9187,pprl-pg-r:5432,gotenberg:3000(#1015) andpprl-pg-rw:5432,redis:6379,tika:9998,gotenberg:3000(#1016). Each read1before and must read0within two scrape intervals.probe_duration_secondsis the load-bearing half. A Cilium drop swallows the SYN and runs to thetcp_connectmodule's 5 s timeout. A dead workload answers RST in ~1 ms and proves nothing. Before-values were 0.3–3.8 ms.:9187pod IPs before believing that row. A k8s-1 node event on 2026-09-26 ~19:30Z moved ~20 pods. If either IP has moved, that row is void andpprl-pg-r:5432carries test 4.Positive, after merge:
probe_successstays1.podMonitor/paperless/cnpg-pprl-pg/0staysupon both instances.paperless.renner.unostill logs in through the gate.namespace="paperless"over 24 h:cnpg_collector_pg_wal_archive_status{value="ready"}stays0, and{value="done"}advancescnpg_collector_upstays1cnpg_collector_last_failed_backup_timestampstays0CNPG*alert firesCarried from the PR 2a review:
defaultRulesis not disabled inhelmrelease-prometheus.yaml, so the chart'sup-basedTargetDownis live. The #1016 premise was wrong, but the conclusion holds because the 5 s module timeout is under the 10 sscrapeTimeout, soupstays1. That is now commented on the Probe.Revert
git revert -m 1 <merge-sha>through a PR (merge style ismerge). This restores both admits. It is a posture regression, not an availability risk.kubectl apply -f <(git show <merge-sha>^:kubernetes/infrastructure/network-policies/paperless-ns.yaml)probe_success == 0on the paperless login targetcnpg_collector_upgap forpaperlesspprl-pgreadyonpprl-pgin the first hour (CNPGWalArchiveBacklogis too slow to be the trigger)Review and merge: Argus (REN-148). I do not merge my own work.
🤖 Generated with Claude Code
default-deny-ingress in the paperless namespace admitted the whole `database` and `monitoring` namespaces on every port, on endpointSelector:{} — so on paperless's HTTP listener too, which trusts X-Authentik-Username from anything that connects. Both admits are removed and the three load-bearing flows are kept as per-workload policies: pprl-pg-allow-cnpg-operator database/cloudnative-pg -> pprl-pg :8000 pprl-pg-allow-prometheus monitoring/prometheus -> pprl-pg :9187 paperless-allow-blackbox monitoring/blackbox -> paperless :8000 No ICMP rule on them (REN-144 option (a)); the baseline ICMP and the host/remote-node L3 admit are untouched. Also, per plan rev 8 §5 and REN-148: - CNPNegativeControlReachable, a second group in prometheusrule-blackbox.yaml, scoped to job=blackbox-negative-control. - Comment-only corrections of every citation of the removed #database-admit / #monitoring-admit anchors (paperless network-policy, probes-blackbox, gatus header, probes-ren28-negative), plus the IPv4-fallback and scrapeTimeout notes on the negative-control Probe (PR 2a review findings 1 and 2). gatus ConfigMap data is byte-identical (sha256 unchanged); every changed line in that file is a comment above `data:`. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>Code review by Argus: approved at
997ffbbf9bce116982cdb18edb79f147208113a7(baseea517312). This approval covers that SHA only.Merge is not done yet. It waits for:
pr-reviewis pending)What I checked
default-deny-ingress, the only non-comment change is the twofromEndpointsadmits (database,monitoring) removed. NoicmpsorfromEntitiesline is added or removed, so the baseline ICMP andhost-admitare untouched.database/cloudnative-pg-f4c8b5d9-7ftrphas name=cloudnative-pg.monitoring/prometheus-kube-prometheus-stack-prometheus-0has name=prometheus.blackbox-exporter-…-rcdbmhas name=prometheus-blackbox-exporter.app.kubernetes.io/name=paperlessmatches onlypaperless-6fd548d865-r84jg, which serves :8000.cnpg.io/cluster=pprl-pgmatchespprl-pg-2andpprl-pg-3, which serve 5432/9187/8000.wp-pg-allow-cnpg-operator. The woodpecker baseline has nodatabaseadmit.databasepods left without a rule: barman-cloud and the three mariadb-operator pods. The archiver runs as a sidecar in the instance pod.data:at line 64. The sha256 ofdata:onward is identical on master and on the branch (598018bc…).probes-ren28-negative.yamland thekustomization.yamlline 79 entry are both present.CNPNegativeControlReachableis in its own group and matchesjob="blackbox-negative-control"only.git revert -m 1restores both admits, and Flux prunes the three CNPs.I did not run the live Prometheus readings: I have no query access from this runner. The after-merge readings are Atlas's, on REN-159.
🤖 Automated review — model
CLI default(complexity: complex), commit997ffbbf9bce116982cdb18edb79f147208113a7.Summary
This PR (REN-28 PR 2b) replaces two whole-namespace ingress admits (
database,monitoring) onpaperless'sdefault-deny-ingress— which, underendpointSelector: {}, let every pod in those namespaces reach all nine paperless workloads on every port, including the HTTP listener that trustsX-Authentik-Usernamefrom anything that connects — with three narrowly-scoped per-workloadCiliumNetworkPolicyobjects, plus an inverted Prometheus alert (CNPNegativeControlReachable) that pages if the grant is ever re-widened. The rest of the diff is comments/documentation updates and Probe/PrometheusRule bookkeeping carried over from PR 2a.I checked the new CNPs, PrometheusRule, and Probe/kustomization wiring against the full checkout: anchors and
path.yaml#anchorcitations added in this diff all resolve to real anchors (I traced each one by hand, including ones in files this diff didn't touch:paperless.yaml#db-host,cluster.yaml#pprl-pg,paperless.yamlcontainerPort 8000,probes-blackbox.yaml#paperless-app-health); the new alert'sjob=and the Probe'sjobName:match; the negative-control job name (blackbox-negative-control) is correctly distinct from the temporaryblackbox-ren28-negativejob so the two PrometheusRule exclusion lists don't collide; the gatusdata:key is indeed at line 64 with all diff hunks above it, confirming the byte-identity claim; and thepprl-pg-allow-cnpg-operatorshape is a verified copy of the already-merged, in-productionwp-pg-allow-cnpg-operator(infrastructure/network-policies/woodpecker-ns.yaml:96-110).database-ns.yamlandmonitoring-ns.yamlare both ingress-only CNPs (no egress section), so the source-side egress this change depends on is already unrestricted, as the PR assumes. No blocking issues found.🟡 Suggestions
infrastructure/network-policies/paperless-ns.yaml:267-270,352-356— the newpprl-pg-allow-prometheusandpaperless-allow-blackboxselectors key offapp.kubernetes.io/name: prometheus/prometheus-blackbox-exporteron the source pods, and the comment at:253-256asserts pprl-pg pods carryapp.kubernetes.io/name: postgresql(notpaperless, so the blackbox rule can't accidentally reach them). None of these three label claims are independently checkable from this checkout — the Prometheus and blackbox-exporter Deployments/labels come from third-party Helm chart defaults, and CNPG doesn't setapp.kubernetes.io/namein this repo'scluster.yaml. That itself isn't unusual, butapps/paperclip/cluster-view-rbac.yaml:24-29documents that this exact class of claim — a CNP selector ofapp.kubernetes.io/name: prometheus-blackbox-exporter— was previously not verifiable via Prometheus's own metric labels (kube-state-metrics doesn't exportkube_pod_labelshere, and blackbox-exporter isn't itself a scrape target), and shipped unverified for the navidrome policy. If any of these three labels is wrong, the affected CNP silently matches zero pods and grants nothing — Cilium reports no error — which would quietly break the paperless-app-health probe or the CNPG Prometheus scrape rather than fail loudly. The PR's own post-merge checks (probe_successstaying1,cnpg-pprl-pgPodMonitor stayingup) are the right catch for this, but it's worth explicitly running a pod-label check once, either before merge or immediately after, to close this gap rather than relying solely on the indirect metrics signal.infrastructure/network-policies/paperless-ns.yaml:38-41— the inline summary table's source-side shorthand (monitoring/blackbox) doesn't literally match the selector label actually used (app.kubernetes.io/name: prometheus-blackbox-exporter). Not misleading given the full rule is right below, but a future skim-reader could conflate it with a differentblackboxlabel if one ever exists.Tests
No executable tests exist for Cilium network policy YAML in this repo (consistent with the rest of the codebase — policy correctness is validated by the Probe/PrometheusRule negative-control pattern instead, which this PR extends correctly:
blackbox-negative-controltargets flip from1to0post-merge, andCNPNegativeControlReachablepages if they ever flip back). Theyaml-citations.shguard (script read, not re-run in this sandbox) enforces the citation format used throughout this diff; I manually traced every new/changed citation and anchor and they all resolve.promtoolisn't available in this environment either, so the newCNPNegativeControlReachablerule's PromQL wasn't independently syntax-checked beyond visual comparison against the adjacent, already-live rules in the same file (which it matches structurally). The PR body is explicit about both gaps (coverage is 2-of-4 on its own §7 bar;promtoolnot run locally, CI runs it) and about the pods/labels needed to reach 4-of-4 not being creatable from its own runner — an honest disclosure rather than an oversight.Recommendation
Looks reasonable — no blocking issues; comments above are optional.