feat(outline)!: NetworkPolicy, on by default

Major, as every chart in this rollout is.

Inbound 3000 only; outbound DNS plus 5432, 6379 and 443. Those three egress
ports are the STANDARD ones and not the configured ones, which is this chart's
real limitation and is written into values.yaml, the template header and
NOTES.txt rather than left to be discovered: Outline's database, Redis and OIDC
provider all arrive as connection URLs inside 1Password items that the chart
never parses, so it cannot know where they listen and cannot validate a guess.
A dependency on another port is blocked, and because all three probes hit
/_health - which checks Postgres and Redis - the symptom is a pod that never
becomes Ready rather than an error anyone reads. networkPolicy.egress.extraPorts
is the escape hatch.

That same property is what makes this chart the right place for the enforcement
experiment in the next commit.

Also reverts the cyberchef fixture from the first experiment: with the probe
port restricted to an address that is not the node's, the install-test went
GREEN, so the claim that it catches a blocked probe path does not hold as
written. The next commit establishes whether policies are enforced in CI at
all.

Part of #5
This commit is contained in:
jklein committed 2026-10-14 14:07:06 +00:00
1 parent d3ef3729f8
commit 77b978d669
6 files changed
+385 -25

No files matched your search

+54
View File
@@ -235,3 +235,57 @@ FORCE_HTTPS IS ON
header. Turned on, Outline redirects to https itself — which loops
endlessly if that header does not reach the pod.
{{- end }}
{{- if .Values.networkPolicy.enabled }}
NETWORKPOLICY IS ACTIVE — WHAT IT CAPS, AND WHAT IT DOES NOT
{{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy selects this workload's pods.
Inbound: TCP 3000 only. Outbound: DNS, TCP 5432 (Postgres), 6379 (Redis),
443 (OIDC discovery){{ with .Values.networkPolicy.egress }}{{ with .extraPorts }}, plus TCP {{ join ", " . }}{{ end }}{{ end }}.
THOSE EGRESS PORTS ARE THE STANDARD ONES, NOT YOUR CONFIGURED ONES. The
database, Redis and OIDC URLs all live inside 1Password items that this chart
never parses, so it cannot know where your dependencies listen. A dependency
on another port is blocked — and since all three probes hit /_health, which
checks Postgres and Redis, the symptom is a pod that never becomes Ready
rather than a connection error anyone sees. Add the real port to
networkPolicy.egress.extraPorts.
Three limits belong in the same breath, because none of them is visible in
the object itself:
- EGRESS IS BY PORT, NEVER BY DESTINATION. A NetworkPolicy matches CIDRs and
selectors and cannot express a hostname, so every egress rule permits its
port to ANY address. That is a limit of the object, not a shortcut — this
cluster's CNI (Canal: Flannel plus Calico) has no FQDN rule either. An
outbound port the chart does not know about is BLOCKED: add it to
networkPolicy.egress.extraPorts, and expect to find out through an
application-level connection error rather than through this object.
{{- if .Values.networkPolicy.probeSources }}
- THE PROBE PATH IS RESTRICTED in this release, because
networkPolicy.probeSources is set to
{{ join ", " .Values.networkPolicy.probeSources }}
Probes come from the IP of the WORKER NODE running the pod. If none of those
CIDRs covers the node this pod lands on, the kubelet's probes are dropped
and THE POD NEVER BECOMES READY — the rollout then times out and the symptom
looks like a broken application. Compare
kubectl -n {{ .Release.Namespace }} describe pod -l app={{ .Release.Name }}-{{ .Chart.Name }}-sfs
against `kubectl get node -o wide`.
{{- else }}
- THE PROBED PORT IS OPEN INSIDE THE CLUSTER. networkPolicy.probeSources is
empty, so that rule carries no source selector at all and any pod the CNI
routes to can reach it. Not from the internet — but this is NOT pod-level
isolation. Narrowing it means listing the worker node IPs in
networkPolicy.probeSources, because the kubelet probes from the node's own
address and the chart cannot know it; a wrong guess there leaves the pod
permanently un-Ready.
{{- end }}
- IT IS ONLY A BOUNDARY WHERE THE CNI ENFORCES POLICIES. Where it does not,
this object is accepted and filters nothing, so everything reads as
configured while nothing is filtered. `kubectl -n {{ .Release.Namespace }} get networkpolicy`
shows the object exists; only a connection attempt from an unrelated
namespace shows that it bites.
Switch it off with networkPolicy.enabled: false.
{{- end }}