feat!: NetworkPolicy in the NodePort and game-server charts, on by default
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 23s
Validate Helm Charts / Bump level (pull_request) Successful in 4s
Validate Helm Charts / Selector match (pull_request) Successful in 12s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 10s
Validate Helm Charts / YAML scalars (pull_request) Successful in 9s
Validate Helm Charts / NetworkPolicy (pull_request) Failing after 10s
Validate Helm Charts / Trivy config apache-tika (pull_request) Successful in 23s
Validate Helm Charts / Build apache-tika (pull_request) Successful in 14s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 6s
Validate Helm Charts / Build bulwark (pull_request) Successful in 6s
Validate Helm Charts / Build calibre-web-automated (pull_request) Successful in 5s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 5s
Validate Helm Charts / Build homeassistant (pull_request) Successful in 10s
Validate Helm Charts / Build garage (pull_request) Successful in 6s
Validate Helm Charts / Build gotenberg (pull_request) Successful in 11s
Validate Helm Charts / Build keycloak (pull_request) Successful in 5s
Validate Helm Charts / Build minecraft-router (pull_request) Successful in 8s
Validate Helm Charts / Build patchmon (pull_request) Successful in 8s
Validate Helm Charts / Build rallly (pull_request) Successful in 5s
Validate Helm Charts / Build satisfactory (pull_request) Successful in 5s
Validate Helm Charts / Build minecraft-server (pull_request) Successful in 7s
Validate Helm Charts / Build outline (pull_request) Successful in 5s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 5s
Validate Helm Charts / Build samba (pull_request) Successful in 5s
Validate Helm Charts / Build searxng (pull_request) Successful in 5s
Validate Helm Charts / Build sure (pull_request) Successful in 11s
Validate Helm Charts / Build teamspeak-server (pull_request) Successful in 18s
Validate Helm Charts / Build template-chart (pull_request) Successful in 12s
Validate Helm Charts / Build urlaubsverwaltung (pull_request) Successful in 4s
Validate Helm Charts / Build ytdlp-web-player (pull_request) Successful in 5s
Validate Helm Charts / Build voucher-vault (pull_request) Successful in 5s
Validate Helm Charts / Build zipline (pull_request) Successful in 4s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 13s
Validate Helm Charts / Trivy config bulwark (pull_request) Successful in 15s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 12s
Validate Helm Charts / Trivy config minecraft-router (pull_request) Successful in 17s
Validate Helm Charts / Trivy config calibre-web-automated (pull_request) Successful in 11s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 13s
Validate Helm Charts / Trivy config garage (pull_request) Successful in 20s
Validate Helm Charts / Trivy config gotenberg (pull_request) Successful in 14s
Validate Helm Charts / Trivy config homeassistant (pull_request) Successful in 11s
Validate Helm Charts / Trivy config outline (pull_request) Successful in 14s
Validate Helm Charts / Trivy config rallly (pull_request) Successful in 15s
Validate Helm Charts / Trivy config minecraft-server (pull_request) Successful in 27s
Validate Helm Charts / Trivy config patchmon (pull_request) Successful in 12s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 13s
Validate Helm Charts / Trivy config teamspeak-server (pull_request) Successful in 11s
Validate Helm Charts / Trivy config samba (pull_request) Successful in 16s
Validate Helm Charts / Trivy config satisfactory (pull_request) Successful in 22s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 22s
Validate Helm Charts / Trivy config sure (pull_request) Successful in 17s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 14s
Validate Helm Charts / Trivy config urlaubsverwaltung (pull_request) Successful in 11s
Validate Helm Charts / Trivy config voucher-vault (pull_request) Successful in 12s
Validate Helm Charts / Trivy config ytdlp-web-player (pull_request) Successful in 17s
Validate Helm Charts / Trivy config zipline (pull_request) Successful in 18s
Install-test / Install-test bulwark (pull_request) Successful in 1m18s
Install-test / Install-test homeassistant (pull_request) Successful in 2m3s
Install-test / Install-test outline (pull_request) Successful in 2m9s
Install-test / Install-test rss-bridge (pull_request) Successful in 51s
Install-test / Install-test samba (pull_request) Successful in 1m14s
Install-test / Install-test apache-tika (pull_request) Successful in 1m7s
Install-test / Install-test bentopdf (pull_request) Successful in 1m5s
Install-test / Install-test calibre-web-automated (pull_request) Successful in 1m47s
Install-test / Install-test cyberchef (pull_request) Successful in 49s
Install-test / Install-test garage (pull_request) Successful in 1m10s
Install-test / Install-test gotenberg (pull_request) Successful in 1m35s
Install-test / Install-test keycloak (pull_request) Successful in 2m23s
Install-test / Install-test minecraft-router (pull_request) Successful in 37s
Install-test / Install-test minecraft-server (pull_request) Successful in 1m34s
Install-test / Install-test patchmon (pull_request) Successful in 1m17s
Install-test / Install-test rallly (pull_request) Successful in 1m30s
Install-test / Install-test searxng (pull_request) Successful in 1m9s
Install-test / Install-test sure (pull_request) Successful in 2m10s
Install-test / Install-test voucher-vault (pull_request) Successful in 1m48s
Install-test / Install-test ytdlp-web-player (pull_request) Successful in 59s
Install-test / Install-test zipline (pull_request) Successful in 1m59s
Validate Helm Charts / Install-test (pull_request) Successful in 33m44s
Install-test / Install-test teamspeak-server (pull_request) Successful in 1m15s
Install-test / Install-test urlaubsverwaltung (pull_request) Successful in 1m51s
Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 23s
Validate Helm Charts / Bump level (pull_request) Successful in 4s
Validate Helm Charts / Selector match (pull_request) Successful in 12s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 10s
Validate Helm Charts / YAML scalars (pull_request) Successful in 9s
Validate Helm Charts / NetworkPolicy (pull_request) Failing after 10s
Validate Helm Charts / Trivy config apache-tika (pull_request) Successful in 23s
Validate Helm Charts / Build apache-tika (pull_request) Successful in 14s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 6s
Validate Helm Charts / Build bulwark (pull_request) Successful in 6s
Validate Helm Charts / Build calibre-web-automated (pull_request) Successful in 5s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 5s
Validate Helm Charts / Build homeassistant (pull_request) Successful in 10s
Validate Helm Charts / Build garage (pull_request) Successful in 6s
Validate Helm Charts / Build gotenberg (pull_request) Successful in 11s
Validate Helm Charts / Build keycloak (pull_request) Successful in 5s
Validate Helm Charts / Build minecraft-router (pull_request) Successful in 8s
Validate Helm Charts / Build patchmon (pull_request) Successful in 8s
Validate Helm Charts / Build rallly (pull_request) Successful in 5s
Validate Helm Charts / Build satisfactory (pull_request) Successful in 5s
Validate Helm Charts / Build minecraft-server (pull_request) Successful in 7s
Validate Helm Charts / Build outline (pull_request) Successful in 5s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 5s
Validate Helm Charts / Build samba (pull_request) Successful in 5s
Validate Helm Charts / Build searxng (pull_request) Successful in 5s
Validate Helm Charts / Build sure (pull_request) Successful in 11s
Validate Helm Charts / Build teamspeak-server (pull_request) Successful in 18s
Validate Helm Charts / Build template-chart (pull_request) Successful in 12s
Validate Helm Charts / Build urlaubsverwaltung (pull_request) Successful in 4s
Validate Helm Charts / Build ytdlp-web-player (pull_request) Successful in 5s
Validate Helm Charts / Build voucher-vault (pull_request) Successful in 5s
Validate Helm Charts / Build zipline (pull_request) Successful in 4s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 13s
Validate Helm Charts / Trivy config bulwark (pull_request) Successful in 15s
Validate Helm Charts / Trivy config keycloak (pull_request) Successful in 12s
Validate Helm Charts / Trivy config minecraft-router (pull_request) Successful in 17s
Validate Helm Charts / Trivy config calibre-web-automated (pull_request) Successful in 11s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 13s
Validate Helm Charts / Trivy config garage (pull_request) Successful in 20s
Validate Helm Charts / Trivy config gotenberg (pull_request) Successful in 14s
Validate Helm Charts / Trivy config homeassistant (pull_request) Successful in 11s
Validate Helm Charts / Trivy config outline (pull_request) Successful in 14s
Validate Helm Charts / Trivy config rallly (pull_request) Successful in 15s
Validate Helm Charts / Trivy config minecraft-server (pull_request) Successful in 27s
Validate Helm Charts / Trivy config patchmon (pull_request) Successful in 12s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 13s
Validate Helm Charts / Trivy config teamspeak-server (pull_request) Successful in 11s
Validate Helm Charts / Trivy config samba (pull_request) Successful in 16s
Validate Helm Charts / Trivy config satisfactory (pull_request) Successful in 22s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 22s
Validate Helm Charts / Trivy config sure (pull_request) Successful in 17s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 14s
Validate Helm Charts / Trivy config urlaubsverwaltung (pull_request) Successful in 11s
Validate Helm Charts / Trivy config voucher-vault (pull_request) Successful in 12s
Validate Helm Charts / Trivy config ytdlp-web-player (pull_request) Successful in 17s
Validate Helm Charts / Trivy config zipline (pull_request) Successful in 18s
Install-test / Install-test bulwark (pull_request) Successful in 1m18s
Install-test / Install-test homeassistant (pull_request) Successful in 2m3s
Install-test / Install-test outline (pull_request) Successful in 2m9s
Install-test / Install-test rss-bridge (pull_request) Successful in 51s
Install-test / Install-test samba (pull_request) Successful in 1m14s
Install-test / Install-test apache-tika (pull_request) Successful in 1m7s
Install-test / Install-test bentopdf (pull_request) Successful in 1m5s
Install-test / Install-test calibre-web-automated (pull_request) Successful in 1m47s
Install-test / Install-test cyberchef (pull_request) Successful in 49s
Install-test / Install-test garage (pull_request) Successful in 1m10s
Install-test / Install-test gotenberg (pull_request) Successful in 1m35s
Install-test / Install-test keycloak (pull_request) Successful in 2m23s
Install-test / Install-test minecraft-router (pull_request) Successful in 37s
Install-test / Install-test minecraft-server (pull_request) Successful in 1m34s
Install-test / Install-test patchmon (pull_request) Successful in 1m17s
Install-test / Install-test rallly (pull_request) Successful in 1m30s
Install-test / Install-test searxng (pull_request) Successful in 1m9s
Install-test / Install-test sure (pull_request) Successful in 2m10s
Install-test / Install-test voucher-vault (pull_request) Successful in 1m48s
Install-test / Install-test ytdlp-web-player (pull_request) Successful in 59s
Install-test / Install-test zipline (pull_request) Successful in 1m59s
Validate Helm Charts / Install-test (pull_request) Successful in 33m44s
Install-test / Install-test teamspeak-server (pull_request) Successful in 1m15s
Install-test / Install-test urlaubsverwaltung (pull_request) Successful in 1m51s
garage, minecraft-router, minecraft-server, samba, teamspeak-server,
satisfactory. One major each. These are the charts the plan said to leave for
last, and they earned it - three of them changed the convention rather than
just consuming it.
GARAGE is the clearest illustration after keycloak. Its four ports split three
ways: s3 and web are RESTRICTED by source (no probe touches them), admin is the
probe path and stays open inside the cluster - which is uncomfortable, because
admin is also the admin API, and that is said out loud instead of glossed - and
rpc gets the narrowest rule the object can express, a podSelector with no
namespaceSelector. All four numbers come from garage.settings.
MINECRAFT-SERVER is the tightest policy in the repository, and the reason is
worth knowing: all three of its probes are EXEC probes, which put no packet on
the network, so no port has to stay open for the kubelet and no probeSources
value is needed at all. RCON and metrics are restricted outright. The game port
has two branches rendered from the same value the Service reads -
server.service.type NodePort means players arrive from the internet and the
rule must be open; anything else means the only path is in-cluster, so the rule
is restricted and the router's namespace has to be in allowedNamespaces.
MINECRAFT-ROUTER derives its egress from the routing table instead of guessing:
server.backendPort plus the "port" override of every mapping that sets one. Add
a backend on a non-standard port and the policy follows. What it cannot express
is the destination - mappings name hosts, and this object matches no hostname -
so the rule is wider than the routing table, and that is written down.
SATISFACTORY gets policyTypes: [Ingress] and no egress section, and the second
half of the reason is the deciding one. Steam's content delivery is not a
pinnable port set (SteamCMD over TCP 80/443/27015-27050, master servers over
UDP 27000-27100 and 4380, changed by Valve, re-downloaded on every update) -
and this is the ONE chart with no install-test at all, because the image is
multiple GB and the first start pulls over 10GB. An egress cap here would be
the only one in the sweep with no empirical check behind it, on the chart whose
correct port set is least knowable.
SAMBA and TEAMSPEAK-SERVER carry the NodePort lesson explicitly: the rules name
445/139 and the pod's voice and file-transfer ports, never the NodePorts, and
teamspeak's file is explicit that its pod port and NodePort happening to share a
number is a coincidence of its values rather than a pattern to copy. Teamspeak's
voice rule is UDP, and the file says why that matters: a TCP rule on the same
number is accepted, looks complete, and drops every voice packet.
THREE THINGS THE CHECKS CAUGHT WHILE THIS BATCH WAS WRITTEN, all of them real:
- minecraft-server's RCON rule named a port the pod does not declare. The
containerPort is gated on server.rcon.enabled and the rule was not, so
assertion 2 rejected it. The rule now carries the same gate.
- samba and satisfactory would have shipped allowedNamespaces and
probeSources values that reach NO rule - every one of their inbound rules
is open to any source. A value that silently does nothing is worse than one
that is absent, because it reads as a control, so both are gone from those
two charts and from their schemas, with the reason in values.yaml.
teamspeak-server loses probeSources for the same reason and keeps
allowedNamespaces, which its web-query rule uses.
- the policy templates no longer declare template variables they do not use.
samba keeps one, satisfactory none.
Part of #5
This commit is contained in:
30 files changed
+1885
-6
No files matched your search
+1
-1
@@ -19,7 +19,7 @@ type: application
|
|||||||
# offer those as "updates", which is why the garage block in renovate.json5
|
# offer those as "updates", which is why the garage block in renovate.json5
|
||||||
# carries a versioningTemplate anchored to ^v<major>.<minor>.<patch>$ - same
|
# carries a versioningTemplate anchored to ^v<major>.<minor>.<patch>$ - same
|
||||||
# shape as the stalwart block, for the same reason.
|
# shape as the stalwart block, for the same reason.
|
||||||
version: 1.1.0+upv2.4.1
|
version: 2.0.0+upv2.4.1
|
||||||
appVersion: "v2.4.1"
|
appVersion: "v2.4.1"
|
||||||
|
|
||||||
maintainers:
|
maintainers:
|
||||||
|
|||||||
@@ -204,3 +204,63 @@ TRAPS WORTH KNOWING BEFORE YOU NEED THEM
|
|||||||
- THE RPC SECRET IS IN THE PROCESS ENVIRONMENT, not in a file. That is
|
- THE RPC SECRET IS IN THE PROCESS ENVIRONMENT, not in a file. That is
|
||||||
forced, not chosen: Garage rejects a secret file with any group bit set,
|
forced, not chosen: Garage rejects a secret file with any group bit set,
|
||||||
and the kubelet sets one. values.yaml has the mechanism.
|
and the kubelet sets one. values.yaml has the mechanism.
|
||||||
|
{{- 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, and the four ports split three ways:
|
||||||
|
- s3 ({{ $settings.ports.s3 | int64 }}) and web ({{ $settings.ports.web | int64 }}): RESTRICTED BY SOURCE — this namespace,
|
||||||
|
networkPolicy.allowedNamespaces, and the ingress controller.
|
||||||
|
- admin ({{ $settings.ports.admin | int64 }}): open inside the cluster, because all three probes address
|
||||||
|
it. This is the ADMIN API, so read that as a smaller ceiling rather than
|
||||||
|
a closed door; networkPolicy.probeSources closes the rest.
|
||||||
|
- rpc ({{ $settings.ports.rpc | int64 }}): this release's own pods only. It has no Service.
|
||||||
|
Outbound: DNS and TCP {{ $settings.ports.rpc | int64 }} (the cluster bus, outbound as well as in){{ with .Values.networkPolicy.egress }}{{ with .extraPorts }},
|
||||||
|
plus TCP {{ join ", " . }}{{ end }}{{ end }}.
|
||||||
|
|
||||||
|
CHECK YOUR S3 CONSUMERS. Anything in another namespace that talks to this
|
||||||
|
endpoint on its in-cluster Service address — a backup job, a Zipline storing
|
||||||
|
uploads, a Stalwart blob store — is blocked until that namespace is in
|
||||||
|
networkPolicy.allowedNamespaces. Consumers using the published Ingress name
|
||||||
|
arrive through the controller and are already covered. The failure is an
|
||||||
|
application-level error with nothing pointing at this object.
|
||||||
|
|
||||||
|
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 }}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
{{- /*
|
||||||
|
NetworkPolicy for garage (Gitea #5).
|
||||||
|
|
||||||
|
Garage declares four ports and they split three ways, which makes this the
|
||||||
|
second-clearest illustration of the convention after keycloak:
|
||||||
|
|
||||||
|
s3 (3900) the S3 API. The Ingress points at it and no probe touches it,
|
||||||
|
so it is RESTRICTED by source.
|
||||||
|
web (3902) static website serving, same treatment as s3.
|
||||||
|
admin (3903) the port all three probes address - tcpSocket twice and a
|
||||||
|
httpGet /health - so it stays open inside the cluster. This is
|
||||||
|
also the admin API, which is the uncomfortable part and is
|
||||||
|
named in values.yaml: narrowing it means probeSources.
|
||||||
|
rpc (3901) the node-to-node bus. It has NO Service at all, deliberately
|
||||||
|
(see garage.service.yaml), and it gets the narrowest rule this
|
||||||
|
object can express: the pods this policy itself selects.
|
||||||
|
|
||||||
|
All four numbers are derived from garage.settings, so moving a port in values
|
||||||
|
moves the policy with it.
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
{{- $settings := fromYaml (include "garage.settings" .) }}
|
||||||
|
{{- $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 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
|
||||||
|
{{- /*
|
||||||
|
Egress is listed and it is narrow, which is the point: a storage node's only
|
||||||
|
outbound traffic is DNS and its own cluster bus. Not permitted, and named in
|
||||||
|
values.yaml because it is the plausible surprise: outbound HTTPS. Garage does
|
||||||
|
not need it for S3 serving, but a deployment that configures remote replication
|
||||||
|
or an external metrics sink does.
|
||||||
|
*/}}
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
{{- /*
|
||||||
|
THE S3 API AND THE STATIC WEB PORT, both genuinely restricted by source,
|
||||||
|
because no probe addresses either of them: this release's own namespace,
|
||||||
|
whatever allowedNamespaces adds, and the ingress controller.
|
||||||
|
|
||||||
|
THE SOURCE LIST IS THE THING TO GET RIGHT HERE. Every consumer of this S3
|
||||||
|
endpoint that lives in another namespace - a backup job, a Zipline using it for
|
||||||
|
uploads, a Stalwart blob store - is a pod in that namespace and is BLOCKED
|
||||||
|
unless the namespace is in allowedNamespaces. Consumers reaching the published
|
||||||
|
Ingress name instead arrive through the controller and are already covered. A
|
||||||
|
blocked S3 consumer fails at the application level, with nothing naming this
|
||||||
|
object.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ $settings.ports.s3 | int64 }}
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ $settings.ports.web | int64 }}
|
||||||
|
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 }}
|
||||||
|
{{- /*
|
||||||
|
THE ADMIN PORT, and it is the probe path: liveness and startup open a TCP
|
||||||
|
socket against it and readiness fetches /health from it. So it stays open
|
||||||
|
inside the cluster by default.
|
||||||
|
|
||||||
|
That is worth stating rather than glossing, because this is the ADMIN API: key
|
||||||
|
management and cluster layout live here. The policy caps it at "reachable from
|
||||||
|
inside the cluster", not at "closed", and probeSources is what closes the rest -
|
||||||
|
at the usual price of needing the worker node IPs.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ $settings.ports.admin | int64 }}
|
||||||
|
{{- 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.
|
||||||
|
|
||||||
|
No ingress-controller entry on this rule: nothing should route to the admin API
|
||||||
|
from outside.
|
||||||
|
*/}}
|
||||||
|
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 }}
|
||||||
|
{{- 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 }}
|
||||||
|
{{- /*
|
||||||
|
THE NODE-TO-NODE RPC BUS, restricted to the pods this policy already selects -
|
||||||
|
a podSelector with no namespaceSelector, which means "these labels, in this
|
||||||
|
namespace".
|
||||||
|
|
||||||
|
This port deliberately has no Service (garage.service.yaml says why: a Service
|
||||||
|
port would advertise a cluster interface that is not one), so this rule is the
|
||||||
|
only description of it anywhere in the chart. The matching egress rule below is
|
||||||
|
what lets a node dial OUT to its peers.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ $settings.ports.rpc | int64 }}
|
||||||
|
from:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
||||||
|
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
|
||||||
|
{{- /*
|
||||||
|
Outbound RPC to the other nodes of this cluster. Needed as well as the inbound
|
||||||
|
rule: policyTypes lists Egress, so a node dialling its peer is subject to this
|
||||||
|
policy too, and with only the inbound rule the bus would be permitted in one
|
||||||
|
direction - a layout that never converges while every node looks healthy.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ $settings.ports.rpc | int64 }}
|
||||||
|
{{- 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 }}
|
||||||
@@ -9,6 +9,57 @@
|
|||||||
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"networkPolicy": {
|
||||||
|
"description": "The chart's NetworkPolicy (Gitea #5), on by default. A ceiling on reachability and not an authentication layer: only the declared container ports are reachable at all, egress is capped by PORT (a NetworkPolicy matches CIDRs and selectors, never hostnames, so a destination host cannot be expressed), and ingress is capped by source for every port no network probe touches. Only a boundary where the cluster's CNI enforces policies - an unenforced policy is accepted by the API server and filters nothing. The template's header carries the full list of what it does not do.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allowedNamespaces": {
|
||||||
|
"description": "Namespaces whose pods may reach this workload, in addition to the release's own namespace, which is always allowed. Matched on the kubernetes.io/metadata.name label Kubernetes sets on every namespace itself, so no labelling work is needed - and no typo protection either: a name that does not exist matches nothing and is silently ineffective. An entry opens the port to EVERY pod in that namespace; for the ingress controller use ingressController instead.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"probeSources": {
|
||||||
|
"description": "Source CIDRs allowed to reach the port the network probes address. EMPTY MEANS OPEN, not blocked: with no entry that rule is rendered without a \"from\" selector. The kubelet's probes come from the IP of the worker node running the pod, an address the chart cannot know and must not guess - a guessed CIDR that excludes the real node blocks every probe and the pod never becomes Ready, which reads as a broken application rather than as a policy. Setting this restricts the rule, which is why the chart adds the own namespace, ingressController and allowedNamespaces to the same rule.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ingressController": {
|
||||||
|
"description": "The ingress controller allowed to reach the routed port once probeSources restricts it; rendered only while ingress.enabled or httpRoute.enabled is true. The two keys become ONE \"from\" entry and are therefore an AND - pods carrying podLabels IN namespace - which is the whole point: the controller's namespace in allowedNamespaces would open the port to every pod in kube-system. The default is measured on this cluster (DaemonSet rke2-traefik in kube-system, which also serves the Gateway API here), not assumed; a policy written against the wrong controller is accepted, looks strict and silently blocks all web traffic.",
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"namespace": {
|
||||||
|
"type": ["string", "null"]
|
||||||
|
},
|
||||||
|
"podLabels": {
|
||||||
|
"description": "Label set the controller's pods carry, rendered verbatim as a podSelector's matchLabels. Kept open because the chart does not interpret the keys.",
|
||||||
|
"type": ["object", "null"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"egress": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"extraPorts": {
|
||||||
|
"description": "Additional outbound TCP ports, to ANY destination - ports and not destinations, because a NetworkPolicy cannot express a hostname. DNS is always allowed and has no switch. Without an entry here an outbound port the chart does not already derive is blocked, which is the cap's whole purpose and also its upgrade risk.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"scaleDown": {
|
"scaleDown": {
|
||||||
"description": "true renders the StatefulSet with replicas: 0 and takes precedence over garage.replicas.",
|
"description": "true renders the StatefulSet with replicas: 0 and takes precedence over garage.replicas.",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
|||||||
@@ -501,3 +501,92 @@ httpRoute:
|
|||||||
# garage.s3.rootDomain - the two still have to be set in step, as they do for
|
# garage.s3.rootDomain - the two still have to be set in step, as they do for
|
||||||
# the Ingress.
|
# the Ingress.
|
||||||
hostnames: []
|
hostnames: []
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# NetworkPolicy (Gitea #5). On by default. The reference implementation with
|
||||||
|
# the full reasoning is template-chart; the convention is in README, "Chart
|
||||||
|
# style guidelines", and the operator's view on this chart's wiki page.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
networkPolicy:
|
||||||
|
# WHAT THIS POLICY ALLOWS, and Garage's four ports split three ways:
|
||||||
|
# - s3 (3900) and web (3902): RESTRICTED BY SOURCE to this namespace,
|
||||||
|
# allowedNamespaces and the ingress controller. No probe touches them,
|
||||||
|
# which is what makes the restriction possible.
|
||||||
|
# - admin (3903): open inside the cluster, because all three probes address
|
||||||
|
# it. This is the ADMIN API - key management and cluster layout - so the
|
||||||
|
# cap is "reachable from inside the cluster", not "closed". probeSources
|
||||||
|
# closes the rest.
|
||||||
|
# - rpc (3901): this release's own pods only. It has no Service at all.
|
||||||
|
# Outbound: DNS and the rpc bus, nothing else.
|
||||||
|
#
|
||||||
|
# THE SOURCE LIST FOR s3 IS THE THING TO GET RIGHT. Every S3 consumer in
|
||||||
|
# ANOTHER namespace - a backup job, a Zipline storing uploads, a Stalwart
|
||||||
|
# blob store - is blocked unless that namespace is in allowedNamespaces.
|
||||||
|
# Consumers that use the published Ingress name arrive through the controller
|
||||||
|
# and are already covered. A blocked consumer fails at the application level,
|
||||||
|
# with nothing naming this object.
|
||||||
|
#
|
||||||
|
# NOT allowed, and the plausible surprise: outbound HTTPS. Garage does not
|
||||||
|
# need it to serve S3, but remote replication or an external metrics sink
|
||||||
|
# does - see egress.extraPorts.
|
||||||
|
enabled: true
|
||||||
|
# Namespaces whose pods may reach this workload, in addition to the release's
|
||||||
|
# own namespace, which is always allowed. Empty means: own namespace only.
|
||||||
|
#
|
||||||
|
# Matched on the "kubernetes.io/metadata.name" label Kubernetes sets on every
|
||||||
|
# namespace itself, so no labelling work is needed on a namespace this chart
|
||||||
|
# does not own - and no typo protection either: a name that does not exist
|
||||||
|
# matches nothing and is silently ineffective. Note the granularity: an entry
|
||||||
|
# opens the port to EVERY pod in that namespace.
|
||||||
|
allowedNamespaces: []
|
||||||
|
# Source CIDRs allowed to reach the admin port (garage.ports.admin, 3903 by default), which is the port all three probes address.
|
||||||
|
#
|
||||||
|
# EMPTY MEANS OPEN, NOT BLOCKED. With no entry that rule is rendered WITHOUT a
|
||||||
|
# "from" selector, so the port is reachable from anywhere the CNI routes to -
|
||||||
|
# inside the cluster, since it carries no NodePort.
|
||||||
|
#
|
||||||
|
# That is the deliberate choice between two bad defaults. The kubelet's probes
|
||||||
|
# originate from the IP of the WORKER NODE running the pod, an address this
|
||||||
|
# chart cannot know and must not guess: a CIDR that excludes the real node
|
||||||
|
# blocks every probe, the pod never becomes Ready, the rollout times out, and
|
||||||
|
# the symptom reads as a broken application rather than as a policy.
|
||||||
|
#
|
||||||
|
# Narrow it by listing the worker node IPs (e.g. ["10.0.1.11/32"]) - and mind
|
||||||
|
# the companion trap: once this list is non-empty the rule IS restricted, so
|
||||||
|
# everything else that needs the port has to be in it. The chart adds the own
|
||||||
|
# namespace, the ingressController below and allowedNamespaces to the same rule for exactly that reason.
|
||||||
|
probeSources: []
|
||||||
|
# The ingress controller allowed to reach the routed port once probeSources
|
||||||
|
# restricts it. Rendered only while ingress.enabled or httpRoute.enabled is true, and only in
|
||||||
|
# the restricted branch - with the default (open) rule the controller reaches
|
||||||
|
# the port anyway. The two keys become ONE "from" entry and are therefore an
|
||||||
|
# AND: pods carrying podLabels IN namespace. That is the point of having them
|
||||||
|
# at all - the controller's namespace in allowedNamespaces would open the port
|
||||||
|
# to every pod in kube-system.
|
||||||
|
#
|
||||||
|
# MEASURED ON THIS CLUSTER, NOT ASSUMED (2026-10-14): ingress-nginx is gone,
|
||||||
|
# the only IngressClass is "traefik" and it is the default, and the controller
|
||||||
|
# is the DaemonSet rke2-traefik in kube-system. The same Traefik serves the
|
||||||
|
# Gateway API here, so this one source covers the Ingress and the HTTPRoute
|
||||||
|
# path alike. A policy written against the wrong controller is accepted, looks
|
||||||
|
# strict, and silently blocks all web traffic - and ingress.className says
|
||||||
|
# nothing about it, because a class names an implementation, not the pods the
|
||||||
|
# traffic comes from.
|
||||||
|
ingressController:
|
||||||
|
namespace: kube-system
|
||||||
|
podLabels:
|
||||||
|
app.kubernetes.io/name: rke2-traefik
|
||||||
|
egress:
|
||||||
|
# Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and
|
||||||
|
# has no switch - a knob whose only other setting breaks every name lookup
|
||||||
|
# in the pod is a trap, not a feature.
|
||||||
|
#
|
||||||
|
# PORTS, NOT DESTINATIONS, and that is a limit of the object rather than
|
||||||
|
# laziness: a NetworkPolicy matches CIDRs and selectors and never hostnames,
|
||||||
|
# so "only to the database host" cannot be written at all. Each entry
|
||||||
|
# permits its port to ANY destination. What the cap still buys is real: a
|
||||||
|
# compromised pod cannot open arbitrary outbound connections.
|
||||||
|
#
|
||||||
|
# 443 is the entry to add for remote replication or an external metrics
|
||||||
|
# sink. Nothing is needed for plain S3 serving.
|
||||||
|
extraPorts: []
|
||||||
@@ -5,7 +5,7 @@ icon: https://avatars.githubusercontent.com/u/988985
|
|||||||
|
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 4.1.4+up1.47.2
|
version: 5.0.0+up1.47.2
|
||||||
appVersion: "1.47.2"
|
appVersion: "1.47.2"
|
||||||
|
|
||||||
maintainers:
|
maintainers:
|
||||||
|
|||||||
@@ -138,3 +138,57 @@ server.env IS APPENDED TO WHAT THE CHART SETS
|
|||||||
in clear text, readable by everyone who can read pods in {{ .Release.Namespace }}; anything
|
in clear text, readable by everyone who can read pods in {{ .Release.Namespace }}; anything
|
||||||
sensitive belongs in valueFrom.secretKeyRef.
|
sensitive belongs in valueFrom.secretKeyRef.
|
||||||
{{- end }}
|
{{- 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: the proxy port (TCP {{ .Values.server.port }}, open — it is published as NodePort
|
||||||
|
{{ .Values.server.outwardPort }} and the senders are players on the internet) and the API port
|
||||||
|
(TCP {{ .Values.server.apiPort }}, the probe path). Outbound: DNS plus the backend ports taken from
|
||||||
|
the routing table itself — TCP {{ $ports := list (int .Values.server.backendPort) }}{{ range .Values.mappings }}{{ if .port }}{{ $ports = append $ports (int .port) }}{{ end }}{{ end }}{{ join ", " ($ports | uniq) }}{{ with .Values.networkPolicy.egress }}{{ with .extraPorts }}, plus TCP {{ join ", " . }}{{ end }}{{ end }}.
|
||||||
|
|
||||||
|
THE BACKEND RULE IS BY PORT, NOT BY DESTINATION. mappings name hosts, and
|
||||||
|
this object cannot match a hostname — so the rule permits those ports to any
|
||||||
|
address, which is wider than your routing table. Adding a backend on a
|
||||||
|
non-standard port needs no change here: the policy reads the same value the
|
||||||
|
router does.
|
||||||
|
|
||||||
|
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 }}-deployment
|
||||||
|
|
||||||
|
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 }}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
{{- /*
|
||||||
|
NetworkPolicy for minecraft-router (Gitea #5).
|
||||||
|
|
||||||
|
The router is the one chart here whose whole job is to forward traffic to OTHER
|
||||||
|
releases, so its egress rule is the interesting one and it is DERIVED from the
|
||||||
|
routing table rather than guessed: server.backendPort, plus the per-mapping
|
||||||
|
"port" override for every entry of mappings that has one. Add a backend on a
|
||||||
|
non-standard port and the policy follows it, because both come from the same
|
||||||
|
value.
|
||||||
|
|
||||||
|
What it does NOT express is the destination. The mappings name hosts
|
||||||
|
(internalUrl), and a NetworkPolicy matches CIDRs and selectors and never
|
||||||
|
hostnames - so the rule permits those PORTS to any address, not those services.
|
||||||
|
A backend in another namespace is reachable through this rule; so is anything
|
||||||
|
else listening on 25565.
|
||||||
|
|
||||||
|
Inbound splits in two: the proxy port is a NodePort and faces the internet, the
|
||||||
|
API port is the probe path.
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
{{- $backendPorts := list (int .Values.server.backendPort) }}
|
||||||
|
{{- range .Values.mappings }}
|
||||||
|
{{- if .port }}{{- $backendPorts = append $backendPorts (int .port) }}{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- $np := .Values.networkPolicy }}
|
||||||
|
{{- $ns := .Release.Namespace }}
|
||||||
|
{{- $probeSources := $np.probeSources | default list }}
|
||||||
|
{{- $allowedNs := $np.allowedNamespaces | default list }}
|
||||||
|
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 router's outbound traffic is exactly known for
|
||||||
|
once - DNS, and the backend ports from its own routing table. That makes this
|
||||||
|
one of the few egress caps in the sweep that is derived rather than guessed.
|
||||||
|
*/}}
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
{{- /*
|
||||||
|
THE PROXY PORT, and deliberately open with no "from" at all: it is published
|
||||||
|
as a NodePort (server.outwardPort) and the senders are Minecraft clients on the
|
||||||
|
internet. After NodePort masquerading the source address is a cluster node
|
||||||
|
anyway, so a source selector here could only ever be a list of node IPs - which
|
||||||
|
would be a restriction in appearance and a maintenance trap in fact.
|
||||||
|
|
||||||
|
NOTE THE NUMBER: this is the POD port (server.port), not server.outwardPort.
|
||||||
|
The NodePort is translated by kube-proxy before the packet reaches the pod, so
|
||||||
|
writing 30565 here would match nothing while looking strict - and being the
|
||||||
|
only rule for this port, it would block every player instead.
|
||||||
|
ci/run-networkpolicy-test.sh asserts exactly this.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.server.port | int64 }}
|
||||||
|
{{- /*
|
||||||
|
THE API PORT - the router's own HTTP interface, and the port all three probes
|
||||||
|
address. So it is the probe path and stays open inside the cluster by default.
|
||||||
|
|
||||||
|
No ingress-controller entry: this chart renders no Ingress, and nothing should
|
||||||
|
route to the router's API from outside.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.server.apiPort | int64 }}
|
||||||
|
{{- 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.
|
||||||
|
*/}}
|
||||||
|
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 }}
|
||||||
|
{{- 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
|
||||||
|
{{- /*
|
||||||
|
THE BACKENDS, by port, derived from the routing table: server.backendPort plus
|
||||||
|
the "port" override of every mapping that sets one. So a backend on a
|
||||||
|
non-standard port is covered automatically, and there is no second list to keep
|
||||||
|
in step with mappings.
|
||||||
|
|
||||||
|
Ports and not destinations: the mappings name hosts (internalUrl) and this
|
||||||
|
object cannot match a hostname, so this rule permits those ports to ANY
|
||||||
|
address. That is wider than the routing table and is the honest description of
|
||||||
|
what the object can do.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
{{- range $backendPorts | uniq }}
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ . }}
|
||||||
|
{{- end }}
|
||||||
|
{{- 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 }}
|
||||||
@@ -9,6 +9,43 @@
|
|||||||
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"networkPolicy": {
|
||||||
|
"description": "The chart's NetworkPolicy (Gitea #5), on by default. A ceiling on reachability and not an authentication layer: only the declared container ports are reachable at all, egress is capped by PORT (a NetworkPolicy matches CIDRs and selectors, never hostnames, so a destination host cannot be expressed), and ingress is capped by source for every port no network probe touches. Only a boundary where the cluster's CNI enforces policies - an unenforced policy is accepted by the API server and filters nothing. The template's header carries the full list of what it does not do.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allowedNamespaces": {
|
||||||
|
"description": "Namespaces whose pods may reach this workload, in addition to the release's own namespace, which is always allowed. Matched on the kubernetes.io/metadata.name label Kubernetes sets on every namespace itself, so no labelling work is needed - and no typo protection either: a name that does not exist matches nothing and is silently ineffective. An entry opens the port to EVERY pod in that namespace; for the ingress controller use ingressController instead.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"probeSources": {
|
||||||
|
"description": "Source CIDRs allowed to reach the port the network probes address. EMPTY MEANS OPEN, not blocked: with no entry that rule is rendered without a \"from\" selector. The kubelet's probes come from the IP of the worker node running the pod, an address the chart cannot know and must not guess - a guessed CIDR that excludes the real node blocks every probe and the pod never becomes Ready, which reads as a broken application rather than as a policy. Setting this restricts the rule, which is why the chart adds the own namespace, ingressController and allowedNamespaces to the same rule.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"egress": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"extraPorts": {
|
||||||
|
"description": "Additional outbound TCP ports, to ANY destination - ports and not destinations, because a NetworkPolicy cannot express a hostname. DNS is always allowed and has no switch. Without an entry here an outbound port the chart does not already derive is blocked, which is the cap's whole purpose and also its upgrade risk.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"scaleDown": {
|
"scaleDown": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -150,3 +150,70 @@ server:
|
|||||||
# name: "some-configmap"
|
# name: "some-configmap"
|
||||||
# key: "someKey"
|
# key: "someKey"
|
||||||
env: []
|
env: []
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# NetworkPolicy (Gitea #5). On by default. The reference implementation with
|
||||||
|
# the full reasoning is template-chart; the convention is in README, "Chart
|
||||||
|
# style guidelines", and the operator's view on this chart's wiki page.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
networkPolicy:
|
||||||
|
# WHAT THIS POLICY ALLOWS: inbound the proxy port (server.port, OPEN - it is
|
||||||
|
# a NodePort and the senders are players on the internet) and the API port
|
||||||
|
# (the probe path, open inside the cluster). Outbound: DNS plus the backend
|
||||||
|
# ports DERIVED from the routing table - server.backendPort and the "port"
|
||||||
|
# override of every mapping that sets one.
|
||||||
|
#
|
||||||
|
# So this is one of the few egress caps here that follows the configuration
|
||||||
|
# instead of guessing at it: add a backend on a non-standard port and the
|
||||||
|
# policy moves with it.
|
||||||
|
#
|
||||||
|
# WHAT IT DOES NOT DO: the rule permits those PORTS to any destination, not
|
||||||
|
# those SERVICES. mappings name hosts (internalUrl), and a NetworkPolicy
|
||||||
|
# matches CIDRs and selectors but never hostnames - so anything else
|
||||||
|
# listening on a backend port is reachable through this rule too.
|
||||||
|
#
|
||||||
|
# And the proxy port carries the POD port, not server.outwardPort: a
|
||||||
|
# NetworkPolicy is evaluated after kube-proxy has translated the NodePort, so
|
||||||
|
# the NodePort number would match nothing while looking strict.
|
||||||
|
enabled: true
|
||||||
|
# Namespaces whose pods may reach this workload, in addition to the release's
|
||||||
|
# own namespace, which is always allowed. Empty means: own namespace only.
|
||||||
|
#
|
||||||
|
# Matched on the "kubernetes.io/metadata.name" label Kubernetes sets on every
|
||||||
|
# namespace itself, so no labelling work is needed on a namespace this chart
|
||||||
|
# does not own - and no typo protection either: a name that does not exist
|
||||||
|
# matches nothing and is silently ineffective. Note the granularity: an entry
|
||||||
|
# opens the port to EVERY pod in that namespace.
|
||||||
|
allowedNamespaces: []
|
||||||
|
# Source CIDRs allowed to reach the API port (server.apiPort), which is the port all three probes address.
|
||||||
|
#
|
||||||
|
# EMPTY MEANS OPEN, NOT BLOCKED. With no entry that rule is rendered WITHOUT a
|
||||||
|
# "from" selector, so the port is reachable from anywhere the CNI routes to -
|
||||||
|
# inside the cluster, since it carries no NodePort.
|
||||||
|
#
|
||||||
|
# That is the deliberate choice between two bad defaults. The kubelet's probes
|
||||||
|
# originate from the IP of the WORKER NODE running the pod, an address this
|
||||||
|
# chart cannot know and must not guess: a CIDR that excludes the real node
|
||||||
|
# blocks every probe, the pod never becomes Ready, the rollout times out, and
|
||||||
|
# the symptom reads as a broken application rather than as a policy.
|
||||||
|
#
|
||||||
|
# Narrow it by listing the worker node IPs (e.g. ["10.0.1.11/32"]) - and mind
|
||||||
|
# the companion trap: once this list is non-empty the rule IS restricted, so
|
||||||
|
# everything else that needs the port has to be in it. The chart adds the own
|
||||||
|
# namespace and allowedNamespaces to the same rule for exactly that reason.
|
||||||
|
probeSources: []
|
||||||
|
egress:
|
||||||
|
# Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and
|
||||||
|
# has no switch - a knob whose only other setting breaks every name lookup
|
||||||
|
# in the pod is a trap, not a feature.
|
||||||
|
#
|
||||||
|
# PORTS, NOT DESTINATIONS, and that is a limit of the object rather than
|
||||||
|
# laziness: a NetworkPolicy matches CIDRs and selectors and never hostnames,
|
||||||
|
# so "only to the database host" cannot be written at all. Each entry
|
||||||
|
# permits its port to ANY destination. What the cap still buys is real: a
|
||||||
|
# compromised pod cannot open arbitrary outbound connections.
|
||||||
|
#
|
||||||
|
# Normally empty: the backend ports already come from mappings. Add a port
|
||||||
|
# only for something outside the routing table - a status API, for
|
||||||
|
# instance.
|
||||||
|
extraPorts: []
|
||||||
@@ -23,7 +23,7 @@ type: application
|
|||||||
# continuously, so a pod restart could pull a different image than the last one
|
# continuously, so a pod restart could pull a different image than the last one
|
||||||
# without anything being recorded anywhere. The dated tags are immutable, so the
|
# without anything being recorded anywhere. The dated tags are immutable, so the
|
||||||
# image is now pinned by the chart version like every other chart in this repo.
|
# image is now pinned by the chart version like every other chart in this repo.
|
||||||
version: 6.0.1+up2026.8.2.java25
|
version: 7.0.0+up2026.8.2.java25
|
||||||
appVersion: "2026.8.2-java25"
|
appVersion: "2026.8.2-java25"
|
||||||
|
|
||||||
maintainers:
|
maintainers:
|
||||||
|
|||||||
@@ -364,3 +364,65 @@ server env IS APPENDED, NOT MERGED
|
|||||||
everyone who can read pods in {{ .Release.Namespace }}; anything sensitive belongs in
|
everyone who can read pods in {{ .Release.Namespace }}; anything sensitive belongs in
|
||||||
valueFrom.secretKeyRef.
|
valueFrom.secretKeyRef.
|
||||||
{{- end }}
|
{{- 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: the game port (TCP {{ .Values.server.port }}){{ if eq .Values.server.service.type "NodePort" }} — OPEN, because the Service is a
|
||||||
|
NodePort and the senders are players on the internet{{ else }} — RESTRICTED to this namespace
|
||||||
|
and networkPolicy.allowedNamespaces, because the Service is {{ .Values.server.service.type }} and the only
|
||||||
|
path to it is in-cluster{{ end }}; RCON (TCP {{ .Values.server.rcon.port }}) and metrics (TCP 19565), both always
|
||||||
|
restricted by source. Outbound: DNS and TCP 443{{ with .Values.networkPolicy.egress }}{{ with .extraPorts }}, plus TCP {{ join ", " . }}{{ end }}{{ end }}.
|
||||||
|
|
||||||
|
THIS POLICY IS UNUSUALLY TIGHT, and the reason is that all three probes are
|
||||||
|
exec probes: they run inside the container and put no packet on the network,
|
||||||
|
so no port has to stay open for the kubelet and networkPolicy.probeSources is
|
||||||
|
not needed at all.
|
||||||
|
{{- if not (eq .Values.server.service.type "NodePort") }}
|
||||||
|
|
||||||
|
SO CHECK ONE THING: players reach this server through minecraft-router, which
|
||||||
|
runs in ITS OWN namespace. That namespace must be in
|
||||||
|
networkPolicy.allowedNamespaces, or the server is unreachable and the symptom
|
||||||
|
is a connection timeout in a game client with a perfectly healthy pod behind
|
||||||
|
it. The same goes for a Prometheus scraping port 19565 from elsewhere.
|
||||||
|
{{- end }}
|
||||||
|
|
||||||
|
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 }}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
{{- /*
|
||||||
|
NetworkPolicy for minecraft-server (Gitea #5).
|
||||||
|
|
||||||
|
THIS IS THE CHART WHERE THE POLICY GETS GENUINELY TIGHT, and the reason is its
|
||||||
|
probes: all three are exec probes. An exec probe runs inside the container
|
||||||
|
through the kubelet's runtime and puts NO packet on the network, so there is no
|
||||||
|
kubelet traffic to accommodate on any port - which means every port here can be
|
||||||
|
restricted by source, with no probeSources needed at all. valkey is tight for
|
||||||
|
the same reason.
|
||||||
|
|
||||||
|
The one rule that cannot be fixed is the game port, and it depends on a value:
|
||||||
|
|
||||||
|
server.service.type == NodePort players arrive from the internet, already
|
||||||
|
masqueraded, so the rule must be OPEN.
|
||||||
|
anything else (the default) the only reachable path is in-cluster, i.e.
|
||||||
|
minecraft-router, so the rule is RESTRICTED
|
||||||
|
to this namespace plus allowedNamespaces -
|
||||||
|
and the router's namespace has to be in that
|
||||||
|
list.
|
||||||
|
|
||||||
|
Both branches are rendered from the same value the Service reads, so they cannot
|
||||||
|
disagree. The RCON and metrics ports are restricted in either case.
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
{{- $allowedNs := $np.allowedNamespaces | default list }}
|
||||||
|
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
|
||||||
|
{{- /*
|
||||||
|
Egress is listed because there is something real to restrict: a Minecraft
|
||||||
|
server needs DNS and HTTPS and nothing else in the normal case. Mod packs that
|
||||||
|
call out on other ports are the exception, and extraPorts is for them.
|
||||||
|
*/}}
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
{{- if eq .Values.server.service.type "NodePort" }}
|
||||||
|
{{- /*
|
||||||
|
THE GAME PORT, NODEPORT BRANCH (server.service.type is NodePort). Open with no
|
||||||
|
"from": the senders are Minecraft clients on the internet and, after NodePort
|
||||||
|
masquerading, the source address is a cluster node anyway.
|
||||||
|
|
||||||
|
THE NUMBER IS THE POD PORT (server.port), not server.service.outwardPort. A
|
||||||
|
NetworkPolicy is evaluated after kube-proxy has translated the NodePort, so the
|
||||||
|
NodePort number would match nothing here while looking strict - and being the
|
||||||
|
only rule for the port, it would block every player instead.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.server.port | int64 }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if not (eq .Values.server.service.type "NodePort") }}
|
||||||
|
{{- /*
|
||||||
|
THE GAME PORT, CLUSTER-INTERNAL BRANCH (the default): the Service is not a
|
||||||
|
NodePort, so nothing outside the cluster can reach this port through this
|
||||||
|
chart, and the rule is restricted to this namespace plus allowedNamespaces.
|
||||||
|
|
||||||
|
PUT THE ROUTER'S NAMESPACE IN allowedNamespaces. In this cluster players reach
|
||||||
|
the server through minecraft-router, which runs in its own namespace - so
|
||||||
|
without that entry the server is unreachable and the symptom is a connection
|
||||||
|
timeout in a game client, with a healthy pod behind it.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.server.port | int64 }}
|
||||||
|
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 }}
|
||||||
|
{{- range $allowedNs }}
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- if .Values.server.rcon.enabled }}
|
||||||
|
{{- /*
|
||||||
|
RCON - remote console, i.e. full administrative control of the server. No
|
||||||
|
Service and no probe, so it is restricted to this namespace plus
|
||||||
|
allowedNamespaces: the rule with the most security value in this chart, and it
|
||||||
|
costs nothing.
|
||||||
|
|
||||||
|
GATED ON server.rcon.enabled, AND THAT GATE IS NOT COSMETIC. The containerPort
|
||||||
|
itself is rendered only when RCON is on (see the StatefulSet), so without the
|
||||||
|
same condition here the policy would name a port the pod does not declare -
|
||||||
|
which is not merely untidy: ci/run-networkpolicy-test.sh rejects it, and it
|
||||||
|
rejected exactly this while the chart was being written. A rule for a port
|
||||||
|
nothing listens on is a rule nobody can verify.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.server.rcon.port | int64 }}
|
||||||
|
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 }}
|
||||||
|
{{- range $allowedNs }}
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: {{ . | quote }}
|
||||||
|
{{- end }}
|
||||||
|
{{- end }}
|
||||||
|
{{- /*
|
||||||
|
The metrics port. No Service, no probe, so restricted like RCON. A Prometheus
|
||||||
|
that scrapes this release from another namespace needs that namespace in
|
||||||
|
allowedNamespaces - and the failure is a target that silently stops reporting,
|
||||||
|
which is worth knowing before it happens.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 19565
|
||||||
|
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 }}
|
||||||
|
{{- range $allowedNs }}
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: {{ . | quote }}
|
||||||
|
{{- 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
|
||||||
|
{{- /*
|
||||||
|
Outbound HTTPS. Needed for Mojang's session server when online-mode is on - a
|
||||||
|
server that cannot reach it rejects every login - and for resource-pack and mod
|
||||||
|
downloads. To any destination, because those are hostnames.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 443
|
||||||
|
{{- 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 }}
|
||||||
@@ -9,6 +9,36 @@
|
|||||||
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"networkPolicy": {
|
||||||
|
"description": "The chart's NetworkPolicy (Gitea #5), on by default. A ceiling on reachability and not an authentication layer: only the declared container ports are reachable at all, egress is capped by PORT (a NetworkPolicy matches CIDRs and selectors, never hostnames, so a destination host cannot be expressed), and ingress is capped by source for every port no network probe touches. Only a boundary where the cluster's CNI enforces policies - an unenforced policy is accepted by the API server and filters nothing. The template's header carries the full list of what it does not do.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allowedNamespaces": {
|
||||||
|
"description": "Namespaces whose pods may reach this workload, in addition to the release's own namespace, which is always allowed. Matched on the kubernetes.io/metadata.name label Kubernetes sets on every namespace itself, so no labelling work is needed - and no typo protection either: a name that does not exist matches nothing and is silently ineffective. An entry opens the port to EVERY pod in that namespace; for the ingress controller use ingressController instead.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"egress": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"extraPorts": {
|
||||||
|
"description": "Additional outbound TCP ports, to ANY destination - ports and not destinations, because a NetworkPolicy cannot express a hostname. DNS is always allowed and has no switch. Without an entry here an outbound port the chart does not already derive is blocked, which is the cap's whole purpose and also its upgrade risk.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"scaleDown": {
|
"scaleDown": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -313,3 +313,56 @@ curseforgeServerpack:
|
|||||||
# Longhorn.
|
# Longhorn.
|
||||||
downloadUrl: null
|
downloadUrl: null
|
||||||
workingDirectory: '/data/serverpack'
|
workingDirectory: '/data/serverpack'
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# NetworkPolicy (Gitea #5). On by default. The reference implementation with
|
||||||
|
# the full reasoning is template-chart; the convention is in README, "Chart
|
||||||
|
# style guidelines", and the operator's view on this chart's wiki page.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
networkPolicy:
|
||||||
|
# THIS IS ONE OF THE TIGHTEST POLICIES IN THE REPOSITORY, and the reason is
|
||||||
|
# the probes: all three are EXEC probes, which run inside the container and
|
||||||
|
# put no packet on the network. So there is no kubelet traffic to
|
||||||
|
# accommodate, every port can be restricted by source, and no probeSources
|
||||||
|
# value is needed at all.
|
||||||
|
#
|
||||||
|
# WHAT IT ALLOWS, inbound:
|
||||||
|
# - the game port (server.port): OPEN when server.service.type is NodePort
|
||||||
|
# (players arrive from the internet, already masqueraded), and
|
||||||
|
# RESTRICTED to this namespace plus allowedNamespaces otherwise - which
|
||||||
|
# is the default. Both branches read the same value the Service does.
|
||||||
|
# - RCON (server.rcon.port) and metrics (19565): always restricted. RCON is
|
||||||
|
# full administrative control of the server, so this is the rule with the
|
||||||
|
# most security value here, and it costs nothing.
|
||||||
|
# Outbound: DNS and TCP 443 (Mojang's session server for online-mode, plus
|
||||||
|
# resource packs and mods).
|
||||||
|
#
|
||||||
|
# PUT THE ROUTER'S NAMESPACE IN allowedNamespaces when the Service is not a
|
||||||
|
# NodePort. In this cluster players reach the server through
|
||||||
|
# minecraft-router, in its own namespace; without that entry the server is
|
||||||
|
# unreachable and the symptom is a timeout in a game client with a healthy
|
||||||
|
# pod behind it. The same applies to a Prometheus scraping 19565.
|
||||||
|
enabled: true
|
||||||
|
# Namespaces whose pods may reach this workload, in addition to the release's
|
||||||
|
# own namespace, which is always allowed. Empty means: own namespace only.
|
||||||
|
#
|
||||||
|
# Matched on the "kubernetes.io/metadata.name" label Kubernetes sets on every
|
||||||
|
# namespace itself, so no labelling work is needed on a namespace this chart
|
||||||
|
# does not own - and no typo protection either: a name that does not exist
|
||||||
|
# matches nothing and is silently ineffective. Note the granularity: an entry
|
||||||
|
# opens the port to EVERY pod in that namespace.
|
||||||
|
allowedNamespaces: []
|
||||||
|
egress:
|
||||||
|
# Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and
|
||||||
|
# has no switch - a knob whose only other setting breaks every name lookup
|
||||||
|
# in the pod is a trap, not a feature.
|
||||||
|
#
|
||||||
|
# PORTS, NOT DESTINATIONS, and that is a limit of the object rather than
|
||||||
|
# laziness: a NetworkPolicy matches CIDRs and selectors and never hostnames,
|
||||||
|
# so "only to the database host" cannot be written at all. Each entry
|
||||||
|
# permits its port to ANY destination. What the cap still buys is real: a
|
||||||
|
# compromised pod cannot open arbitrary outbound connections.
|
||||||
|
#
|
||||||
|
# Add a port for a mod pack that calls out somewhere other than 443 - an
|
||||||
|
# authentication proxy, a map renderer upload, a Discord bridge.
|
||||||
|
extraPorts: []
|
||||||
+1
-1
@@ -5,6 +5,6 @@ icon: https://avatars.githubusercontent.com/u/13281359
|
|||||||
|
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 7.1.1+up4.23.10
|
version: 8.0.0+up4.23.10
|
||||||
|
|
||||||
appVersion: 4.23.10
|
appVersion: 4.23.10
|
||||||
@@ -245,3 +245,59 @@ samba.env IS THE CONTAINER'S WHOLE ENVIRONMENT
|
|||||||
fully rendered by the chart, so image variables that would generate a
|
fully rendered by the chart, so image variables that would generate a
|
||||||
config do not override it.
|
config do not override it.
|
||||||
{{- end }}
|
{{- 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 445 and 139 — the POD ports, open to any source, because they
|
||||||
|
are published as NodePorts ({{ .Values.samba.smbNodePort }} and {{ .Values.samba.netbiosNodePort }}) and the clients are machines
|
||||||
|
on the LAN. Outbound: DNS{{ with .Values.networkPolicy.egress }}{{ with .extraPorts }} plus TCP {{ join ", " . }}{{ end }}{{ end }}.
|
||||||
|
|
||||||
|
THE POLICY NAMES 445 AND 139, NOT THE NODEPORTS. A NetworkPolicy is evaluated
|
||||||
|
at the pod's network interface, after kube-proxy has already translated the
|
||||||
|
NodePort — so the NodePort numbers would match nothing there. This is the
|
||||||
|
easiest mistake to make in this chart, and it would look like a strict policy
|
||||||
|
while blocking every SMB client.
|
||||||
|
|
||||||
|
A DOMAIN-JOINED SAMBA NEEDS MORE: Kerberos (88) and LDAP (389 or 636) in
|
||||||
|
networkPolicy.egress.extraPorts. A standalone one needs nothing.
|
||||||
|
|
||||||
|
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 }}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
{{- /*
|
||||||
|
NetworkPolicy for samba (Gitea #5).
|
||||||
|
|
||||||
|
Samba publishes both of its ports as NodePorts and the clients are machines on
|
||||||
|
the LAN, so both inbound rules are deliberately open: after NodePort
|
||||||
|
masquerading the source address is a cluster node anyway, and a source selector
|
||||||
|
could only ever be a list of node IPs - a restriction in appearance and a
|
||||||
|
maintenance trap in fact. 445 is additionally the port the probes address.
|
||||||
|
|
||||||
|
THE NUMBERS HERE ARE 445 AND 139, NOT samba.smbNodePort AND
|
||||||
|
samba.netbiosNodePort. That distinction is this chart's easiest mistake, which
|
||||||
|
is why it is spelled out: a NetworkPolicy is evaluated at the pod's interface
|
||||||
|
after kube-proxy has translated the NodePort, so writing 30445 would match
|
||||||
|
nothing while looking strict - and, being the only rule for the port, would
|
||||||
|
block every SMB client instead.
|
||||||
|
|
||||||
|
Egress is DNS only, which is as tight as a file server gets. Note what that
|
||||||
|
does NOT cover and why it does not matter: the NetBIOS name-service broadcasts
|
||||||
|
on 137 and 138/UDP are not published by this chart's Service at all, and
|
||||||
|
Windows clients here reach the share by address rather than by browse list.
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
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
|
||||||
|
{{- /*
|
||||||
|
Egress is listed because the restriction is nearly total and therefore worth
|
||||||
|
having: a standalone file server needs name resolution and nothing else. A
|
||||||
|
domain-joined one would need Kerberos and LDAP, which is what extraPorts is
|
||||||
|
for.
|
||||||
|
*/}}
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
{{- /*
|
||||||
|
SMB (445) and NetBIOS session service (139), both POD ports, both published as
|
||||||
|
NodePorts (samba.smbNodePort and samba.netbiosNodePort) and both deliberately
|
||||||
|
open: the clients are machines on the LAN, and after masquerading the source is
|
||||||
|
a cluster node. 445 is also the port all three probes open a TCP socket
|
||||||
|
against, so it is the probe path as well - justified twice over.
|
||||||
|
|
||||||
|
Writing the NodePort numbers here instead would match nothing and block every
|
||||||
|
client. See the header.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 445
|
||||||
|
- protocol: TCP
|
||||||
|
port: 139
|
||||||
|
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 }}
|
||||||
@@ -9,6 +9,29 @@
|
|||||||
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"networkPolicy": {
|
||||||
|
"description": "The chart's NetworkPolicy (Gitea #5), on by default. A ceiling on reachability and not an authentication layer: only the declared container ports are reachable at all, egress is capped by PORT (a NetworkPolicy matches CIDRs and selectors, never hostnames, so a destination host cannot be expressed), and ingress is capped by source for every port no network probe touches. Only a boundary where the cluster's CNI enforces policies - an unenforced policy is accepted by the API server and filters nothing. The template's header carries the full list of what it does not do.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"egress": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"extraPorts": {
|
||||||
|
"description": "Additional outbound TCP ports, to ANY destination - ports and not destinations, because a NetworkPolicy cannot express a hostname. DNS is always allowed and has no switch. Without an entry here an outbound port the chart does not already derive is blocked, which is the cap's whole purpose and also its upgrade risk.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"scaleDown": {
|
"scaleDown": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -96,3 +96,49 @@ samba:
|
|||||||
- SETUID
|
- SETUID
|
||||||
|
|
||||||
storageClass: longhorn
|
storageClass: longhorn
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# NetworkPolicy (Gitea #5). On by default. The reference implementation with
|
||||||
|
# the full reasoning is template-chart; the convention is in README, "Chart
|
||||||
|
# style guidelines", and the operator's view on this chart's wiki page.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
networkPolicy:
|
||||||
|
# THERE IS ONLY ONE VALUE HERE BESIDES enabled, AND THAT IS DELIBERATE.
|
||||||
|
# Both of this chart's inbound rules are open to any source (see below), so
|
||||||
|
# neither allowedNamespaces nor probeSources would reach a rule - and a value
|
||||||
|
# that silently does nothing is worse than a value that is absent: it reads
|
||||||
|
# as a control. If a source restriction is ever wanted here it needs a rule
|
||||||
|
# to attach to first.
|
||||||
|
#
|
||||||
|
# WHAT THIS POLICY ALLOWS: inbound 445 and 139 - the POD ports - open to any
|
||||||
|
# source, and outbound nothing but DNS.
|
||||||
|
#
|
||||||
|
# BOTH INBOUND RULES ARE OPEN ON PURPOSE. The ports are published as
|
||||||
|
# NodePorts and the clients are machines on the LAN; after NodePort
|
||||||
|
# masquerading the source address is a cluster node, so a source selector
|
||||||
|
# could only be a list of node IPs - a restriction in appearance and a
|
||||||
|
# maintenance trap in fact. 445 is also the probe port.
|
||||||
|
#
|
||||||
|
# AND THE NUMBERS ARE 445 AND 139, NOT samba.smbNodePort AND
|
||||||
|
# samba.netbiosNodePort. A NetworkPolicy is evaluated at the pod's interface
|
||||||
|
# after kube-proxy has translated the NodePort, so the NodePort numbers would
|
||||||
|
# match nothing while looking strict - and would block every client.
|
||||||
|
#
|
||||||
|
# Not covered, and it does not matter here: the NetBIOS name-service
|
||||||
|
# broadcasts on 137/138 UDP are not published by this chart's Service at all,
|
||||||
|
# and clients reach the share by address rather than by browse list.
|
||||||
|
enabled: true
|
||||||
|
egress:
|
||||||
|
# Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and
|
||||||
|
# has no switch - a knob whose only other setting breaks every name lookup
|
||||||
|
# in the pod is a trap, not a feature.
|
||||||
|
#
|
||||||
|
# PORTS, NOT DESTINATIONS, and that is a limit of the object rather than
|
||||||
|
# laziness: a NetworkPolicy matches CIDRs and selectors and never hostnames,
|
||||||
|
# so "only to the database host" cannot be written at all. Each entry
|
||||||
|
# permits its port to ANY destination. What the cap still buys is real: a
|
||||||
|
# compromised pod cannot open arbitrary outbound connections.
|
||||||
|
#
|
||||||
|
# A domain-joined Samba needs Kerberos (88) and LDAP (389 or 636) here. A
|
||||||
|
# standalone one needs nothing.
|
||||||
|
extraPorts: []
|
||||||
@@ -3,7 +3,7 @@ name: satisfactory
|
|||||||
description: A Helm chart to deploy a Satisfactory Gameserver on Kubernetes
|
description: A Helm chart to deploy a Satisfactory Gameserver on Kubernetes
|
||||||
icon: https://raw.githubusercontent.com/wolveix/satisfactory-server/main/.github/logo.png
|
icon: https://raw.githubusercontent.com/wolveix/satisfactory-server/main/.github/logo.png
|
||||||
type: application
|
type: application
|
||||||
version: 7.0.2+upv1.9.10
|
version: 8.0.0+upv1.9.10
|
||||||
appVersion: "v1.9.10"
|
appVersion: "v1.9.10"
|
||||||
maintainers:
|
maintainers:
|
||||||
- name: jklein
|
- name: jklein
|
||||||
@@ -323,3 +323,60 @@ env IS APPENDED, NOT MERGED
|
|||||||
stored in the pod spec and readable by everyone who can read pods in
|
stored in the pod spec and readable by everyone who can read pods in
|
||||||
{{ .Release.Namespace }}.
|
{{ .Release.Namespace }}.
|
||||||
{{- end }}
|
{{- 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: the three declared game ports only — TCP and UDP {{ include "satisfactory.formatNumber" .Values.server.gamePort }} and TCP
|
||||||
|
{{ include "satisfactory.formatNumber" .Values.server.messagingPort }} — open to any source, because they are NodePorts and the clients
|
||||||
|
are players on the internet. Anything else the image binds is NOT reachable.
|
||||||
|
|
||||||
|
THERE IS NO EGRESS RESTRICTION AT ALL, deliberately. What this server needs
|
||||||
|
outbound is Steam's content delivery, which is not a pinnable port list
|
||||||
|
(SteamCMD over TCP 80, 443 and 27015-27050; master servers over UDP
|
||||||
|
27000-27100 and 4380; Valve changes it; the server re-downloads on every
|
||||||
|
update). And this is the one chart in the repository with NO install-test —
|
||||||
|
the image is multiple GB and the first start pulls over 10GB of game files —
|
||||||
|
so an egress cap here would be the only one in the sweep with no empirical
|
||||||
|
check behind it. If you want one, write it at release level where Steam's
|
||||||
|
behaviour can be observed.
|
||||||
|
|
||||||
|
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 }}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
{{- /*
|
||||||
|
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 }}
|
||||||
|
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 }}
|
||||||
|
{{- end }}
|
||||||
@@ -9,6 +9,16 @@
|
|||||||
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"networkPolicy": {
|
||||||
|
"description": "The chart's NetworkPolicy (Gitea #5), on by default. A ceiling on reachability and not an authentication layer: only the declared container ports are reachable at all, egress is capped by PORT (a NetworkPolicy matches CIDRs and selectors, never hostnames, so a destination host cannot be expressed), and ingress is capped by source for every port no network probe touches. Only a boundary where the cluster's CNI enforces policies - an unenforced policy is accepted by the API server and filters nothing. The template's header carries the full list of what it does not do.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"scaleDown": {
|
"scaleDown": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -148,3 +148,40 @@ securityContext:
|
|||||||
drop:
|
drop:
|
||||||
- ALL
|
- ALL
|
||||||
add: []
|
add: []
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# NetworkPolicy (Gitea #5). On by default. The reference implementation with
|
||||||
|
# the full reasoning is template-chart; the convention is in README, "Chart
|
||||||
|
# style guidelines", and the operator's view on this chart's wiki page.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
networkPolicy:
|
||||||
|
# enabled IS THE ONLY VALUE HERE, and that is deliberate. The single inbound
|
||||||
|
# rule is open to any source and there is no egress section at all (see
|
||||||
|
# below), so allowedNamespaces and probeSources would both reach no rule -
|
||||||
|
# and a value that silently does nothing is worse than one that is absent,
|
||||||
|
# because it reads as a control.
|
||||||
|
#
|
||||||
|
# WHAT THIS POLICY ALLOWS: inbound the three declared game ports only, open
|
||||||
|
# to any source - they are NodePorts and the clients are players on the
|
||||||
|
# internet. THERE IS NO EGRESS SECTION, and that is the decision in this
|
||||||
|
# chart rather than an omission.
|
||||||
|
#
|
||||||
|
# TWO REASONS, AND THEY COMPOUND. First, the outbound requirement is Steam's
|
||||||
|
# content delivery, which is not a pinnable port list: SteamCMD uses TCP 80,
|
||||||
|
# 443 and 27015-27050, the master servers UDP 27000-27100 and 4380, Valve
|
||||||
|
# changes the set, and the server re-downloads on every update. Second, and
|
||||||
|
# decisively: satisfactory is the ONE chart here with no install-test at all
|
||||||
|
# (ci/excluded-charts.txt - the image is multiple GB and the first start
|
||||||
|
# pulls over 10GB). An egress cap here would be the only one in the sweep
|
||||||
|
# with no empirical check behind it, on the chart whose correct port set is
|
||||||
|
# least knowable, and a review cannot verify a Steam port range.
|
||||||
|
#
|
||||||
|
# So policyTypes names Ingress only, for the same reason valkey does: a rule
|
||||||
|
# that has to be widened until it permits everything makes the object look
|
||||||
|
# stricter than it is. A deployment that wants an egress cap should write one
|
||||||
|
# at release level, where Steam's behaviour can be observed.
|
||||||
|
#
|
||||||
|
# The game port is allowed as BOTH TCP and UDP, because the server binds it
|
||||||
|
# as both - a TCP-only rule would be accepted and would drop half the game
|
||||||
|
# traffic.
|
||||||
|
enabled: true
|
||||||
@@ -5,5 +5,5 @@ icon: https://avatars.githubusercontent.com/u/136759148
|
|||||||
|
|
||||||
type: application
|
type: application
|
||||||
|
|
||||||
version: 5.1.3+up6.0.0.beta13.1
|
version: 6.0.0+up6.0.0.beta13.1
|
||||||
appVersion: "6.0.0-beta13.1"
|
appVersion: "6.0.0-beta13.1"
|
||||||
@@ -239,3 +239,57 @@ env IS APPENDED, NOT MERGED — AND CAN SHADOW THE CHART'S PORTS
|
|||||||
stored in the pod spec and readable by everyone who can read pods in
|
stored in the pod spec and readable by everyone who can read pods in
|
||||||
{{ .Release.Namespace }}.
|
{{ .Release.Namespace }}.
|
||||||
{{- end }}
|
{{- 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: voice (UDP {{ .Values.server.voicePort }}) and file transfer (TCP {{ .Values.server.fileTransferPort }}), both NodePorts and both
|
||||||
|
open — the clients are on the internet; and the web query interface (TCP
|
||||||
|
10080), RESTRICTED to this namespace plus networkPolicy.allowedNamespaces,
|
||||||
|
because it has no Service, no probe, and is administrative. Outbound: DNS and
|
||||||
|
TCP 443{{ with .Values.networkPolicy.egress }}{{ with .extraPorts }}, plus TCP {{ join ", " . }}{{ end }}{{ end }}.
|
||||||
|
|
||||||
|
IF THE SERVER WILL NOT START OR WILL NOT REGISTER AFTER THIS UPGRADE, widen
|
||||||
|
the egress rule: TeamSpeak's accounting service has historically used ports of
|
||||||
|
its own (TCP 2008, UDP 2010 on the 3.x line), and this chart cannot know what
|
||||||
|
the 6.x beta does. Put them in 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 }}
|
||||||
@@ -0,0 +1,161 @@
|
|||||||
|
{{- /*
|
||||||
|
NetworkPolicy for teamspeak-server (Gitea #5).
|
||||||
|
|
||||||
|
Three ports, two of them NodePorts facing the internet and one that is purely
|
||||||
|
internal - so this chart shows the public/internal split without the probe
|
||||||
|
problem getting in the way of either.
|
||||||
|
|
||||||
|
voice (UDP) NodePort, open. The clients are on the internet.
|
||||||
|
fileTransfer (TCP) NodePort, open, and also the port all three probes open
|
||||||
|
a socket against - justified twice.
|
||||||
|
web query (10080) NO Service at all, no probe, so it is RESTRICTED to this
|
||||||
|
namespace plus allowedNamespaces. That is the rule worth
|
||||||
|
having here: the query interface is administrative.
|
||||||
|
|
||||||
|
NOTE THE PORT NUMBERS. This chart happens to use the SAME number for the pod
|
||||||
|
port and the NodePort (server.voicePort, server.fileTransferPort are both), so
|
||||||
|
the usual NodePort mistake is invisible here - the number is right either way.
|
||||||
|
That is a coincidence of this chart's values and not a rule; do not copy the
|
||||||
|
pattern to a chart where the two differ.
|
||||||
|
|
||||||
|
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 }}
|
||||||
|
{{- $allowedNs := $np.allowedNamespaces | default list }}
|
||||||
|
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
|
||||||
|
{{- /*
|
||||||
|
Egress is listed because a voice server's outbound needs are small: DNS and
|
||||||
|
HTTPS. The uncertainty is named in the rule itself rather than hidden - see the
|
||||||
|
note on TeamSpeak's accounting ports.
|
||||||
|
*/}}
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
{{- /*
|
||||||
|
THE VOICE PORT, UDP, published as a NodePort and deliberately open: the clients
|
||||||
|
are on the internet, and after masquerading the source address is a cluster
|
||||||
|
node anyway.
|
||||||
|
|
||||||
|
PROTOCOL MATTERS AS MUCH AS THE NUMBER HERE. Voice is UDP, and a rule written
|
||||||
|
for TCP on the same number would be accepted, would look complete, and would
|
||||||
|
drop every voice packet - which is why ci/run-networkpolicy-test.sh checks the
|
||||||
|
protocol against the containerPort's and not just the number.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: UDP
|
||||||
|
port: {{ .Values.server.voicePort | int64 }}
|
||||||
|
{{- /*
|
||||||
|
THE FILE-TRANSFER PORT, TCP, also a NodePort and also open. It is additionally
|
||||||
|
the port all three probes open a TCP socket against, so it is the probe path as
|
||||||
|
well - open for two independent reasons.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: {{ .Values.server.fileTransferPort | int64 }}
|
||||||
|
{{- /*
|
||||||
|
THE WEB QUERY INTERFACE, and the one rule in this chart with real security
|
||||||
|
value. It has no Service and no probe, so it can be restricted, and it is: this
|
||||||
|
namespace plus allowedNamespaces. The query interface is administrative, and
|
||||||
|
before this object the only thing keeping it off the network was that nothing
|
||||||
|
published it.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 10080
|
||||||
|
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 }}
|
||||||
|
{{- range $allowedNs }}
|
||||||
|
- namespaceSelector:
|
||||||
|
matchLabels:
|
||||||
|
kubernetes.io/metadata.name: {{ . | quote }}
|
||||||
|
{{- 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
|
||||||
|
{{- /*
|
||||||
|
Outbound HTTPS, for the server's own update and accounting calls. To any
|
||||||
|
destination - hostnames cannot appear here.
|
||||||
|
|
||||||
|
IF THE SERVER REFUSES TO START AFTER THIS POLICY ARRIVES, THIS IS THE RULE TO
|
||||||
|
WIDEN: TeamSpeak's accounting service has historically used ports of its own
|
||||||
|
(TCP 2008, UDP 2010 on the 3.x line), and this chart cannot know what the 6.x
|
||||||
|
beta does. The install-test covers this chart, so a startup that depends on a
|
||||||
|
blocked port shows up there rather than in production - but a license or
|
||||||
|
accounting check that merely degrades would not.
|
||||||
|
*/}}
|
||||||
|
- ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 443
|
||||||
|
{{- 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 }}
|
||||||
@@ -9,6 +9,36 @@
|
|||||||
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
"description": "Helm's conventional cross-chart values. Unused by this chart, kept open so an umbrella install cannot break on it.",
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"networkPolicy": {
|
||||||
|
"description": "The chart's NetworkPolicy (Gitea #5), on by default. A ceiling on reachability and not an authentication layer: only the declared container ports are reachable at all, egress is capped by PORT (a NetworkPolicy matches CIDRs and selectors, never hostnames, so a destination host cannot be expressed), and ingress is capped by source for every port no network probe touches. Only a boundary where the cluster's CNI enforces policies - an unenforced policy is accepted by the API server and filters nothing. The template's header carries the full list of what it does not do.",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"enabled": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"allowedNamespaces": {
|
||||||
|
"description": "Namespaces whose pods may reach this workload, in addition to the release's own namespace, which is always allowed. Matched on the kubernetes.io/metadata.name label Kubernetes sets on every namespace itself, so no labelling work is needed - and no typo protection either: a name that does not exist matches nothing and is silently ineffective. An entry opens the port to EVERY pod in that namespace; for the ingress controller use ingressController instead.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"egress": {
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": {
|
||||||
|
"extraPorts": {
|
||||||
|
"description": "Additional outbound TCP ports, to ANY destination - ports and not destinations, because a NetworkPolicy cannot express a hostname. DNS is always allowed and has no switch. Without an entry here an outbound port the chart does not already derive is blocked, which is the cap's whole purpose and also its upgrade risk.",
|
||||||
|
"type": ["array", "null"],
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"scaleDown": {
|
"scaleDown": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -89,3 +89,60 @@ securityContext:
|
|||||||
storage:
|
storage:
|
||||||
size: 25Gi
|
size: 25Gi
|
||||||
storageClass: longhorn
|
storageClass: longhorn
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# NetworkPolicy (Gitea #5). On by default. The reference implementation with
|
||||||
|
# the full reasoning is template-chart; the convention is in README, "Chart
|
||||||
|
# style guidelines", and the operator's view on this chart's wiki page.
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
networkPolicy:
|
||||||
|
# NOTE WHAT IS NOT HERE: probeSources. The port the probes address is the
|
||||||
|
# file-transfer port, and its rule is open for an independent reason - it is
|
||||||
|
# a NodePort facing the internet. So probeSources would reach no rule, and a
|
||||||
|
# value that silently does nothing reads as a control it is not.
|
||||||
|
# allowedNamespaces IS here, because the web-query rule uses it.
|
||||||
|
#
|
||||||
|
# WHAT THIS POLICY ALLOWS, and the three ports differ:
|
||||||
|
# - voice (server.voicePort, UDP): NodePort, OPEN - clients are on the
|
||||||
|
# internet. Note the protocol: a TCP rule on the same number would be
|
||||||
|
# accepted and would drop every voice packet.
|
||||||
|
# - fileTransfer (server.fileTransferPort, TCP): NodePort, open, and also
|
||||||
|
# the probe port.
|
||||||
|
# - web query (10080): RESTRICTED to this namespace plus
|
||||||
|
# allowedNamespaces. No Service, no probe, and administrative - this is
|
||||||
|
# the rule with real value here.
|
||||||
|
# Outbound: DNS and TCP 443.
|
||||||
|
#
|
||||||
|
# IF THE SERVER REFUSES TO START, WIDEN THE EGRESS RULE FIRST. TeamSpeak's
|
||||||
|
# accounting service has historically used ports of its own (TCP 2008, UDP
|
||||||
|
# 2010 on the 3.x line) and this chart cannot know what the 6.x beta does.
|
||||||
|
# The install-test covers this chart, so a startup that depends on a blocked
|
||||||
|
# port shows up there - but a check that merely degrades would not.
|
||||||
|
#
|
||||||
|
# A coincidence worth not copying: this chart uses the same number for the
|
||||||
|
# pod port and the NodePort, so the usual NodePort mistake is invisible here.
|
||||||
|
# That is a property of these values, not a rule.
|
||||||
|
enabled: true
|
||||||
|
# Namespaces whose pods may reach this workload, in addition to the release's
|
||||||
|
# own namespace, which is always allowed. Empty means: own namespace only.
|
||||||
|
#
|
||||||
|
# Matched on the "kubernetes.io/metadata.name" label Kubernetes sets on every
|
||||||
|
# namespace itself, so no labelling work is needed on a namespace this chart
|
||||||
|
# does not own - and no typo protection either: a name that does not exist
|
||||||
|
# matches nothing and is silently ineffective. Note the granularity: an entry
|
||||||
|
# opens the port to EVERY pod in that namespace.
|
||||||
|
allowedNamespaces: []
|
||||||
|
egress:
|
||||||
|
# Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and
|
||||||
|
# has no switch - a knob whose only other setting breaks every name lookup
|
||||||
|
# in the pod is a trap, not a feature.
|
||||||
|
#
|
||||||
|
# PORTS, NOT DESTINATIONS, and that is a limit of the object rather than
|
||||||
|
# laziness: a NetworkPolicy matches CIDRs and selectors and never hostnames,
|
||||||
|
# so "only to the database host" cannot be written at all. Each entry
|
||||||
|
# permits its port to ANY destination. What the cap still buys is real: a
|
||||||
|
# compromised pod cannot open arbitrary outbound connections.
|
||||||
|
#
|
||||||
|
# TCP 2008 and UDP 2010 are the entries to try if the server will not start
|
||||||
|
# or will not register - see the note above.
|
||||||
|
extraPorts: []
|
||||||
Reference in new issue
Block a user