Files
helm-charts/satisfactory/templates/satisfactory.networkpolicy.yaml
T
jklein 1e28d1a992 feat(satisfactory): networkPolicy.ingress.ambientMesh
Gitea #143. Additive, default false, so the render is byte-identical to the
previous release - proven by diffing both renders against origin/main rather
than asserted.

Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying
the probe port, and - inside that rule's source-restricted branch -
169.254.7.127/32, the address the kubelet's probes arrive from once the pod is
in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the
port a pod whose namespace has joined the mesh is unreachable.

The two halves are one value because the second is the one nobody thinks of:
15008 added by hand leaves a policy that passes review and drops every probe
the first time probeSources is narrowed, and a pod that never becomes Ready
reads as a broken application rather than as a policy.

Minor, not major: additive value, unchanged default, unchanged render.

Port half only: this chart offers no networkPolicy.probeSources - its probes
put no packet on the network - so there is no redirected probe to allow and an
ipBlock for one would match nothing while making the rule look like it did
something. ci/run-networkpolicy-test.sh ties that exemption to the absence of
probeSources, not to a chart name.
2026-10-19 17:56:58 +00:00

137 lines
7.1 KiB
YAML

{{- /*
NetworkPolicy for satisfactory (Gitea #5).
INGRESS ONLY, AND THE MISSING EGRESS SECTION IS THE DECISION IN THIS FILE.
Two reasons, and they compound.
First, what this server needs outbound is Steam's content delivery, and that is
not a port list anyone can pin down: the dedicated server downloads and UPDATES
the game through SteamCMD over TCP 80 and 443 plus the 27015-27050 range, and
talks to the Steam master servers over UDP 27000-27100 and 4380. Valve changes
that set, the server re-downloads on every update, and a cap that is wrong
leaves a server that cannot start - or worse, one that starts and never appears
in the server browser.
Second, AND THIS IS WHY THE DECISION GOES THIS WAY RATHER THAN THE OTHER:
satisfactory is the ONE chart in this repository with no install-test at all.
It is in ci/excluded-charts.txt, because the image is multiple GB and the first
start pulls over 10GB of game files - impractical on a runner. So an egress cap
here would be the only policy in the sweep with no empirical check whatsoever
behind it, on the chart whose correct port set is least knowable. Review is the
only net this chart has, and a review cannot verify a Steam port range.
So valkey's rule applies: policyTypes names only what there is something to
restrict. A deployment that wants an egress cap should write one at release
level, where the Steam behaviour can actually be observed.
What the Ingress half still buys is real: only the three declared game ports
are reachable, so anything else the image binds is not.
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 }}
{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same
thing as the top-level .Values.ingress (the Ingress object) - both match
a search for "ingress". */}}
{{- $npIngress := .Values.networkPolicy.ingress | default dict }}
{{- $ambientMesh := $npIngress.ambientMesh }}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-networkpolicy
spec:
{{- /*
Selects the StatefulSet'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 }}-sfs
policyTypes:
- Ingress
ingress:
{{- /*
THE GAME PORTS, all three published as NodePorts and all deliberately open: the
clients are players on the internet and, after NodePort masquerading, the source
address is a cluster node anyway.
The game port appears TWICE, once for each protocol, because the server binds
it as both - and a policy that named only TCP would be accepted, would look
complete, and would drop the UDP half of the game traffic. The TCP side is
additionally what the readiness probe opens a socket against.
The numbers go through satisfactory.formatNumber for the same reason they do in
the Service and the StatefulSet: a port read from values as a float64 renders
in exponential notation above a certain size, and a NetworkPolicy with "3.0777e
+04" in it is not valid.
*/}}
- ports:
- protocol: TCP
port: {{ include "satisfactory.formatNumber" .Values.server.gamePort }}
- protocol: UDP
port: {{ include "satisfactory.formatNumber" .Values.server.gamePort }}
- protocol: TCP
port: {{ include "satisfactory.formatNumber" .Values.server.messagingPort }}
{{- if $ambientMesh }}
{{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's
HBONE tunnel, the port every inbound connection arrives on once the
namespace carries istio.io/dataplane-mode - ambient "does not and will
never bypass Kubernetes NetworkPolicy enforcement", so without it a pod
in the mesh is unreachable.
It is the one port here that is NOT a containerPort of the selected pod
spec: ztunnel terminates it, not the application. ci/run-networkpolicy-
test.sh holds a NAMED exception for exactly this number, gated on exactly
this value, and proves on a second render that it stays the only such
port and that it is absent while the value is false.
THE PORT HALF ONLY, and that is not an omission. Elsewhere this switch
also adds 169.254.7.127/32 as a probe source, because inside the mesh the
kubelet's probes are redirected away from the worker node's address.
This chart offers no networkPolicy.probeSources at all, so the rule
carrying the probe port is never restricted by source - and an
unrestricted rule needs no probe address. The chart's networkPolicy
block has nothing but enabled. Measured in the render: two exec probes
and one tcpSocket.
WHICH RULE IT JOINS:
the only rule this policy has, the one carrying the game and messaging
ports.
NOT MEASURED: that 15008 alone suffices comes from Istio's
documentation. Measured on this cluster is only which listeners ztunnel
opens. Provable at the first namespace to join the mesh; none has. */}}
- protocol: TCP
port: 15008
{{- end }}
{{- end }}