Files
helm-charts/bentopdf/templates/bentopdf.networkpolicy.yaml
jklein ee9ae8a724
Install-test / Install-test apache-tika (pull_request) Successful in 1m21s
Install-test / Install-test bentopdf (pull_request) Successful in 1m16s
Install-test / Install-test cyberchef (pull_request) Successful in 1m6s
Install-test / Install-test gotenberg (pull_request) Successful in 1m32s
Install-test / Install-test outline (pull_request) Failing after 1m16s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m1s
Install-test / Install-test searxng (pull_request) Successful in 1m13s
Validate Helm Charts / Install-test (pull_request) Failing after 9m57s
Install-test / Install-test ytdlp-web-player (pull_request) Successful in 1m11s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Failing after 18s
Validate Helm Charts / HTTPRoute match (pull_request) Failing after 13s
Validate Helm Charts / Selector match (pull_request) Failing after 17s
Validate Helm Charts / Bump level (pull_request) Successful in 8s
Validate Helm Charts / YAML scalars (pull_request) Failing after 16s
Validate Helm Charts / NetworkPolicy (pull_request) Failing after 9s
Validate Helm Charts / Build apache-tika (pull_request) Successful in 5s
Validate Helm Charts / Trivy config apache-tika (pull_request) Successful in 13s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 5s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 6s
Validate Helm Charts / Build outline (pull_request) Successful in 5s
Validate Helm Charts / Build template-chart (pull_request) Successful in 5s
Validate Helm Charts / Trivy config outline (pull_request) Failing after 12s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 15s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 14s
Validate Helm Charts / Build gotenberg (pull_request) Successful in 5s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 5s
Validate Helm Charts / Build searxng (pull_request) Successful in 5s
Validate Helm Charts / Build ytdlp-web-player (pull_request) Successful in 5s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 14s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 13s
Validate Helm Charts / Trivy config gotenberg (pull_request) Successful in 13s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 14s
Validate Helm Charts / Trivy config ytdlp-web-player (pull_request) Successful in 15s
feat!: NetworkPolicy in six more charts, on by default
bentopdf, apache-tika, gotenberg, rss-bridge, searxng, ytdlp-web-player. One
major each, networkPolicy.enabled defaults to true.

Three of them differ from the simple case in ways worth naming:

GOTENBERG gets DNS-only egress as a deliberate, documented trade. It drives a
headless Chromium, and 3000 is its probe port - so it is reachable from the
whole cluster by default, and an egress rule for 80/443 would turn a service
anyone can POST to into a way of fetching arbitrary URLs. The price is the
remote-asset half of HTML conversion: inline content converts, images the page
would have fetched silently do not. That degrades a PDF instead of failing a
pod, so nothing alerts on it, which is why it is in the template header, in
values.yaml and in NOTES.txt, with extraPorts: [80, 443] named as the knowing
opt-in.

RSS-BRIDGE, SEARXNG and YTDLP-WEB-PLAYER get the open internet on their web
ports, because reaching other people's servers IS the application and this
object cannot name a host. Said plainly in each, so none of them reads
stricter than it is. What the cap still buys is real: no other outbound port,
so a compromised pod reaches no database, no mail server and no SSH anywhere.

APACHE-TIKA and GOTENBERG render no Ingress - their consumers are other pods,
in other namespaces here - so neither gets an ingressController value and both
say in NOTES.txt that narrowing probeSources means listing the consumers'
namespaces in allowedNamespaces in the same change, or extraction and
conversion stop.

YTDLP-WEB-PLAYER names the gap this convention has: its oauth2-proxy subchart
renders its own Deployment with its own labels and ships no policy, so with the
proxy enabled the pod that actually faces the Ingress is unrestricted in both
directions. Nothing in this chart can change that - an upstream chart is
consumed as it comes - so it is written down instead.

ci/outline/test-values.yaml gains egress.extraPorts: [5556]. The CI dex runs
there rather than on 443, and Outline fetches the discovery document at boot -
so this is the chart's documented escape hatch used for exactly the case it was
written for, and the one place in the repo where the cap's upgrade risk is
actually exercised.

Part of #5
2026-10-14 14:20:39 +00:00

158 lines
6.4 KiB
YAML

{{- /*
NetworkPolicy for bentopdf (Gitea #5).
BentoPDF is a browser-side PDF tool: nginx serves the bundle, the documents are
processed in the visitor's browser, and the chart has no secrets and no
volumes. So the pod holds nothing and calls nothing, and the policy is as tight
as this object gets - inbound 8080, outbound DNS.
The ingress direction is not restricted by source in the default, because 8080
is the port all three probes address. See the rule below.
WHAT THIS OBJECT IS: a ceiling on reachability, not an authentication layer.
Only the ports declared below are reachable at all - whatever else the process
binds, now or after the next app update, is blocked without anyone having to
notice it exists.
WHAT IT IS NOT:
- EGRESS IS COARSE. A NetworkPolicy matches CIDRs and selectors and NEVER
hostnames, so "only to the database host" cannot be expressed at all. This
cluster's CNI is Canal (Flannel plus Calico for policy) and has no FQDN
rule; Calico's GlobalNetworkSet could express one but would bind this chart
to Calico, the same argument that kept implementation-specific objects out
of the Gateway-API sweep (#114). Each egress rule therefore permits its
PORT to any destination.
- NOT A BOUNDARY WHERE THE CNI DOES NOT ENFORCE ONE. An unenforced policy is
accepted by the API server and filters nothing, so everything reads as
configured while nothing is filtered.
- THE REASONING IS IN TEMPLATE COMMENTS, not in "#" comments: those are part
of the rendered object, travel into the cluster and appear in every release
diff. ci/run-networkpolicy-test.sh fails on a rendered "#".
THE PORT NUMBERS BELOW ARE POD PORTS, NEVER NODEPORTS. A NetworkPolicy is
evaluated at the pod's network interface, after kube-proxy has already
translated a NodePort to the targetPort, so a NodePort number here would match
nothing while looking strict - and, being the only rule for that port, would
block every inbound connection instead. ci/run-networkpolicy-test.sh asserts
every port here against the containerPorts of the pod spec this policy
selects.
*/ -}}
{{- if .Values.networkPolicy.enabled }}
{{- $np := .Values.networkPolicy }}
{{- $ns := .Release.Namespace }}
{{- $probeSources := $np.probeSources | default list }}
{{- $allowedNs := $np.allowedNamespaces | default list }}
{{- $ic := $np.ingressController | default dict }}
{{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy
spec:
{{- /*
Selects the Deployment's pods. ci/run-networkpolicy-test.sh fails if
this matches no pod spec of the same render - which is the mistake that
would otherwise leave a policy in the namespace filtering nothing, and
its usual cause is a label renamed in the workload template.
*/}}
podSelector:
matchLabels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-deployment
policyTypes:
- Ingress
{{- /*
Egress is listed because the restriction is close to total and therefore worth
having: a static file server needs name resolution and nothing else, so DNS is
the only rule.
*/}}
- Egress
ingress:
{{- /*
8080 - the image's EXPOSE and the port its nginx.conf listens on, the one the
Service targets, and the one all three probes address. The probe path.
*/}}
- ports:
- protocol: TCP
port: 8080
{{- if $probeSources }}
{{- /*
RESTRICTED, because the operator supplied probeSources. The kubelet's probes
come from the IP of the WORKER NODE running the pod, so that address has to be
in the list or the pod never becomes Ready - and a pod that is never Ready
reads as a broken application rather than as a policy.
The ingress controller is added to the same rule, or the Ingress and the
HTTPRoute both go dark the moment this list is filled.
*/}}
from:
- namespaceSelector:
matchLabels:
{{- /*
Set by Kubernetes itself on every namespace (>= 1.21), so this needs no
labelling work on a namespace this chart does not own.
*/}}
kubernetes.io/metadata.name: {{ $ns }}
{{- if and $routed $ic.namespace }}
{{- /*
ONE from-entry with TWO selectors, which is an AND: pods carrying these labels
IN that namespace. Written as two list entries it would be an OR, and would
open the port to every pod in kube-system.
*/}}
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ $ic.namespace | quote }}
{{- with $ic.podLabels }}
podSelector:
matchLabels:
{{- toYaml . | nindent 14 }}
{{- end }}
{{- end }}
{{- range $allowedNs }}
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: {{ . | quote }}
{{- end }}
{{- range $probeSources }}
- ipBlock:
cidr: {{ . | quote }}
{{- end }}
{{- else }}
{{- /*
NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the
cluster, because this port carries no NodePort. The alternative was to guess
the worker node CIDRs, and a wrong guess blocks every kubelet probe: the pod
stays un-Ready, the rollout times out, and the cause looks like the
application. An empty "from: []" would be worse than either - it allows
everything too, but while reading as a restriction. Narrow it with
networkPolicy.probeSources.
*/}}
{{- end }}
egress:
{{- /*
DNS, unconditionally. There is no value to switch this off, and that is
deliberate: a knob whose only other setting breaks every name lookup in
the pod is a trap and not a feature.
*/}}
- ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
{{- with $np.egress }}
{{- with .extraPorts }}
{{- /*
Ports only, to any destination - see the header. This is the operator's
escape hatch from the egress cap, and the only alternative to switching
the whole policy off.
*/}}
- ports:
{{- range . }}
- protocol: TCP
port: {{ int . }}
{{- end }}
{{- end }}
{{- end }}
{{- end }}