From badfaa32cb52dca199ee0bf48553a7e2f3f5a96d Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:01:03 +0000 Subject: [PATCH 01/63] feat(template-chart): networkPolicy.ingress.ambientMesh, plus its checked CI exception Gitea #143. A pod cannot be reached inside Istio's ambient mesh unless its NetworkPolicy lets ztunnel's HBONE tunnel in - ambient explicitly does not bypass policy enforcement - and 15008 is deliberately no containerPort of the application, so assertion 2 of ci/run-networkpolicy-test.sh rejected it. The switch is a named intention rather than a free port list, and it renders two things that cannot be had separately: 15008/TCP inbound, and - where probeSources is non-empty - 169.254.7.127/32 as an additional peer, because inside the mesh the kubelet's probes are redirected away from the worker node's address. Whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe: the pod never becomes Ready and the symptom reads as a broken application. That is assertion 4 of the same script, one value further along. Assertion 2 keeps its edge. It gets ONE named exception for ONE port number, gated on one value, and it is not granted in the main loop at all - which renders every chart with the value at its default, so 15008 stays an ordinary failure there. The free-port variant was design A of #143 and would have opened the assertion for all 30 charts. Assertion 7 holds the exception to five things on two renders per chart: the port and the ipBlock are absent while the switch is off, 15008 is present while it is on, 15008 is still the ONLY ingress port that is not a containerPort, and wherever the rule carrying it restricts sources it also carries the ipBlock. The last one is reachable because these renders apply ci/networkpolicy/.yaml, where probeSources is non-empty. Built into the existing script, as for #136: the exception weakens assertion 2 and belongs beside it - a separate script could go green while the exception it is paired with is wrong, with nothing connecting the two. The job already runs over every chart on every pull request. Found by running it: the policy's name comes from Chart.yaml's name, not from the directory - template-chart is called "xxx". Not proven, and said so in values.yaml, the template header and the script's header: that 15008 alone suffices comes from Istio's documentation, and the probe-source half reaches no release, because probeSources is empty in every chart here. No namespace of this cluster carries istio.io/dataplane-mode, so there is no path on which either could be measured. What is proven is the render and the assertions. --- ci/run-networkpolicy-test.sh | 411 ++++++++++++++++++ .../templates/xxx.networkpolicy.yaml | 70 +++ template-chart/values.schema.json | 11 + template-chart/values.yaml | 56 +++ 4 files changed, 548 insertions(+) diff --git a/ci/run-networkpolicy-test.sh b/ci/run-networkpolicy-test.sh index 4acfab7..19398fb 100755 --- a/ci/run-networkpolicy-test.sh +++ b/ci/run-networkpolicy-test.sh @@ -83,6 +83,15 @@ # because a green tick for a code path nobody entered is worse than an # honest gap (README, "Two rules for every check added here"). # - PORT RANGES (endPort). Same treatment: reported, never counted. +# - THAT 15008 IS ENOUGH TO REACH A POD IN THE AMBIENT MESH. Assertion 7 +# asserts the RENDER of networkPolicy.ingress.ambientMesh in both +# directions, and nothing beyond it. That the HBONE port alone suffices +# comes from Istio's documentation; measured on this cluster is only which +# listeners ztunnel opens, and no namespace carries istio.io/dataplane-mode +# yet, so there is no path on which it could be measured end to end. The +# probe-source half (169.254.7.127/32) is in the same position: its render is +# asserted, its effect is not, because probeSources is empty in every chart +# of this repository. # # AND THE ANTI-VACUUM GUARDS, because this test could be green over nothing: # @@ -492,6 +501,20 @@ for policy in policies: # port is a port on the DESTINATION and has nothing to do with this pod's # containerPorts, so holding egress against them would fail every chart that # reaches a database. + # + # THE ONE NAMED EXCEPTION, AND IT IS NOT GRANTED HERE (Gitea #143). Port + # 15008 - ztunnel's HBONE tunnel - is terminated by the mesh and not by the + # application, so it is legitimately no containerPort while + # networkPolicy.ingress.ambientMesh is true. This loop renders every chart + # with that value at its DEFAULT, which is false, so for this assertion + # 15008 is an ordinary number and stays an ordinary failure - that is the + # negative half of the exception and it costs nothing. The exception itself + # lives in assertion 7, on a SECOND render with the value on, and it permits + # exactly that one number. It is deliberately not implemented as "skip + # operator-supplied ports when a gate is set": that was design A in #143 and + # would have opened this assertion for all 30 charts, letting every mistyped + # ingress port through - including the NodePort mistake this assertion + # exists for. for i, rule in enumerate(spec.get("ingress") or []): if not isinstance(rule, dict): fail("%s ingress[%d] is not a mapping" % (tag, i)) @@ -870,6 +893,11 @@ EGRESS_PEER_ROWS="paperless-ngx|apache-tika,gotenberg|paperless.files.tikaGotenb # leaves the parsed JSON in $2. The render/split/parse steps are repeated here # instead of refactored out of the loop on purpose: the five assertions above # are load-bearing and were not worth disturbing for this. +# RCJ_WITH_OVERLAY=1 additionally applies ci/networkpolicy/.yaml, last, +# exactly as the main loop does. Off by default so the cross-chart rows below +# keep rendering the consumer the way its release does; assertion 7 switches it +# on, because the overlay is the ONLY way the source-restricted branch - and +# with it the probe-source half of the ambient switch - is reachable at all. render_chart_json() { RCJ_CHART="$1" RCJ_OUT="$2" @@ -883,6 +911,9 @@ render_chart_json() { RCJ_VALUES=(-f "$CI_DIR/trivy/values/$RCJ_CHART.yaml") RCJ_LABEL="ci/trivy/values/$RCJ_CHART.yaml" fi + if [ "${RCJ_WITH_OVERLAY:-}" = "1" ] && [ -f "$OVERLAY_DIR/$RCJ_CHART.yaml" ]; then + RCJ_VALUES+=(-f "$OVERLAY_DIR/$RCJ_CHART.yaml") + fi "$ROOT/ci/helm-dependencies.sh" "$ROOT/$RCJ_CHART" >/dev/null || return 1 RCJ_MANIFEST="$RCJ_OUT.manifest.yaml" helm template "$RELEASE" "$ROOT/$RCJ_CHART" \ @@ -1210,6 +1241,385 @@ PY fi done +# --- Assertion 7: the ambient-mesh switch (Gitea #143) ----------------------- +# networkPolicy.ingress.ambientMesh renders the two things a pod needs before it +# can be reached inside Istio's ambient mesh, and the whole design question of +# #143 was how to allow the first of them without blunting assertion 2: +# +# 15008/TCP inbound - ztunnel's HBONE tunnel. Terminated by the +# mesh, so it is NOT a containerPort of the +# application. Assertion 2 would reject it, and +# it is right to reject it by default. +# 169.254.7.127/32 as a peer - where probeSources is non-empty. Inside the +# mesh the kubelet's probes are redirected and +# no longer arrive from the worker node's +# address. +# +# WHAT THIS ASSERTION IS FOR. The exception assertion 2 grants is NAMED and +# single-valued: one port number, gated on one value. The danger of any +# exception is that it stops being checked and becomes the hole it was carved +# around, so the exception is held to five things at once, on TWO renders of +# every chart: +# +# switch OFF (the main loop's render, reused) +# - 15008 must be ABSENT. Without this half the exception is a hole: a +# chart could render the port unconditionally and nothing would notice, +# because assertion 7 would find it present and assertion 2 is where it +# would have failed. +# - 169.254.7.127/32 must be absent too, for the same reason. +# switch ON +# - 15008/TCP must be PRESENT, or the value is decorative. +# - 15008 must be the ONLY ingress port that is not a containerPort. This is +# the line between a named exception and design A of #143: every other +# mistyped or NodePort number still fails here, with the switch on. +# - wherever the rule carrying 15008 restricts sources, that rule's "from" +# must contain 169.254.7.127/32. This is the inseparability of the two +# halves, asserted rather than trusted - and it is REACHABLE, because +# these renders apply ci/networkpolicy/.yaml, which is where +# probeSources is non-empty (template-chart, gotenberg). +# +# And the policy is found by the NAME CONVENTION, --*, the same +# way assertion 6 finds it: an exception must not be granted to an object this +# test did not identify. A chart whose policy stops following the convention +# fails here instead of silently losing its coverage. +# +# BUILT INTO THIS SCRIPT rather than added as a new test with its own pipeline +# job - the same decision as assertion 6 for #136, and here it is stronger than +# convenience. The exception WEAKENS assertion 2, so it belongs next to it: a +# separate script can be green while the exception it is paired with is wrong, +# and nothing would connect the two. Everything else is already here as well - +# the job runs over every chart on every pull request and so cannot be skipped +# by a path filter, helm is already the YAML parser through ci/yaml2json, and +# the verdict, grouping and summary plumbing is shared. +# +# EVERY CHART MUST OFFER THE VALUE, and a chart that does not fails. Same +# mechanism as assertion 0: the mistake arrives by copy into a NEW chart whose +# own diff nobody reads for it, which is exactly how garage (#81) and keycloak +# (#83) arrived with no policy at all after #5 had been filed. +AMBIENT_VALUE="networkPolicy.ingress.ambientMesh" +for CHART in "${CHARTS[@]}"; do + # A chart whose main-loop render already failed has nothing to compare + # against; it is reported there and would only produce a second, derived + # error here. + case " $FAILED " in *" $CHART "*) continue ;; esac + [ -s "$WORK/$CHART.json" ] || continue + + echo "::group::ambient-mesh switch: $CHART" + ROW_FAIL="" + ON_JSON="$WORK/ambient-$CHART-on.json" + if ! RCJ_WITH_OVERLAY=1 render_chart_json "$CHART" "$ON_JSON" \ + --set "$AMBIENT_VALUE=true"; then + sed 's/^/ /' "$WORK/xerr" + echo " FAIL $CHART does not render with $AMBIENT_VALUE=true" + echo "::error title=ambient-mesh $CHART::$CHART does not render with $AMBIENT_VALUE=true. Since Gitea #143 every chart in this repository offers that value, and networkPolicy carries additionalProperties: false in all of them - so an unknown key does not get ignored, it fails the render. Add the key to values.yaml AND to values.schema.json (schema and template belong together per chart), and render the ingress port 15008 plus, inside the source-restricted branch, the ipBlock 169.254.7.127/32. Copy template-chart/templates/xxx.networkpolicy.yaml." + ROW_FAIL=1 + fi + + # The policy's name is built from Chart.yaml's name, which is NOT always the + # directory name - template-chart is called "xxx", and the whole point of that + # chart is that it is a copy template. Reading the directory name here found + # exactly that on the first run, so the name comes from Chart.yaml. + CHART_NAME="$(sed -n 's/^name:[[:space:]]*//p' "$ROOT/$CHART/Chart.yaml" \ + | head -1 | tr -d '[:space:]')" + if [ -z "$CHART_NAME" ]; then + echo " FAIL $CHART has no name in Chart.yaml" + echo "::error title=ambient-mesh $CHART::$CHART/Chart.yaml declares no name, so the NetworkPolicy's conventional name could not be derived." + ROW_FAIL=1 + fi + + if [ -z "$ROW_FAIL" ]; then + set +e + python3 - "$CHART" "$RELEASE" "$AMBIENT_VALUE" "$WORK/$CHART.json" "$ON_JSON" \ + "$ROOT/$CHART" "$CHART_NAME" \ + >"$WORK/ambient-$CHART.out" 2>"$WORK/ambient-$CHART.pyerr" <<'PY' +import json, re, sys + +chart, release, gate, off_path, on_path, chart_dir, chart_name = sys.argv[1:8] + +PORT = 15008 +CIDR = "169.254.7.127/32" + +lines = [] +def ok(msg): lines.append("OK|%s: %s" % (chart, msg)) +def skip(msg): lines.append("SKIP|%s: %s" % (chart, msg)) +def fail(msg): lines.append("FAIL|%s: %s" % (chart, msg)) + + +def docs_of(path): + return [d for d in (json.load(open(path)).get("docs") or []) if isinstance(d, dict)] + + +def pod_templates(node): + out = [] + if isinstance(node, dict): + for key, value in node.items(): + if (key == "template" and isinstance(value, dict) + and isinstance(value.get("spec"), dict) + and "containers" in value["spec"]): + out.append(value) + out.extend(pod_templates(value)) + elif isinstance(node, list): + for value in node: + out.extend(pod_templates(value)) + return out + + +def labels_of(template): + meta = template.get("metadata") + labels = meta.get("labels") if isinstance(meta, dict) else None + return labels if isinstance(labels, dict) else {} + + +def matches(selector, labels): + for key, value in selector.items(): + if key not in labels or str(labels[key]) != str(value): + return False + return True + + +def container_ports(template): + out = [] + spec = template.get("spec") or {} + for key in ("containers", "initContainers"): + for container in spec.get(key) or []: + if not isinstance(container, dict): + continue + for port in container.get("ports") or []: + if isinstance(port, dict) and port.get("containerPort") is not None: + out.append((int(port["containerPort"]), port.get("name"))) + return out + + +def own_policies(path): + """This chart's OWN NetworkPolicies, by the -- name + convention - so a bundled subchart's policy can neither satisfy nor break an + assertion the parent chart is responsible for.""" + prefix = "%s-%s-" % (release, chart_name) + out = [] + for doc in docs_of(path): + if doc.get("kind") != "NetworkPolicy": + continue + name = ((doc.get("metadata") or {}).get("name")) or "" + if name.startswith(prefix): + out.append((name, doc)) + return out + + +def ingress_rules(doc): + spec = doc.get("spec") if isinstance(doc.get("spec"), dict) else {} + return [r for r in (spec.get("ingress") or []) if isinstance(r, dict)] + + +def rule_ports(rule): + """[(number_or_name, protocol)] for the rule's ports.""" + out = [] + for port in rule.get("ports") or []: + if not isinstance(port, dict): + continue + value = port.get("port") + proto = str(port.get("protocol") or "TCP") + if isinstance(value, int) or (isinstance(value, str) and value.isdigit()): + out.append((int(value), proto)) + elif value is not None: + out.append((value, proto)) + return out + + +def cidrs_of(rule): + out = [] + for peer in rule.get("from") or []: + if isinstance(peer, dict) and isinstance(peer.get("ipBlock"), dict): + out.append(str(peer["ipBlock"].get("cidr"))) + return out + + +# --- The value is declared, in BOTH places it has to be -------------------- +# The render with the switch on already proves the SCHEMA declares it - an +# undeclared key cannot pass additionalProperties: false. What that render +# cannot prove is that values.yaml ships the documented default, and a value +# only reachable through --set is not a value an operator finds. +values_yaml = "" +try: + values_yaml = open("%s/values.yaml" % chart_dir, encoding="utf-8").read() +except IOError: + pass +if re.search(r"^\s*ambientMesh\s*:", values_yaml, re.M): + ok("values.yaml ships the ambientMesh key with its default, and the render " + "with %s=true succeeded - which is itself the proof that " + "values.schema.json declares it, since networkPolicy carries " + "additionalProperties: false" % gate) +else: + fail("renders with %s=true - so the schema knows the key - but values.yaml " + "contains no ambientMesh key at all. A value that exists only in the " + "schema is reachable through --set and findable nowhere: the defaults " + "are what an operator reads. Add it with its comment, default false, " + "as in template-chart." % gate) + +off_policies = own_policies(off_path) +on_policies = own_policies(on_path) + +if not off_policies or not on_policies: + fail("renders no NetworkPolicy named %s-%s-* (off: %d, on: %d), so the " + "ambient-mesh exception could not be held against this chart's own " + "policy. The convention is {{ .Release.Name }}-{{ .Chart.Name }}-; " + "if the name changed, this assertion has to follow - it is not skipped, " + "because an exception granted to an object this test cannot identify is " + "worth less than no exception at all." + % (release, chart_name, len(off_policies), len(on_policies))) +else: + # --- The switch OFF half ------------------------------------------------ + off_hits, off_cidr_hits = [], [] + for name, doc in off_policies: + for i, rule in enumerate(ingress_rules(doc)): + for value, proto in rule_ports(rule): + if value == PORT: + off_hits.append("%s ingress[%d] %s/%s" % (name, i, value, proto)) + for cidr in cidrs_of(rule): + if cidr == CIDR: + off_cidr_hits.append("%s ingress[%d]" % (name, i)) + if off_hits: + fail("allows ingress port %d with %s at its default (false): %s. The " + "exception assertion 2 grants for %d is gated on that value, and " + "this is the half that keeps it from being a hole - a port rendered " + "unconditionally would otherwise be found present by this assertion " + "and never held against the pod spec by assertion 2. Gate the port " + "on the value." % (PORT, gate, ", ".join(off_hits), PORT)) + elif off_cidr_hits: + fail("carries the ipBlock %s with %s at its default (false): %s. The " + "probe-source half of the switch is for the mesh only, where the " + "kubelet's probes are redirected; outside it the address matches " + "nothing and makes the rule read as if it did something." + % (CIDR, gate, ", ".join(off_cidr_hits))) + else: + ok("with %s at its default the policy allows neither port %d nor the " + "ipBlock %s - the negative half of assertion 2's named exception, " + "without which the exception would be a hole" % (gate, PORT, CIDR)) + + # --- The switch ON half ------------------------------------------------- + templates = [] + for doc in docs_of(on_path): + templates.extend(pod_templates(doc)) + + found_port, bad_ports, restricted_without_cidr, carriers = [], [], [], [] + for name, doc in on_policies: + spec = doc.get("spec") if isinstance(doc.get("spec"), dict) else {} + selector = spec.get("podSelector") + match_labels = (selector or {}).get("matchLabels") or {} + selected = [t for t in templates if matches(match_labels, labels_of(t))] + declared = set() + for t in selected: + for number, pname in container_ports(t): + declared.add(number) + if pname: + declared.add(pname) + for i, rule in enumerate(ingress_rules(doc)): + for value, proto in rule_ports(rule): + if value == PORT: + found_port.append("%s ingress[%d] %d/%s" % (name, i, PORT, proto)) + carriers.append((name, i, rule)) + if proto != "TCP": + bad_ports.append("%s ingress[%d] renders %d as %s, but " + "HBONE is TCP" % (name, i, PORT, proto)) + elif value not in declared and selected: + bad_ports.append( + "%s ingress[%d] allows %r, which is no containerPort of " + "the selected pod spec" % (name, i, value)) + + if not found_port: + fail("renders no ingress port %d with %s=true, so the value is " + "decorative: the chart accepts it, the schema declares it, and the " + "policy that comes out is the same one as before. A pod whose " + "namespace has joined the ambient mesh is then unreachable, because " + "ambient does not bypass NetworkPolicy enforcement." % (PORT, gate)) + else: + ok("with %s=true the policy allows %s - ztunnel's HBONE tunnel, the one " + "port assertion 2 grants a named exception for" + % (gate, ", ".join(found_port))) + + if bad_ports: + fail("with %s=true the policy allows ingress port(s) that are NOT " + "containerPorts of the selected pod spec and are NOT the one number " + "the exception covers (%d): %s. This is the line between a named " + "exception and the free port key that was rejected as design A of " + "#143 - with the switch on, every OTHER wrong port still fails, " + "including a NodePort number written where a pod port belongs." + % (gate, PORT, "; ".join(bad_ports))) + elif found_port: + ok("with %s=true, port %d is still the ONLY ingress port of this chart " + "that is not a containerPort of the selected pod spec - the exception " + "covers exactly one number and nothing rode in beside it" + % (gate, PORT)) + + # The inseparability of the two halves, where the render can reach it. + for name, i, rule in carriers: + if not rule.get("from"): + continue + if CIDR in cidrs_of(rule): + ok("%s ingress[%d] carries port %d AND the ipBlock %s in the same " + "\"from\" list - the two halves of the switch are inseparable, " + "which is the reason it is one value and not two" + % (name, i, PORT, CIDR)) + else: + restricted_without_cidr.append( + "%s ingress[%d] (peers: %s)" + % (name, i, ", ".join(cidrs_of(rule)) or "no ipBlock at all")) + if restricted_without_cidr: + fail("with %s=true the rule carrying port %d restricts its sources but " + "does not include the ipBlock %s: %s. Inside the mesh the kubelet's " + "probes are redirected and arrive from that address instead of from " + "the worker node's, so a restricted rule without it drops every " + "probe - the pod never becomes Ready and the symptom reads as a " + "broken application rather than as a policy. That is assertion 4 of " + "this script, re-created one value further along, and it is the " + "whole reason the two halves are a single switch." + % (gate, PORT, CIDR, "; ".join(restricted_without_cidr))) + if not any(r.get("from") for _n, _i, r in carriers): + skip("the rule carrying port %d has no \"from\" list in this render, so " + "the probe-source half (%s) is not reachable here and was not " + "checked. That is the state of every chart's DEFAULTS - probeSources " + "is empty everywhere - and it is why ci/networkpolicy/.yaml " + "exists: template-chart and gotenberg render the restricted branch " + "and the half IS checked there. Reported, not counted as passing." + % (PORT, CIDR)) + +for line in lines: + print(line) +PY + RC=$? + set -e + if [ "$RC" -gt 1 ]; then + sed 's/^/ /' "$WORK/ambient-$CHART.pyerr" + echo "::error title=ambient-mesh $CHART::the ambient-mesh comparison itself failed for $CHART (python exit $RC). Treated as a failure, never as a pass." + ROW_FAIL=1 + else + while IFS='|' read -r VERDICT DETAIL; do + case "$VERDICT" in + OK) + TOTAL_ASSERTIONS=$((TOTAL_ASSERTIONS + 1)) + echo " ok $DETAIL" + ;; + SKIP) echo " gap $DETAIL" ;; + FAIL) + ROW_FAIL=1 + echo " FAIL $DETAIL" + echo "::error title=ambient-mesh $CHART::$DETAIL" + ;; + *) [ -n "$VERDICT" ] && echo " $VERDICT $DETAIL" ;; + esac + done <"$WORK/ambient-$CHART.out" + fi + fi + + echo "::endgroup::" + if [ -n "$ROW_FAIL" ]; then + FAILED="$FAILED $CHART(ambient-mesh)" + echo "ambient-mesh $CHART: FAILED" + else + AMBIENT_OK="${AMBIENT_OK:-} $CHART" + echo "ambient-mesh $CHART: ok" + fi +done + # --- Report ------------------------------------------------------------------ count() { set -- $1; echo "$#"; } listing() { set -- $1; [ "$#" -eq 0 ] && { echo "–"; return; }; printf '`%s`' "$1"; shift; for C in "$@"; do printf ', `%s`' "$C"; done; echo; } @@ -1225,6 +1635,7 @@ SUMMARY="${GITHUB_STEP_SUMMARY:-/dev/null}" echo "| Result | Count | Charts |" echo "|---|---|---|" echo "| Policies checked | $(count "$CHECKED") | $(listing "$CHECKED") |" + echo "| Ambient-mesh switch held (Gitea #143) | $(count "${AMBIENT_OK:-}") | $(listing "${AMBIENT_OK:-}") |" echo "| Failed | $(count "$FAILED") | $(listing "$FAILED") |" echo echo "$TOTAL_ASSERTIONS assertion(s) held in total." diff --git a/template-chart/templates/xxx.networkpolicy.yaml b/template-chart/templates/xxx.networkpolicy.yaml index 023f1fd..90051ce 100644 --- a/template-chart/templates/xxx.networkpolicy.yaml +++ b/template-chart/templates/xxx.networkpolicy.yaml @@ -96,6 +96,24 @@ spec this policy selects. {{- $probeSources := $np.probeSources | default list }} {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} +{{- /* networkPolicy.ingress - the policy's INGRESS DIRECTION, and not to be + confused with networkPolicy.ingressController one line up, nor with the + chart's top-level .Values.ingress. Three different things whose names + overlap as substrings: + + .Values.ingress the Ingress OBJECT this chart renders + .Values.networkPolicy.ingress switches on the policy's inbound side + .Values.networkPolicy.ingressController + the SOURCE SELECTOR for the ingress + controller's pods, a sibling of + networkPolicy.ingress and NO PART OF + IT + + Named here because a search for "ingress" matches all three, and one + round of this work already reported ingressController as an inbound + escape hatch on the strength of the substring alone. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy @@ -129,6 +147,29 @@ spec: - protocol: {{ .protocol | default "TCP" }} port: {{ .containerPort }} {{- end }} + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh. 15008/TCP is + ztunnel's HBONE tunnel, the port every inbound mesh connection + arrives on once the namespace carries istio.io/dataplane-mode. + Istio's own documentation is explicit that ambient "does not and + will never bypass Kubernetes NetworkPolicy enforcement", so + without this port a pod in the mesh is simply unreachable. + + It is the one port in this rule that is NOT a containerPort of + the selected pod spec - ztunnel terminates it, not the + application - so ci/run-networkpolicy-test.sh holds a NAMED + exception for exactly this number, gated on exactly this value. + Anything else that is not a containerPort still fails, and so + does 15008 itself while the value is off. + + NOT MEASURED, and the chart should not pretend otherwise: that + 15008 ALONE suffices comes from Istio's documentation. What has + been measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace that joins the mesh, and + no namespace carries the label yet. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes come from the IP of the WORKER NODE running the @@ -168,6 +209,35 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason + the switch is one value instead of two. Inside the mesh the + kubelet's probes are redirected and no longer arrive from the + worker node's address: they arrive from 169.254.7.127, which is + therefore the probeSources entry nobody would think to add. + + The two halves are deliberately inseparable. An operator who + only opens 15008 by hand has a policy that passes every review - + and the moment probeSources is non-empty, every probe is dropped, + the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh, re-created one value further along. + One switch cannot be set halfway. + + Only rendered in this branch, because this is the only branch + with a "from" list at all: with probeSources empty the rule is + open and needs no peer. The IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff is deliberately absent - + this cluster is SingleStack IPv4, and an ipBlock of the wrong + family is accepted by the API server and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart of + this repository (stalwart sets it to the empty list explicitly), + so no release reaches this branch today. What CI proves is its + render, through the overlay in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the cluster, because this port carries no NodePort. The diff --git a/template-chart/values.schema.json b/template-chart/values.schema.json index fcbcdef..a1fcc0b 100644 --- a/template-chart/values.schema.json +++ b/template-chart/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/template-chart/values.yaml b/template-chart/values.yaml index 85ef0de..0812102 100644 --- a/template-chart/values.yaml +++ b/template-chart/values.yaml @@ -272,6 +272,62 @@ networkPolicy: podLabels: app.kubernetes.io/name: rke2-traefik + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are not the + # same thing: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the controller's + # pods. A SIBLING of this block and NO PART + # OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh. OFF by default, and additive: + # with it off this chart renders byte for byte as it did before the value + # existed. + # + # WHAT IT RENDERS, and the two halves are the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and Istio's documentation is explicit that + # ambient "does not and will never bypass Kubernetes NetworkPolicy + # enforcement". Without this port a pod whose namespace has joined the + # mesh is unreachable. + # 2. 169.254.7.127/32 as an additional probe source - but ONLY where + # probeSources is non-empty, because only then does the rule have a + # "from" list at all. Inside the mesh the kubelet's probes are + # redirected and no longer come from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO. Point 2 is the one nobody thinks of. An + # operator who adds "15008" to a port list by hand writes a policy that + # passes review, and the first time probeSources is narrowed every probe is + # dropped: the pod never becomes Ready and the symptom reads as a broken + # application rather than as a policy. A switch whose halves can only be + # had together cannot be set halfway - which is why this is a named + # intention and not a free port list. + # + # TWO THINGS IT DOES NOT PROVE, and they belong next to the value rather + # than in a commit message: + # + # - That 15008 alone is enough. That comes from Istio's documentation. + # Measured on this cluster is only which listeners ztunnel opens + # ([::]:15008 inbound, plus its own admin and stats ports). Provable at + # the first namespace that joins the mesh; none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every + # chart here, so no release reaches that branch. CI proves its render, + # not its effect. + # + # If the policy restricts sources, note that 15008 is restricted with them: + # the port joins the rule that carries the probe port, so it is reachable + # from that rule's peers only. Whether ztunnel needs a peer of its own is + # open - it runs with its own pod IPs in istio-system (measured: no + # hostNetwork on the DaemonSet, Istio 1.30.6), so if one turns out to be + # needed it is expressible as a namespaceSelector plus podSelector and does + # not need an unknowable node-IP CIDR. + ambientMesh: false + 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 -- 2.54.0 From 009c93e3cd5fe532d902b1488dd2d536477e7511 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:05:46 +0000 Subject: [PATCH 02/63] feat(cyberchef): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed. Minor, not major: additive value, unchanged default, unchanged render. --- cyberchef/Chart.yaml | 2 +- .../templates/cyberchef.networkpolicy.yaml | 52 +++++++++++++++++++ cyberchef/values.schema.json | 11 ++++ cyberchef/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/cyberchef/Chart.yaml b/cyberchef/Chart.yaml index 7b6dec9..3e75799 100644 --- a/cyberchef/Chart.yaml +++ b/cyberchef/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/gchq/CyberChef/master/src/web/static/ima type: application -version: 10.0.0+up11.4.0 +version: 10.1.0+up11.4.0 appVersion: "11.4.0" maintainers: diff --git a/cyberchef/templates/cyberchef.networkpolicy.yaml b/cyberchef/templates/cyberchef.networkpolicy.yaml index 5f95b85..e364808 100644 --- a/cyberchef/templates/cyberchef.networkpolicy.yaml +++ b/cyberchef/templates/cyberchef.networkpolicy.yaml @@ -51,6 +51,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -85,6 +93,26 @@ spec: - ports: - protocol: TCP port: 8080 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -128,6 +156,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/cyberchef/values.schema.json b/cyberchef/values.schema.json index 6aace01..cd477ab 100644 --- a/cyberchef/values.schema.json +++ b/cyberchef/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/cyberchef/values.yaml b/cyberchef/values.yaml index b2113d0..60cd5cb 100644 --- a/cyberchef/values.yaml +++ b/cyberchef/values.yaml @@ -189,6 +189,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 7a3f15efc75b69b9e2c6a589d7b77066c81e147a Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:39 +0000 Subject: [PATCH 03/63] feat(apache-tika): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- apache-tika/Chart.yaml | 2 +- .../templates/apache-tika.networkpolicy.yaml | 52 +++++++++++++++++++ apache-tika/values.schema.json | 11 ++++ apache-tika/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/apache-tika/Chart.yaml b/apache-tika/Chart.yaml index c6e5b5c..975097a 100644 --- a/apache-tika/Chart.yaml +++ b/apache-tika/Chart.yaml @@ -5,5 +5,5 @@ icon: https://www.apache.org/logos/res/tika/default.png type: application -version: 11.0.0+up3.3.1.0.full +version: 11.1.0+up3.3.1.0.full appVersion: "3.3.1.0-full" diff --git a/apache-tika/templates/apache-tika.networkpolicy.yaml b/apache-tika/templates/apache-tika.networkpolicy.yaml index 7d24aa0..39ab28d 100644 --- a/apache-tika/templates/apache-tika.networkpolicy.yaml +++ b/apache-tika/templates/apache-tika.networkpolicy.yaml @@ -50,6 +50,14 @@ selects. {{- $ns := .Release.Namespace }} {{- $probeSources := $np.probeSources | default list }} {{- $allowedNs := $np.allowedNamespaces | default list }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -83,6 +91,26 @@ spec: - ports: - protocol: TCP port: 9998 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -110,6 +138,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/apache-tika/values.schema.json b/apache-tika/values.schema.json index f2d820e..f08fc46 100644 --- a/apache-tika/values.schema.json +++ b/apache-tika/values.schema.json @@ -31,6 +31,17 @@ "type": "string" } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/apache-tika/values.yaml b/apache-tika/values.yaml index b749e35..76ce2fe 100644 --- a/apache-tika/values.yaml +++ b/apache-tika/values.yaml @@ -115,6 +115,57 @@ networkPolicy: # 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: [] + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From d7d6f8a907887ed5ee33f12e8f005a14a0c6edba Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:39 +0000 Subject: [PATCH 04/63] feat(bentopdf): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- bentopdf/Chart.yaml | 2 +- .../templates/bentopdf.networkpolicy.yaml | 52 +++++++++++++++++++ bentopdf/values.schema.json | 11 ++++ bentopdf/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/bentopdf/Chart.yaml b/bentopdf/Chart.yaml index a4d6091..00d9491 100644 --- a/bentopdf/Chart.yaml +++ b/bentopdf/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/alam00000/bentopdf/main/public/images/fa type: application -version: 2.0.0+up2.8.8 +version: 2.1.0+up2.8.8 appVersion: "2.8.8" maintainers: diff --git a/bentopdf/templates/bentopdf.networkpolicy.yaml b/bentopdf/templates/bentopdf.networkpolicy.yaml index 55ad14a..06f7697 100644 --- a/bentopdf/templates/bentopdf.networkpolicy.yaml +++ b/bentopdf/templates/bentopdf.networkpolicy.yaml @@ -44,6 +44,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -76,6 +84,26 @@ spec: - ports: - protocol: TCP port: 8080 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -118,6 +146,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/bentopdf/values.schema.json b/bentopdf/values.schema.json index 9cd201d..61168a2 100644 --- a/bentopdf/values.schema.json +++ b/bentopdf/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/bentopdf/values.yaml b/bentopdf/values.yaml index b86f830..25831ee 100644 --- a/bentopdf/values.yaml +++ b/bentopdf/values.yaml @@ -362,6 +362,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From fef9034862ff8b86506ff09904adc536649d7a71 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:40 +0000 Subject: [PATCH 05/63] feat(bulwark): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- bulwark/Chart.yaml | 2 +- bulwark/templates/bulwark.networkpolicy.yaml | 52 ++++++++++++++++++++ bulwark/values.schema.json | 11 +++++ bulwark/values.yaml | 51 +++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/bulwark/Chart.yaml b/bulwark/Chart.yaml index 2bae6a9..f03d14c 100644 --- a/bulwark/Chart.yaml +++ b/bulwark/Chart.yaml @@ -21,7 +21,7 @@ type: application # duplicated here. The short version: eight tag shapes, six of them things # nobody should pin, and plain `docker` versioning excludes all six - so that # block deliberately carries NO versioningTemplate. -version: 2.0.0+up1.12.0 +version: 2.1.0+up1.12.0 appVersion: "1.12.0" maintainers: diff --git a/bulwark/templates/bulwark.networkpolicy.yaml b/bulwark/templates/bulwark.networkpolicy.yaml index c8f60b5..1e189c2 100644 --- a/bulwark/templates/bulwark.networkpolicy.yaml +++ b/bulwark/templates/bulwark.networkpolicy.yaml @@ -50,6 +50,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := .Values.ingress.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -84,6 +92,26 @@ spec: - ports: - protocol: TCP port: 3000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -126,6 +154,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/bulwark/values.schema.json b/bulwark/values.schema.json index e036389..4f2d858 100644 --- a/bulwark/values.schema.json +++ b/bulwark/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/bulwark/values.yaml b/bulwark/values.yaml index aa294cf..2802305 100644 --- a/bulwark/values.yaml +++ b/bulwark/values.yaml @@ -883,6 +883,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 5b53d62b5f7d21082e086625e6f2f288af50824f Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:40 +0000 Subject: [PATCH 06/63] feat(calibre-web-automated): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- calibre-web-automated/Chart.yaml | 2 +- .../calibre-web-automated.networkpolicy.yaml | 52 +++++++++++++++++++ calibre-web-automated/values.schema.json | 11 ++++ calibre-web-automated/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/calibre-web-automated/Chart.yaml b/calibre-web-automated/Chart.yaml index aee34f2..5e961e5 100644 --- a/calibre-web-automated/Chart.yaml +++ b/calibre-web-automated/Chart.yaml @@ -5,7 +5,7 @@ name: calibre-web-automated description: A Helm chart to deploy calibere-web-automated icon: https://raw.githubusercontent.com/crocodilestick/Calibre-Web-Automated/main/README_images/cwa-logo-round-dark.png -version: 8.0.0+upv4.0.6 +version: 8.1.0+upv4.0.6 appVersion: v4.0.6 maintainers: diff --git a/calibre-web-automated/templates/calibre-web-automated.networkpolicy.yaml b/calibre-web-automated/templates/calibre-web-automated.networkpolicy.yaml index cff3cb5..7fb7e89 100644 --- a/calibre-web-automated/templates/calibre-web-automated.networkpolicy.yaml +++ b/calibre-web-automated/templates/calibre-web-automated.networkpolicy.yaml @@ -45,6 +45,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := .Values.ingress.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -79,6 +87,26 @@ spec: - ports: - protocol: TCP port: 8083 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -121,6 +149,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/calibre-web-automated/values.schema.json b/calibre-web-automated/values.schema.json index cfb24d7..5695744 100644 --- a/calibre-web-automated/values.schema.json +++ b/calibre-web-automated/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/calibre-web-automated/values.yaml b/calibre-web-automated/values.yaml index dd8a5e5..11d505b 100644 --- a/calibre-web-automated/values.yaml +++ b/calibre-web-automated/values.yaml @@ -250,6 +250,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 9950e935ab769d98e4e4292c2e143b834c2c0813 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:40 +0000 Subject: [PATCH 07/63] feat(freesailarr): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- freesailarr/Chart.yaml | 2 +- .../templates/freesailarr.networkpolicy.yaml | 52 +++++++++++++++++++ freesailarr/values.schema.json | 11 ++++ freesailarr/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/freesailarr/Chart.yaml b/freesailarr/Chart.yaml index 6e14a3e..8a179bc 100644 --- a/freesailarr/Chart.yaml +++ b/freesailarr/Chart.yaml @@ -8,7 +8,7 @@ description: A Helm chart to deploy a Wireguard-tunneled media automation stack type: application -version: 5.0.0+up1.0.0 +version: 5.1.0+up1.0.0 # The chart's own version marker, freely chosen and tied to no upstream. The # stack bundles ten independently versioned projects (nine of them on by # default), so there is no single "app version" to track here. diff --git a/freesailarr/templates/freesailarr.networkpolicy.yaml b/freesailarr/templates/freesailarr.networkpolicy.yaml index 9ea4dfd..502e9f9 100644 --- a/freesailarr/templates/freesailarr.networkpolicy.yaml +++ b/freesailarr/templates/freesailarr.networkpolicy.yaml @@ -68,6 +68,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -125,6 +133,26 @@ spec: port: 6767 - protocol: TCP port: 5007 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -168,6 +196,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/freesailarr/values.schema.json b/freesailarr/values.schema.json index 4b580d1..476fb3b 100644 --- a/freesailarr/values.schema.json +++ b/freesailarr/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/freesailarr/values.yaml b/freesailarr/values.yaml index c54e786..044d217 100644 --- a/freesailarr/values.yaml +++ b/freesailarr/values.yaml @@ -1412,6 +1412,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From aedc6ff13655a1d44b4c9576db92e2cc7990e3b7 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:40 +0000 Subject: [PATCH 08/63] feat(gotenberg): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- gotenberg/Chart.yaml | 2 +- .../templates/gotenberg.networkpolicy.yaml | 52 +++++++++++++++++++ gotenberg/values.schema.json | 11 ++++ gotenberg/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/gotenberg/Chart.yaml b/gotenberg/Chart.yaml index bf1dd51..4573e43 100644 --- a/gotenberg/Chart.yaml +++ b/gotenberg/Chart.yaml @@ -5,7 +5,7 @@ icon: https://gotenberg.dev/img/logo.png type: application -version: 9.1.0+up8.38.1 +version: 9.2.0+up8.38.1 appVersion: "8.38.1" maintainers: diff --git a/gotenberg/templates/gotenberg.networkpolicy.yaml b/gotenberg/templates/gotenberg.networkpolicy.yaml index 8ad7d24..121f758 100644 --- a/gotenberg/templates/gotenberg.networkpolicy.yaml +++ b/gotenberg/templates/gotenberg.networkpolicy.yaml @@ -54,6 +54,14 @@ selects. {{- $ns := .Release.Namespace }} {{- $probeSources := $np.probeSources | default list }} {{- $allowedNs := $np.allowedNamespaces | default list }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -86,6 +94,26 @@ spec: - ports: - protocol: TCP port: 3000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -113,6 +141,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/gotenberg/values.schema.json b/gotenberg/values.schema.json index e450c7a..9b86507 100644 --- a/gotenberg/values.schema.json +++ b/gotenberg/values.schema.json @@ -31,6 +31,17 @@ "type": "string" } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/gotenberg/values.yaml b/gotenberg/values.yaml index 9794e8b..476964b 100644 --- a/gotenberg/values.yaml +++ b/gotenberg/values.yaml @@ -140,6 +140,57 @@ networkPolicy: # 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: [] + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + egress: # REMOTE ASSETS: lets Chromium fetch over TCP 80 and 443, to any # destination. OFF by default, and both halves of that are deliberate. -- 2.54.0 From b0572834e069ee242a527356e657d5ca295b1fe3 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:40 +0000 Subject: [PATCH 09/63] feat(matrix-synapse): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- matrix-synapse/Chart.yaml | 2 +- .../matrix-synapse.networkpolicy.yaml | 52 +++++++++++++++++++ matrix-synapse/values.schema.json | 11 ++++ matrix-synapse/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/matrix-synapse/Chart.yaml b/matrix-synapse/Chart.yaml index afa2208..b3c0c1e 100644 --- a/matrix-synapse/Chart.yaml +++ b/matrix-synapse/Chart.yaml @@ -3,7 +3,7 @@ name: matrix-synapse description: A Helm chart to deploy a Matrix Synapse server on Kubernetes icon: https://raw.githubusercontent.com/element-hq/synapse/develop/docs/favicon.png type: application -version: 8.0.0+upv1.159.0 +version: 8.1.0+upv1.159.0 appVersion: "v1.159.0" maintainers: - name: jklein \ No newline at end of file diff --git a/matrix-synapse/templates/matrix-synapse.networkpolicy.yaml b/matrix-synapse/templates/matrix-synapse.networkpolicy.yaml index 79ecf57..c568c64 100644 --- a/matrix-synapse/templates/matrix-synapse.networkpolicy.yaml +++ b/matrix-synapse/templates/matrix-synapse.networkpolicy.yaml @@ -49,6 +49,14 @@ WHAT THESE POLICIES DO NOT DO: {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -76,6 +84,26 @@ spec: - ports: - protocol: TCP port: 8008 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's @@ -121,6 +149,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/matrix-synapse/values.schema.json b/matrix-synapse/values.schema.json index cdfaf9f..a7ced6a 100644 --- a/matrix-synapse/values.schema.json +++ b/matrix-synapse/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/matrix-synapse/values.yaml b/matrix-synapse/values.yaml index 97782c2..c97840a 100644 --- a/matrix-synapse/values.yaml +++ b/matrix-synapse/values.yaml @@ -364,6 +364,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + egress: # Additional outbound TCP ports FOR SYNAPSE ONLY. These deliberately do NOT # widen the delegation server's policy: an entry meant for Synapse's -- 2.54.0 From e09b3bb9f8e9256737296229b21541f5ac4db29f Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:40 +0000 Subject: [PATCH 10/63] feat(outline): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- outline/Chart.yaml | 2 +- outline/templates/outline.networkpolicy.yaml | 52 ++++++++++++++++++++ outline/values.schema.json | 11 +++++ outline/values.yaml | 51 +++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/outline/Chart.yaml b/outline/Chart.yaml index 4fcf627..b5550e6 100644 --- a/outline/Chart.yaml +++ b/outline/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/outline/outline/main/public/images/icon- type: application -version: 8.1.0+up1.11.0 +version: 8.2.0+up1.11.0 appVersion: 1.11.0 maintainers: diff --git a/outline/templates/outline.networkpolicy.yaml b/outline/templates/outline.networkpolicy.yaml index 3be11b3..13a7d51 100644 --- a/outline/templates/outline.networkpolicy.yaml +++ b/outline/templates/outline.networkpolicy.yaml @@ -51,6 +51,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -85,6 +93,26 @@ spec: - ports: - protocol: TCP port: 3000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -127,6 +155,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/outline/values.schema.json b/outline/values.schema.json index 5087fe9..3913c54 100644 --- a/outline/values.schema.json +++ b/outline/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/outline/values.yaml b/outline/values.yaml index 4921a97..309a9f4 100644 --- a/outline/values.yaml +++ b/outline/values.yaml @@ -241,6 +241,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 1a7d5817c20fef33deb44207d58784656d08bb59 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:40 +0000 Subject: [PATCH 11/63] feat(paperless-ngx): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- paperless-ngx/Chart.yaml | 3 +- .../paperless-server.networkpolicy.yaml | 52 +++++++++++++++++++ paperless-ngx/values.schema.json | 11 ++++ paperless-ngx/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 2 deletions(-) diff --git a/paperless-ngx/Chart.yaml b/paperless-ngx/Chart.yaml index e896ac3..f088939 100644 --- a/paperless-ngx/Chart.yaml +++ b/paperless-ngx/Chart.yaml @@ -5,8 +5,7 @@ icon: https://raw.githubusercontent.com/paperless-ngx/paperless-ngx/dev/src-ui/s type: application -version: 15.0.0+up3.3.1 - +version: 15.1.0+up3.3.1 appVersion: 3.3.1 maintainers: diff --git a/paperless-ngx/templates/paperless-server.networkpolicy.yaml b/paperless-ngx/templates/paperless-server.networkpolicy.yaml index 765b691..6e33f23 100644 --- a/paperless-ngx/templates/paperless-server.networkpolicy.yaml +++ b/paperless-ngx/templates/paperless-server.networkpolicy.yaml @@ -79,6 +79,14 @@ that the egress rule below contains their containerPorts. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -113,6 +121,26 @@ spec: - ports: - protocol: TCP port: 8000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -155,6 +183,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/paperless-ngx/values.schema.json b/paperless-ngx/values.schema.json index 5ff881a..d5d4c02 100644 --- a/paperless-ngx/values.schema.json +++ b/paperless-ngx/values.schema.json @@ -87,6 +87,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/paperless-ngx/values.yaml b/paperless-ngx/values.yaml index ee4cea0..2e693cc 100644 --- a/paperless-ngx/values.yaml +++ b/paperless-ngx/values.yaml @@ -636,6 +636,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + egress: # Outbound IMAPS (993) for paperless' mail import. OFF, and it has to be a # value rather than something the chart derives: the mail accounts are ROWS -- 2.54.0 From 609bbaeab21caa4f3ecd8c49db27278ca35c3a91 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:06:40 +0000 Subject: [PATCH 12/63] feat(patchmon): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- patchmon/Chart.yaml | 2 +- .../templates/patchmon.networkpolicy.yaml | 52 +++++++++++++++++++ patchmon/values.schema.json | 11 ++++ patchmon/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/patchmon/Chart.yaml b/patchmon/Chart.yaml index 74bde87..9e48ebe 100644 --- a/patchmon/Chart.yaml +++ b/patchmon/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/PatchMon/PatchMon/main/frontend/public/a type: application -version: 8.0.0+up2.1.3 +version: 8.1.0+up2.1.3 appVersion: "2.1.3" maintainers: diff --git a/patchmon/templates/patchmon.networkpolicy.yaml b/patchmon/templates/patchmon.networkpolicy.yaml index bcef2e2..a6e52f2 100644 --- a/patchmon/templates/patchmon.networkpolicy.yaml +++ b/patchmon/templates/patchmon.networkpolicy.yaml @@ -45,6 +45,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := .Values.ingress.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -80,6 +88,26 @@ spec: - ports: - protocol: TCP port: 3000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -122,6 +150,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/patchmon/values.schema.json b/patchmon/values.schema.json index 233df81..001cf31 100644 --- a/patchmon/values.schema.json +++ b/patchmon/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/patchmon/values.yaml b/patchmon/values.yaml index f471153..5ede5c3 100644 --- a/patchmon/values.yaml +++ b/patchmon/values.yaml @@ -288,6 +288,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 38ea124c947cbea92fcc0be68d0bc897d7288a4c Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:18 +0000 Subject: [PATCH 13/63] feat(rallly): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- rallly/Chart.yaml | 2 +- rallly/templates/rallly.networkpolicy.yaml | 52 ++++++++++++++++++++++ rallly/values.schema.json | 11 +++++ rallly/values.yaml | 51 +++++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/rallly/Chart.yaml b/rallly/Chart.yaml index fb94316..a63543f 100644 --- a/rallly/Chart.yaml +++ b/rallly/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/lukevella/rallly/main/apps/landing/publi type: application -version: 9.0.1+up4.15.5 +version: 9.1.0+up4.15.5 appVersion: "4.15.5" maintainers: diff --git a/rallly/templates/rallly.networkpolicy.yaml b/rallly/templates/rallly.networkpolicy.yaml index 1ac8d3e..646cd56 100644 --- a/rallly/templates/rallly.networkpolicy.yaml +++ b/rallly/templates/rallly.networkpolicy.yaml @@ -45,6 +45,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := .Values.ingress.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -79,6 +87,26 @@ spec: - ports: - protocol: TCP port: 3000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -121,6 +149,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/rallly/values.schema.json b/rallly/values.schema.json index 0038530..63cdea6 100644 --- a/rallly/values.schema.json +++ b/rallly/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/rallly/values.yaml b/rallly/values.yaml index ea1ae68..b1ce5ce 100644 --- a/rallly/values.yaml +++ b/rallly/values.yaml @@ -252,6 +252,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 589021aed12596aacc37d639895a291f038b1234 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:18 +0000 Subject: [PATCH 14/63] feat(rss-bridge): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- rss-bridge/Chart.yaml | 2 +- .../templates/rss-bridge.networkpolicy.yaml | 52 +++++++++++++++++++ rss-bridge/values.schema.json | 11 ++++ rss-bridge/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/rss-bridge/Chart.yaml b/rss-bridge/Chart.yaml index 65d1d7d..aed1583 100644 --- a/rss-bridge/Chart.yaml +++ b/rss-bridge/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/RSS-Bridge/rss-bridge/master/static/logo type: application -version: 3.0.0+up2026.09.30 +version: 3.1.0+up2026.09.30 appVersion: "2026-09-30" maintainers: diff --git a/rss-bridge/templates/rss-bridge.networkpolicy.yaml b/rss-bridge/templates/rss-bridge.networkpolicy.yaml index 0c0b988..e21ebcd 100644 --- a/rss-bridge/templates/rss-bridge.networkpolicy.yaml +++ b/rss-bridge/templates/rss-bridge.networkpolicy.yaml @@ -48,6 +48,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -85,6 +93,26 @@ spec: - ports: - protocol: TCP port: 80 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -127,6 +155,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/rss-bridge/values.schema.json b/rss-bridge/values.schema.json index 1be3172..c2851e3 100644 --- a/rss-bridge/values.schema.json +++ b/rss-bridge/values.schema.json @@ -46,6 +46,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/rss-bridge/values.yaml b/rss-bridge/values.yaml index b765e20..997be38 100644 --- a/rss-bridge/values.yaml +++ b/rss-bridge/values.yaml @@ -419,6 +419,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 6fc8332ffd010fc38b615200ea46989590caa249 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:19 +0000 Subject: [PATCH 15/63] feat(searxng): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- searxng/Chart.yaml | 2 +- searxng/templates/searxng.networkpolicy.yaml | 52 ++++++++++++++++++++ searxng/values.schema.json | 11 +++++ searxng/values.yaml | 51 +++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/searxng/Chart.yaml b/searxng/Chart.yaml index e7110d8..5725637 100644 --- a/searxng/Chart.yaml +++ b/searxng/Chart.yaml @@ -9,7 +9,7 @@ type: application # (2026.8.22-9fea41204) and "latest" moves daily. appVersion is therefore the # exact tag, and the +up suffix normalizes the dash to a dot as the versioning # scheme requires - the same shape rss-bridge and minecraft-server use. -version: 2.0.0+up2026.10.13.22c64b8d3 +version: 2.1.0+up2026.10.13.22c64b8d3 appVersion: "2026.10.13-22c64b8d3" maintainers: diff --git a/searxng/templates/searxng.networkpolicy.yaml b/searxng/templates/searxng.networkpolicy.yaml index f44b1ab..e2ac245 100644 --- a/searxng/templates/searxng.networkpolicy.yaml +++ b/searxng/templates/searxng.networkpolicy.yaml @@ -46,6 +46,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -80,6 +88,26 @@ spec: - ports: - protocol: TCP port: 8080 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -122,6 +150,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/searxng/values.schema.json b/searxng/values.schema.json index 0182a86..e6b89e8 100644 --- a/searxng/values.schema.json +++ b/searxng/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/searxng/values.yaml b/searxng/values.yaml index 6ea00f4..cc4a4a3 100644 --- a/searxng/values.yaml +++ b/searxng/values.yaml @@ -465,6 +465,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 0d2a385673ddbab2bcb40aa95bb4d1ed519682d4 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:19 +0000 Subject: [PATCH 16/63] feat(sure): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- sure/Chart.yaml | 2 +- sure/templates/sure.networkpolicy.yaml | 52 ++++++++++++++++++++++++++ sure/values.schema.json | 11 ++++++ sure/values.yaml | 51 +++++++++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/sure/Chart.yaml b/sure/Chart.yaml index d4d1a76..d1b4e6f 100644 --- a/sure/Chart.yaml +++ b/sure/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/we-promise/sure/main/public/android-chro type: application -version: 2.0.0+up0.7.4 +version: 2.1.0+up0.7.4 appVersion: "0.7.4" maintainers: diff --git a/sure/templates/sure.networkpolicy.yaml b/sure/templates/sure.networkpolicy.yaml index b3a2d2b..e69289c 100644 --- a/sure/templates/sure.networkpolicy.yaml +++ b/sure/templates/sure.networkpolicy.yaml @@ -52,6 +52,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := .Values.ingress.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -86,6 +94,26 @@ spec: - ports: - protocol: TCP port: 3000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -128,6 +156,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/sure/values.schema.json b/sure/values.schema.json index 81041bb..46d2f53 100644 --- a/sure/values.schema.json +++ b/sure/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/sure/values.yaml b/sure/values.yaml index fe05e49..8b50e1b 100644 --- a/sure/values.yaml +++ b/sure/values.yaml @@ -443,6 +443,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 6fd61beec7657edf3eb3c8ecd43bd186b3fab933 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:19 +0000 Subject: [PATCH 17/63] feat(urlaubsverwaltung): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- urlaubsverwaltung/Chart.yaml | 2 +- .../urlaubsverwaltung.networkpolicy.yaml | 52 +++++++++++++++++++ urlaubsverwaltung/values.schema.json | 11 ++++ urlaubsverwaltung/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/urlaubsverwaltung/Chart.yaml b/urlaubsverwaltung/Chart.yaml index 2470ec7..b12d099 100644 --- a/urlaubsverwaltung/Chart.yaml +++ b/urlaubsverwaltung/Chart.yaml @@ -4,5 +4,5 @@ description: A Helm chart to deploy urlaubsverwaltung on Kubernetes icon: https://raw.githubusercontent.com/urlaubsverwaltung/urlaubsverwaltung/main/src/main/resources/static/favicons/apple-touch-icon.png type: application -version: 11.0.0+up6.17.0 +version: 11.1.0+up6.17.0 appVersion: 6.17.0 diff --git a/urlaubsverwaltung/templates/urlaubsverwaltung.networkpolicy.yaml b/urlaubsverwaltung/templates/urlaubsverwaltung.networkpolicy.yaml index 08f7513..306f00c 100644 --- a/urlaubsverwaltung/templates/urlaubsverwaltung.networkpolicy.yaml +++ b/urlaubsverwaltung/templates/urlaubsverwaltung.networkpolicy.yaml @@ -48,6 +48,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := .Values.ingress.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -82,6 +90,26 @@ spec: - ports: - protocol: TCP port: 8080 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -124,6 +152,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/urlaubsverwaltung/values.schema.json b/urlaubsverwaltung/values.schema.json index ed48c3e..6e2c098 100644 --- a/urlaubsverwaltung/values.schema.json +++ b/urlaubsverwaltung/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/urlaubsverwaltung/values.yaml b/urlaubsverwaltung/values.yaml index 977fa89..d756b88 100644 --- a/urlaubsverwaltung/values.yaml +++ b/urlaubsverwaltung/values.yaml @@ -262,6 +262,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From fb222b3d377a5ea2f0415d5739be1689692bd0b7 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:19 +0000 Subject: [PATCH 18/63] feat(voucher-vault): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- voucher-vault/Chart.yaml | 2 +- .../voucher-vault.networkpolicy.yaml | 52 +++++++++++++++++++ voucher-vault/values.schema.json | 11 ++++ voucher-vault/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/voucher-vault/Chart.yaml b/voucher-vault/Chart.yaml index c033cee..5913963 100644 --- a/voucher-vault/Chart.yaml +++ b/voucher-vault/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/l4rm4nd/VoucherVault/main/myapp/static/a type: application -version: 7.0.0+up1.30.3 +version: 7.1.0+up1.30.3 appVersion: "1.30.3" maintainers: diff --git a/voucher-vault/templates/voucher-vault.networkpolicy.yaml b/voucher-vault/templates/voucher-vault.networkpolicy.yaml index 51d98e7..07a645d 100644 --- a/voucher-vault/templates/voucher-vault.networkpolicy.yaml +++ b/voucher-vault/templates/voucher-vault.networkpolicy.yaml @@ -41,6 +41,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := .Values.ingress.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -75,6 +83,26 @@ spec: - ports: - protocol: TCP port: 8000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -117,6 +145,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/voucher-vault/values.schema.json b/voucher-vault/values.schema.json index cd9f3cd..0edddac 100644 --- a/voucher-vault/values.schema.json +++ b/voucher-vault/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/voucher-vault/values.yaml b/voucher-vault/values.yaml index c6eb3b7..4ab217f 100644 --- a/voucher-vault/values.yaml +++ b/voucher-vault/values.yaml @@ -338,6 +338,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 91e2e5093e60084cda94cdb5df327e79308748db Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:19 +0000 Subject: [PATCH 19/63] feat(ytdlp-web-player): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- ytdlp-web-player/Chart.yaml | 2 +- .../ytdlp-web-player.networkpolicy.yaml | 52 +++++++++++++++++++ ytdlp-web-player/values.schema.json | 11 ++++ ytdlp-web-player/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/ytdlp-web-player/Chart.yaml b/ytdlp-web-player/Chart.yaml index c1ee8c6..2ef22b4 100644 --- a/ytdlp-web-player/Chart.yaml +++ b/ytdlp-web-player/Chart.yaml @@ -5,7 +5,7 @@ icon: https://raw.githubusercontent.com/matszwe02/ytdlp_web_player/main/src/stat type: application -version: 2.0.0+upv1.2.0 +version: 2.1.0+upv1.2.0 appVersion: "v1.2.0" maintainers: diff --git a/ytdlp-web-player/templates/ytdlp-web-player.networkpolicy.yaml b/ytdlp-web-player/templates/ytdlp-web-player.networkpolicy.yaml index db24ced..f8c8549 100644 --- a/ytdlp-web-player/templates/ytdlp-web-player.networkpolicy.yaml +++ b/ytdlp-web-player/templates/ytdlp-web-player.networkpolicy.yaml @@ -51,6 +51,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -86,6 +94,26 @@ spec: - ports: - protocol: TCP port: 5000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -129,6 +157,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/ytdlp-web-player/values.schema.json b/ytdlp-web-player/values.schema.json index 57667d1..145490e 100644 --- a/ytdlp-web-player/values.schema.json +++ b/ytdlp-web-player/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/ytdlp-web-player/values.yaml b/ytdlp-web-player/values.yaml index 21a1bc6..b536845 100644 --- a/ytdlp-web-player/values.yaml +++ b/ytdlp-web-player/values.yaml @@ -524,6 +524,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From a04e47ac87f7b35d645d855ea07103b4cb828c08 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:19 +0000 Subject: [PATCH 20/63] feat(zipline): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- zipline/Chart.yaml | 3 +- zipline/templates/zipline.networkpolicy.yaml | 52 ++++++++++++++++++++ zipline/values.schema.json | 11 +++++ zipline/values.yaml | 51 +++++++++++++++++++ 4 files changed, 115 insertions(+), 2 deletions(-) diff --git a/zipline/Chart.yaml b/zipline/Chart.yaml index 77bbb18..d80c6ed 100644 --- a/zipline/Chart.yaml +++ b/zipline/Chart.yaml @@ -16,8 +16,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 8.0.0+up4.7.0 - +version: 8.1.0+up4.7.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to # follow Semantic Versioning. They should reflect the version the application is using. diff --git a/zipline/templates/zipline.networkpolicy.yaml b/zipline/templates/zipline.networkpolicy.yaml index 315b315..f6a85a2 100644 --- a/zipline/templates/zipline.networkpolicy.yaml +++ b/zipline/templates/zipline.networkpolicy.yaml @@ -42,6 +42,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := .Values.ingress.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -77,6 +85,26 @@ spec: - ports: - protocol: TCP port: 3000 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -119,6 +147,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/zipline/values.schema.json b/zipline/values.schema.json index 3ab4551..0fa9c02 100644 --- a/zipline/values.schema.json +++ b/zipline/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/zipline/values.yaml b/zipline/values.yaml index 28b97a8..2f19ecd 100644 --- a/zipline/values.yaml +++ b/zipline/values.yaml @@ -232,6 +232,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From 5b3400581fc0947e82a9b445bae3172e84f64867 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:20 +0000 Subject: [PATCH 21/63] feat(homeassistant): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- homeassistant/Chart.yaml | 3 +- .../homeassistant.networkpolicy.yaml | 52 +++++++++++++++++++ homeassistant/values.schema.json | 11 ++++ homeassistant/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 2 deletions(-) diff --git a/homeassistant/Chart.yaml b/homeassistant/Chart.yaml index 9804892..a36f326 100644 --- a/homeassistant/Chart.yaml +++ b/homeassistant/Chart.yaml @@ -4,8 +4,7 @@ description: A Helm chart to deploy Home Assistant on Kubernetes icon: https://www.home-assistant.io/images/home-assistant-logo.svg type: application -version: 7.0.0+up2026.8.3 - +version: 7.1.0+up2026.8.3 appVersion: "2026.8.3" maintainers: diff --git a/homeassistant/templates/homeassistant.networkpolicy.yaml b/homeassistant/templates/homeassistant.networkpolicy.yaml index 069404f..a43ae4e 100644 --- a/homeassistant/templates/homeassistant.networkpolicy.yaml +++ b/homeassistant/templates/homeassistant.networkpolicy.yaml @@ -56,6 +56,14 @@ selects. {{- $ns := .Release.Namespace }} {{- $probeSources := $np.probeSources | default list }} {{- $allowedNs := $np.allowedNamespaces | default list }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -86,6 +94,26 @@ spec: - ports: - protocol: TCP port: 8123 + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -110,6 +138,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/homeassistant/values.schema.json b/homeassistant/values.schema.json index d148ceb..ebe2611 100644 --- a/homeassistant/values.schema.json +++ b/homeassistant/values.schema.json @@ -14,6 +14,17 @@ "type": "object", "additionalProperties": false, "properties": { + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "enabled": { "type": "boolean" }, diff --git a/homeassistant/values.yaml b/homeassistant/values.yaml index 656bc78..7a191df 100644 --- a/homeassistant/values.yaml +++ b/homeassistant/values.yaml @@ -288,3 +288,54 @@ networkPolicy: # 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: [] + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + -- 2.54.0 From 19a2c5e4a5cfc4b30eb3bd1f427514ffb4392b09 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:07:40 +0000 Subject: [PATCH 22/63] test(networkpolicy): tie the ambient probe-source half to probeSources, not to a chart name Gitea #143. Five charts offer no networkPolicy.probeSources - their probes are exec probes and put no packet on the network - so for them 169.254.7.127/32 would be an ipBlock that matches nothing, which is exactly the strict-looking empty rule this script exists to catch. Assertion 7 now exempts them on the absence of that value, the same conditionality assertion 4 and the overlay guard already use, and records the exemption as a held assertion rather than as silence. Also names two limits of assertion 7 in its header: assertions 1, 3, 4 and 5 are not re-run on the switched-on render, and assertion 3's omission is the interesting one - with probeSources empty, 15008 sits in a rule with no "from" that neither of assertion 3's two justifications covers. Teaching it a third would mean writing a rule against a guess, because where ztunnel dials from cannot be settled until a namespace joins the mesh. --- ci/run-networkpolicy-test.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/ci/run-networkpolicy-test.sh b/ci/run-networkpolicy-test.sh index 19398fb..fd77156 100755 --- a/ci/run-networkpolicy-test.sh +++ b/ci/run-networkpolicy-test.sh @@ -1283,6 +1283,24 @@ done # test did not identify. A chart whose policy stops following the convention # fails here instead of silently losing its coverage. # +# TWO LIMITS OF THIS ASSERTION, named rather than left to be discovered: +# +# - ASSERTIONS 1, 3, 4 AND 5 ARE NOT RE-RUN ON THE SWITCHED-ON RENDER. Only +# assertion 2's subject matter is, because that is the one the exception +# touches. Assertion 3 is the interesting omission: with the switch on and +# probeSources empty, 15008 sits in a rule with no "from", and assertion 3's +# two justifications for an open rule - a network probe addresses the port, +# or a NodePort publishes it - cover neither. That is intended rather than +# overlooked. The port has to be reachable from wherever ztunnel dials from, +# and that address is not knowable from a render: ztunnel runs with its own +# pod IPs in istio-system (measured 2026-10-19, no hostNetwork on the +# DaemonSet, Istio 1.30.6), but whether a peer of its own is needed cannot +# be settled until a namespace actually joins the mesh. Teaching assertion 3 +# a third justification before then would be a rule written against a guess. +# - THE PROBE-SOURCE HALF IS CONDITIONAL ON THE CHART OFFERING probeSources, +# like assertion 4 and the overlay guard. A chart with exec-only probes has +# no probe on the network to redirect, so the ipBlock would match nothing. +# # BUILT INTO THIS SCRIPT rather than added as a new test with its own pipeline # job - the same decision as assertion 6 for #136, and here it is stronger than # convenience. The exception WEAKENS assertion 2, so it belongs next to it: a @@ -1551,9 +1569,26 @@ else: % (gate, PORT)) # The inseparability of the two halves, where the render can reach it. + # + # Conditional on the chart OFFERING probeSources, exactly as assertion 4 and + # the overlay guard are, and for the same reason: a chart with exec-only + # probes (valkey, minecraft-server) puts no probe packet on the network at + # all, so there is no probe to redirect and 169.254.7.127/32 would be an + # entry that matches nothing - the kind of strict-looking, empty rule this + # script exists to catch. Those charts render the port half only, and say so + # in their values.yaml. + offers_probe_sources = bool(re.search( + r"^\s*probeSources\s*:", values_yaml, re.M)) for name, i, rule in carriers: if not rule.get("from"): continue + if not offers_probe_sources: + ok("%s ingress[%d] carries port %d in a source-restricted rule and " + "no ipBlock %s - correct here, because this chart offers no " + "networkPolicy.probeSources: its probes put no packet on the " + "network, so there is no redirected probe to allow" + % (name, i, PORT, CIDR)) + continue if CIDR in cidrs_of(rule): ok("%s ingress[%d] carries port %d AND the ipBlock %s in the same " "\"from\" list - the two halves of the switch are inseparable, " -- 2.54.0 From 219dcc705d899b5eadc8ae46a7daa870d170ab9a Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:08:37 +0000 Subject: [PATCH 23/63] feat(garage): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- garage/Chart.yaml | 2 +- garage/templates/garage.networkpolicy.yaml | 52 ++++++++++++++++++++++ garage/values.schema.json | 11 +++++ garage/values.yaml | 51 +++++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/garage/Chart.yaml b/garage/Chart.yaml index a262667..8d6d0d6 100644 --- a/garage/Chart.yaml +++ b/garage/Chart.yaml @@ -19,7 +19,7 @@ type: application # offer those as "updates", which is why the garage block in renovate.json5 # carries a versioningTemplate anchored to ^v..$ - same # shape as the stalwart block, for the same reason. -version: 2.0.0+upv2.4.1 +version: 2.1.0+upv2.4.1 appVersion: "v2.4.1" maintainers: diff --git a/garage/templates/garage.networkpolicy.yaml b/garage/templates/garage.networkpolicy.yaml index f5a989b..0f79429 100644 --- a/garage/templates/garage.networkpolicy.yaml +++ b/garage/templates/garage.networkpolicy.yaml @@ -54,6 +54,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -140,6 +148,26 @@ spec: - ports: - protocol: TCP port: {{ $settings.ports.admin | int64 }} + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -167,6 +195,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/garage/values.schema.json b/garage/values.schema.json index 3248d4b..9572869 100644 --- a/garage/values.schema.json +++ b/garage/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/garage/values.yaml b/garage/values.yaml index 36c5069..80efe98 100644 --- a/garage/values.yaml +++ b/garage/values.yaml @@ -576,6 +576,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From ab09378e0a48a66a3888802025da29f78f6a3518 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:08:38 +0000 Subject: [PATCH 24/63] feat(keycloak): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- keycloak/Chart.yaml | 2 +- .../templates/keycloak.networkpolicy.yaml | 52 +++++++++++++++++++ keycloak/values.schema.json | 11 ++++ keycloak/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/keycloak/Chart.yaml b/keycloak/Chart.yaml index 5cafd20..7b8106a 100644 --- a/keycloak/Chart.yaml +++ b/keycloak/Chart.yaml @@ -43,7 +43,7 @@ type: application # procedure lives in NOTES.txt and on the wiki page instead of in a # packageRules entry. Read the "MINOR UPGRADES" section of NOTES.txt before # merging an app-version PR that changes the minor. -version: 2.0.0+up26.8.0 +version: 2.1.0+up26.8.0 appVersion: "26.8.0" maintainers: diff --git a/keycloak/templates/keycloak.networkpolicy.yaml b/keycloak/templates/keycloak.networkpolicy.yaml index e58887a..a391fee 100644 --- a/keycloak/templates/keycloak.networkpolicy.yaml +++ b/keycloak/templates/keycloak.networkpolicy.yaml @@ -81,6 +81,14 @@ selects. {{- $allowedNs := $np.allowedNamespaces | default list }} {{- $ic := $np.ingressController | default dict }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -166,6 +174,26 @@ spec: - ports: - protocol: TCP port: {{ $settings.ports.management | int64 }} + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -193,6 +221,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/keycloak/values.schema.json b/keycloak/values.schema.json index 2c56f73..f61bdc1 100644 --- a/keycloak/values.schema.json +++ b/keycloak/values.schema.json @@ -45,6 +45,17 @@ } } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/keycloak/values.yaml b/keycloak/values.yaml index c3fe0b3..d342bf3 100644 --- a/keycloak/values.yaml +++ b/keycloak/values.yaml @@ -967,6 +967,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From f911a5cddd170611b5a4156cdb0e06541c47438f Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:08:38 +0000 Subject: [PATCH 25/63] feat(minecraft-router): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- minecraft-router/Chart.yaml | 2 +- .../minecraft-router.networkpolicy.yaml | 52 +++++++++++++++++++ minecraft-router/values.schema.json | 11 ++++ minecraft-router/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/minecraft-router/Chart.yaml b/minecraft-router/Chart.yaml index bf555fc..7445877 100644 --- a/minecraft-router/Chart.yaml +++ b/minecraft-router/Chart.yaml @@ -5,7 +5,7 @@ icon: https://avatars.githubusercontent.com/u/988985 type: application -version: 5.1.0+up1.48.0 +version: 5.2.0+up1.48.0 appVersion: "1.48.0" maintainers: diff --git a/minecraft-router/templates/minecraft-router.networkpolicy.yaml b/minecraft-router/templates/minecraft-router.networkpolicy.yaml index 47e903a..2a19a8d 100644 --- a/minecraft-router/templates/minecraft-router.networkpolicy.yaml +++ b/minecraft-router/templates/minecraft-router.networkpolicy.yaml @@ -54,6 +54,14 @@ selects. {{- $ns := .Release.Namespace }} {{- $probeSources := $np.probeSources | default list }} {{- $allowedNs := $np.allowedNamespaces | default list }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -105,6 +113,26 @@ spec: - ports: - protocol: TCP port: {{ .Values.server.apiPort | int64 }} + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The kubelet's probes @@ -129,6 +157,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/minecraft-router/values.schema.json b/minecraft-router/values.schema.json index 51a8346..16d9363 100644 --- a/minecraft-router/values.schema.json +++ b/minecraft-router/values.schema.json @@ -31,6 +31,17 @@ "type": "string" } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/minecraft-router/values.yaml b/minecraft-router/values.yaml index 2ea58ef..3a61564 100644 --- a/minecraft-router/values.yaml +++ b/minecraft-router/values.yaml @@ -202,6 +202,57 @@ networkPolicy: # 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: [] + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + 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 -- 2.54.0 From fe573cc7627b2beb22a69bb3ced04a2317d180a7 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:08:38 +0000 Subject: [PATCH 26/63] feat(stalwart): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. --- stalwart/Chart.yaml | 2 +- .../templates/stalwart.networkpolicy.yaml | 52 +++++++++++++++++++ stalwart/values.schema.json | 11 ++++ stalwart/values.yaml | 51 ++++++++++++++++++ 4 files changed, 115 insertions(+), 1 deletion(-) diff --git a/stalwart/Chart.yaml b/stalwart/Chart.yaml index 2275b6d..a92fb7e 100644 --- a/stalwart/Chart.yaml +++ b/stalwart/Chart.yaml @@ -37,7 +37,7 @@ type: application # # Note for whoever bumps this next: the suffix tracks appVersion, so a major # here keeps the +up it was merged with. -version: 3.0.0+upv0.16.26 +version: 3.1.0+upv0.16.26 appVersion: "v0.16.26" maintainers: diff --git a/stalwart/templates/stalwart.networkpolicy.yaml b/stalwart/templates/stalwart.networkpolicy.yaml index 464f71e..718a94a 100644 --- a/stalwart/templates/stalwart.networkpolicy.yaml +++ b/stalwart/templates/stalwart.networkpolicy.yaml @@ -61,6 +61,14 @@ unenforced policy is accepted and filters nothing. {{- $probeSources := .Values.networkPolicy.probeSources | default list -}} {{- $allowedNs := .Values.networkPolicy.allowedNamespaces | default list -}} {{- $ic := .Values.networkPolicy.ingressController | default dict -}} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same thing + as networkPolicy.ingressController (the SOURCE SELECTOR for the ingress + controller's pods, a SIBLING of it and no part of it) nor as the + top-level .Values.ingress (the Ingress object). All three match a search + for "ingress"; one round of this work already mistook the second for an + inbound escape hatch on the strength of the substring. */}} +{{- $npIngress := .Values.networkPolicy.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} {{- range $group, $spec := fromYaml (include "stalwart.nodes" .) }} {{- $name := printf "%s-%s" (include "stalwart.fullname" $root) $group }} --- @@ -105,6 +113,26 @@ spec: - ports: - protocol: {{ $http.protocol | default "TCP" }} port: {{ int $http.containerPort }} + {{- if $ambientMesh }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP + is ztunnel's HBONE tunnel, the port every inbound connection arrives on + once the namespace carries istio.io/dataplane-mode - ambient "does not + and will never bypass Kubernetes NetworkPolicy enforcement", so without + it a pod in the mesh is simply unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh therefore holds a NAMED exception for exactly this number, gated + on exactly this value - and assertion 7 of that script proves, on a + second render, that 15008 stays the ONLY such port and that it is absent + while the value is false. + + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- if or $probeSources $allowedNs }} {{- /* Restricted because networkPolicy.probeSources or .allowedNamespaces is @@ -147,6 +175,30 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} + {{- if $ambientMesh }} + {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is + one value and not two. Inside the mesh the kubelet's probes are + redirected and no longer arrive from the worker node's address: they + arrive from 169.254.7.127. + + An operator who only opens 15008 by hand writes a policy that passes + review - and the moment probeSources is non-empty every probe is + dropped, the pod never becomes Ready, and the symptom reads as a broken + application rather than as a policy. That is assertion 4 of + ci/run-networkpolicy-test.sh one value further along. A switch whose + halves can only be had together cannot be set halfway. + + Only in this branch, because this is the only branch with a "from" list: + with no sources restricted the rule is open and needs no peer. The IPv6 + counterpart is deliberately absent - this cluster is SingleStack IPv4, + and an ipBlock of the wrong family is accepted and matches nothing. + + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + release reaches this branch. CI proves its render, through the overlays + in ci/networkpolicy. */}} + - ipBlock: + cidr: 169.254.7.127/32 + {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only inside the diff --git a/stalwart/values.schema.json b/stalwart/values.schema.json index 6a51942..27f1d8c 100644 --- a/stalwart/values.schema.json +++ b/stalwart/values.schema.json @@ -334,6 +334,17 @@ "minLength": 1 } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "description": "Egress is restricted by PORT, not by destination: a NetworkPolicy matches CIDRs and selectors and never hostnames, so no host value from this file can appear in it.", "type": "object", diff --git a/stalwart/values.yaml b/stalwart/values.yaml index f95a308..91e191c 100644 --- a/stalwart/values.yaml +++ b/stalwart/values.yaml @@ -541,6 +541,57 @@ networkPolicy: namespace: kube-system podLabels: app.kubernetes.io/name: rke2-traefik + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # IT RENDERS TWO THINGS, and that they come together is the point: + # + # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod + # whose namespace has joined the mesh is unreachable. + # 2. 169.254.7.127/32 as an additional peer - but ONLY where the rule + # restricts sources at all, which here means probeSources is non-empty. + # Inside the mesh the kubelet's probes are redirected and no longer come + # from the worker node's address. + # + # WHY ONE SWITCH AND NOT TWO: point 2 is the one nobody thinks of. Whoever + # adds "15008" to a port list by hand writes a policy that passes review, and + # the first time probeSources is narrowed every probe is dropped - the pod + # never becomes Ready and the symptom reads as a broken application rather + # than as a policy. A switch whose halves can only be had together cannot be + # set halfway, which is why this is a named intention and not a port list. + # + # TWO THINGS IT DOES NOT PROVE: + # - That 15008 alone is enough. That comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh - none carries + # istio.io/dataplane-mode yet. + # - That the probe-source half works. probeSources is empty in every chart + # here, so no release reaches that branch. CI proves its render, not its + # effect. + # + # Note where the port lands: it joins the rule that carries the probe port, + # so once probeSources restricts that rule, 15008 is restricted with it. + # Whether ztunnel needs a peer of its own is open - it runs with its own pod + # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio + # 1.30.6), so if one proves necessary it is expressible as a + # namespaceSelector plus podSelector and needs no unknowable node CIDR. + ambientMesh: false + # Egress is restricted BY PORT, not by destination, and the reason is a limit # of the object rather than laziness: a NetworkPolicy matches ipBlock CIDRs # and pod/namespace selectors, never hostnames — so dataStore.postgres.host -- 2.54.0 From 445ca36d7d0c7f0916afb0b8b04e51e29f51d1c1 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:11:37 +0000 Subject: [PATCH 27/63] feat(samba): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. Port half only: this chart offers no networkPolicy.probeSources - its probes put no packet on the network - so there is no redirected probe to allow and an ipBlock for one would match nothing while making the rule look like it did something. ci/run-networkpolicy-test.sh ties that exemption to the absence of probeSources, not to a chart name. --- samba/Chart.yaml | 3 +- samba/templates/samba.networkpolicy.yaml | 34 ++++++++++++++++++++++ samba/values.schema.json | 11 ++++++++ samba/values.yaml | 36 ++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 2 deletions(-) diff --git a/samba/Chart.yaml b/samba/Chart.yaml index 19f2e28..3080fb5 100644 --- a/samba/Chart.yaml +++ b/samba/Chart.yaml @@ -5,6 +5,5 @@ icon: https://avatars.githubusercontent.com/u/13281359 type: application -version: 8.0.0+up4.23.10 - +version: 8.1.0+up4.23.10 appVersion: 4.23.10 diff --git a/samba/templates/samba.networkpolicy.yaml b/samba/templates/samba.networkpolicy.yaml index 853ee04..fad813c 100644 --- a/samba/templates/samba.networkpolicy.yaml +++ b/samba/templates/samba.networkpolicy.yaml @@ -49,6 +49,11 @@ selects. */ -}} {{- if .Values.networkPolicy.enabled }} {{- $np := .Values.networkPolicy }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same + thing as the top-level .Values.ingress (the Ingress object) - both match + a search for "ingress". */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -90,6 +95,35 @@ spec: port: 445 - protocol: TCP port: 139 + {{- if $ambientMesh }} + {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's + HBONE tunnel, the port every inbound connection arrives on once the + namespace carries istio.io/dataplane-mode - ambient "does not and will + never bypass Kubernetes NetworkPolicy enforcement", so without it a pod + in the mesh is unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh holds a NAMED exception for exactly this number, gated on exactly + this value, and proves on a second render that it stays the only such + port and that it is absent while the value is false. + + THE PORT HALF ONLY, and that is not an omission. Elsewhere this switch + also adds 169.254.7.127/32 as a probe source, because inside the mesh the + kubelet's probes are redirected away from the worker node's address. + This chart offers no networkPolicy.probeSources at all, so the rule + carrying the probe port is never restricted by source - and an + unrestricted rule needs no probe address. Measured in the render: three + tcpSocket probes, so these ARE network probes - they are simply never + restricted here, and 445 is additionally published as a NodePort. + WHICH RULE IT JOINS: + the only rule this policy has, the one carrying 445 and 139. + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} egress: {{- /* DNS, unconditionally. There is no value to switch this off, and that is diff --git a/samba/values.schema.json b/samba/values.schema.json index 633eb17..34b2dcc 100644 --- a/samba/values.schema.json +++ b/samba/values.schema.json @@ -17,6 +17,17 @@ "enabled": { "type": "boolean" }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/samba/values.yaml b/samba/values.yaml index dd0ca82..5673954 100644 --- a/samba/values.yaml +++ b/samba/values.yaml @@ -128,6 +128,42 @@ networkPolicy: # 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 + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # It renders 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod whose + # namespace has joined the mesh is unreachable. + # + # ONE HALF SHORTER THAN ELSEWHERE, deliberately. In other charts this switch + # also adds 169.254.7.127/32 as a probe source, because inside the mesh the + # kubelet's probes are redirected away from the worker node's address. This + # chart has no networkPolicy.probeSources at all, so the rule carrying the + # probe port is never restricted by source - and an unrestricted rule needs + # no probe address. An ipBlock for one would match nothing while making the + # rule look like it did something, which is the kind of strict-looking empty + # rule ci/run-networkpolicy-test.sh exists to catch; that script ties the + # exemption to the absence of probeSources rather than to a chart name. + # + # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens, and no + # namespace carries istio.io/dataplane-mode yet. + ambientMesh: false + 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 -- 2.54.0 From bc580ee2916bdbadda9f5f4dccb5729179ebdd1a Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:11:37 +0000 Subject: [PATCH 28/63] feat(teamspeak-server): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. Port half only: this chart offers no networkPolicy.probeSources - its probes put no packet on the network - so there is no redirected probe to allow and an ipBlock for one would match nothing while making the rule look like it did something. ci/run-networkpolicy-test.sh ties that exemption to the absence of probeSources, not to a chart name. --- teamspeak-server/Chart.yaml | 2 +- .../teamspeak-server.networkpolicy.yaml | 38 +++++++++++++++++++ teamspeak-server/values.schema.json | 11 ++++++ teamspeak-server/values.yaml | 36 ++++++++++++++++++ 4 files changed, 86 insertions(+), 1 deletion(-) diff --git a/teamspeak-server/Chart.yaml b/teamspeak-server/Chart.yaml index 1ec1d89..c6b98bd 100644 --- a/teamspeak-server/Chart.yaml +++ b/teamspeak-server/Chart.yaml @@ -5,5 +5,5 @@ icon: https://avatars.githubusercontent.com/u/136759148 type: application -version: 6.0.0+up6.0.0.beta13.1 +version: 6.1.0+up6.0.0.beta13.1 appVersion: "6.0.0-beta13.1" diff --git a/teamspeak-server/templates/teamspeak-server.networkpolicy.yaml b/teamspeak-server/templates/teamspeak-server.networkpolicy.yaml index 771fb16..ad462ba 100644 --- a/teamspeak-server/templates/teamspeak-server.networkpolicy.yaml +++ b/teamspeak-server/templates/teamspeak-server.networkpolicy.yaml @@ -50,6 +50,11 @@ selects. {{- $np := .Values.networkPolicy }} {{- $ns := .Release.Namespace }} {{- $allowedNs := $np.allowedNamespaces | default list }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same + thing as the top-level .Values.ingress (the Ingress object) - both match + a search for "ingress". */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -96,6 +101,39 @@ spec: - ports: - protocol: TCP port: {{ .Values.server.fileTransferPort | int64 }} + {{- if $ambientMesh }} + {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's + HBONE tunnel, the port every inbound connection arrives on once the + namespace carries istio.io/dataplane-mode - ambient "does not and will + never bypass Kubernetes NetworkPolicy enforcement", so without it a pod + in the mesh is unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh holds a NAMED exception for exactly this number, gated on exactly + this value, and proves on a second render that it stays the only such + port and that it is absent while the value is false. + + THE PORT HALF ONLY, and that is not an omission. Elsewhere this switch + also adds 169.254.7.127/32 as a probe source, because inside the mesh the + kubelet's probes are redirected away from the worker node's address. + This chart offers no networkPolicy.probeSources at all, so the rule + carrying the probe port is never restricted by source - and an + unrestricted rule needs no probe address. Measured in the render: three + tcpSocket probes, so these ARE network probes - they are simply never + restricted here. + WHICH RULE IT JOINS: + the file-transfer rule, the policy's open TCP rule. The voice rule is + UDP and the rule on 10080 is restricted to the namespace. A policy is an + OR over its rules, so which rule carries the port decides only which + sources reach it - and an open TCP rule is the honest default while it + is unknown where ztunnel dials from. + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- /* 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 diff --git a/teamspeak-server/values.schema.json b/teamspeak-server/values.schema.json index 5db644f..af54a82 100644 --- a/teamspeak-server/values.schema.json +++ b/teamspeak-server/values.schema.json @@ -24,6 +24,17 @@ "type": "string" } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/teamspeak-server/values.yaml b/teamspeak-server/values.yaml index 13d6fb1..2375b1d 100644 --- a/teamspeak-server/values.yaml +++ b/teamspeak-server/values.yaml @@ -132,6 +132,42 @@ networkPolicy: # matches nothing and is silently ineffective. Note the granularity: an entry # opens the port to EVERY pod in that namespace. allowedNamespaces: [] + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # It renders 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod whose + # namespace has joined the mesh is unreachable. + # + # ONE HALF SHORTER THAN ELSEWHERE, deliberately. In other charts this switch + # also adds 169.254.7.127/32 as a probe source, because inside the mesh the + # kubelet's probes are redirected away from the worker node's address. This + # chart has no networkPolicy.probeSources at all, so the rule carrying the + # probe port is never restricted by source - and an unrestricted rule needs + # no probe address. An ipBlock for one would match nothing while making the + # rule look like it did something, which is the kind of strict-looking empty + # rule ci/run-networkpolicy-test.sh exists to catch; that script ties the + # exemption to the absence of probeSources rather than to a chart name. + # + # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens, and no + # namespace carries istio.io/dataplane-mode yet. + ambientMesh: false + 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 -- 2.54.0 From 4a7ee1036c32be91699c8f93f06903ea7dd30c7a Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:11:37 +0000 Subject: [PATCH 29/63] feat(minecraft-server): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. Port half only: this chart offers no networkPolicy.probeSources - its probes put no packet on the network - so there is no redirected probe to allow and an ipBlock for one would match nothing while making the rule look like it did something. ci/run-networkpolicy-test.sh ties that exemption to the absence of probeSources, not to a chart name. --- minecraft-server/Chart.yaml | 2 +- .../minecraft-server.networkpolicy.yaml | 69 +++++++++++++++++++ minecraft-server/values.schema.json | 11 +++ minecraft-server/values.yaml | 36 ++++++++++ 4 files changed, 117 insertions(+), 1 deletion(-) diff --git a/minecraft-server/Chart.yaml b/minecraft-server/Chart.yaml index 74e393d..291e8fb 100644 --- a/minecraft-server/Chart.yaml +++ b/minecraft-server/Chart.yaml @@ -23,7 +23,7 @@ type: application # 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 # image is now pinned by the chart version like every other chart in this repo. -version: 7.0.0+up2026.8.2.java25 +version: 7.1.0+up2026.8.2.java25 appVersion: "2026.8.2-java25" maintainers: diff --git a/minecraft-server/templates/minecraft-server.networkpolicy.yaml b/minecraft-server/templates/minecraft-server.networkpolicy.yaml index c71c01d..4e4795e 100644 --- a/minecraft-server/templates/minecraft-server.networkpolicy.yaml +++ b/minecraft-server/templates/minecraft-server.networkpolicy.yaml @@ -53,6 +53,11 @@ selects. {{- $np := .Values.networkPolicy }} {{- $ns := .Release.Namespace }} {{- $allowedNs := $np.allowedNamespaces | default list }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same + thing as the top-level .Values.ingress (the Ingress object) - both match + a search for "ingress". */}} +{{- $npIngress := $np.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -92,6 +97,38 @@ spec: - ports: - protocol: TCP port: {{ .Values.server.port | int64 }} + {{- if $ambientMesh }} + {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's + HBONE tunnel, the port every inbound connection arrives on once the + namespace carries istio.io/dataplane-mode - ambient "does not and will + never bypass Kubernetes NetworkPolicy enforcement", so without it a pod + in the mesh is unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh holds a NAMED exception for exactly this number, gated on exactly + this value, and proves on a second render that it stays the only such + port and that it is absent while the value is false. + + THE PORT HALF ONLY, and that is not an omission. Elsewhere this switch + also adds 169.254.7.127/32 as a probe source, because inside the mesh the + kubelet's probes are redirected away from the worker node's address. + This chart offers no networkPolicy.probeSources at all, so the rule + carrying the probe port is never restricted by source - and an + unrestricted rule needs no probe address. Measured in the render: three + exec probes, which put no packet on the network at all. + WHICH RULE IT JOINS: + the game-port rule, in BOTH of its branches. That rule is open when the + Service is a NodePort (players arrive from outside, already masqueraded) + and restricted to the namespace otherwise, and the two branches are + separate rules in this template - so the port is added twice, or it + would be missing from whichever branch a release happens to render. + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- end }} {{- if not (eq .Values.server.service.type "NodePort") }} {{- /* @@ -107,6 +144,38 @@ spec: - ports: - protocol: TCP port: {{ .Values.server.port | int64 }} + {{- if $ambientMesh }} + {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's + HBONE tunnel, the port every inbound connection arrives on once the + namespace carries istio.io/dataplane-mode - ambient "does not and will + never bypass Kubernetes NetworkPolicy enforcement", so without it a pod + in the mesh is unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh holds a NAMED exception for exactly this number, gated on exactly + this value, and proves on a second render that it stays the only such + port and that it is absent while the value is false. + + THE PORT HALF ONLY, and that is not an omission. Elsewhere this switch + also adds 169.254.7.127/32 as a probe source, because inside the mesh the + kubelet's probes are redirected away from the worker node's address. + This chart offers no networkPolicy.probeSources at all, so the rule + carrying the probe port is never restricted by source - and an + unrestricted rule needs no probe address. Measured in the render: three + exec probes, which put no packet on the network at all. + WHICH RULE IT JOINS: + the game-port rule, in BOTH of its branches. That rule is open when the + Service is a NodePort (players arrive from outside, already masqueraded) + and restricted to the namespace otherwise, and the two branches are + separate rules in this template - so the port is added twice, or it + would be missing from whichever branch a release happens to render. + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} from: - namespaceSelector: matchLabels: diff --git a/minecraft-server/values.schema.json b/minecraft-server/values.schema.json index 8816d55..2620365 100644 --- a/minecraft-server/values.schema.json +++ b/minecraft-server/values.schema.json @@ -24,6 +24,17 @@ "type": "string" } }, + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "egress": { "type": ["object", "null"], "additionalProperties": false, diff --git a/minecraft-server/values.yaml b/minecraft-server/values.yaml index bd1b6e7..34be1f1 100644 --- a/minecraft-server/values.yaml +++ b/minecraft-server/values.yaml @@ -352,6 +352,42 @@ networkPolicy: # matches nothing and is silently ineffective. Note the granularity: an entry # opens the port to EVERY pod in that namespace. allowedNamespaces: [] + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # It renders 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod whose + # namespace has joined the mesh is unreachable. + # + # ONE HALF SHORTER THAN ELSEWHERE, deliberately. In other charts this switch + # also adds 169.254.7.127/32 as a probe source, because inside the mesh the + # kubelet's probes are redirected away from the worker node's address. This + # chart has no networkPolicy.probeSources at all, so the rule carrying the + # probe port is never restricted by source - and an unrestricted rule needs + # no probe address. An ipBlock for one would match nothing while making the + # rule look like it did something, which is the kind of strict-looking empty + # rule ci/run-networkpolicy-test.sh exists to catch; that script ties the + # exemption to the absence of probeSources rather than to a chart name. + # + # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens, and no + # namespace carries istio.io/dataplane-mode yet. + ambientMesh: false + 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 -- 2.54.0 From 4c3e970bfebe53afe23c94dd6e18519ccd1fda3b Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:11:37 +0000 Subject: [PATCH 30/63] feat(valkey): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. Port half only: this chart offers no networkPolicy.probeSources - its probes put no packet on the network - so there is no redirected probe to allow and an ipBlock for one would match nothing while making the rule look like it did something. ci/run-networkpolicy-test.sh ties that exemption to the absence of probeSources, not to a chart name. --- valkey/Chart.yaml | 2 +- valkey/templates/valkey.networkpolicy.yaml | 42 ++++++++++++++++++++++ valkey/values.schema.json | 11 ++++++ valkey/values.yaml | 36 +++++++++++++++++++ 4 files changed, 90 insertions(+), 1 deletion(-) diff --git a/valkey/Chart.yaml b/valkey/Chart.yaml index a2109b6..df93a62 100644 --- a/valkey/Chart.yaml +++ b/valkey/Chart.yaml @@ -26,7 +26,7 @@ type: application # running image with no change in this repository and nothing reports it. The # remedy if that ever matters is pinDigests for this one image - see the valkey # block in renovate.json5. -version: 2.0.0+up9.1.2.alpine +version: 2.1.0+up9.1.2.alpine appVersion: "9.1.2-alpine" maintainers: diff --git a/valkey/templates/valkey.networkpolicy.yaml b/valkey/templates/valkey.networkpolicy.yaml index e72a2a1..03a1ff6 100644 --- a/valkey/templates/valkey.networkpolicy.yaml +++ b/valkey/templates/valkey.networkpolicy.yaml @@ -32,6 +32,11 @@ So this policy is what the rest of the rollout aims at and mostly cannot reach: a port that is genuinely closed to everything but the namespaces named below. */ -}} {{- if .Values.networkPolicy.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same + thing as the top-level .Values.ingress (the Ingress object) - both match + a search for "ingress". */}} +{{- $npIngress := .Values.networkPolicy.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -68,4 +73,41 @@ spec: ports: - protocol: TCP port: 6379 + {{- if $ambientMesh }} + {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's + HBONE tunnel, the port every inbound connection arrives on once the + namespace carries istio.io/dataplane-mode - ambient "does not and will + never bypass Kubernetes NetworkPolicy enforcement", so without it a pod + in the mesh is unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh holds a NAMED exception for exactly this number, gated on exactly + this value, and proves on a second render that it stays the only such + port and that it is absent while the value is false. + + THE PORT HALF ONLY, and that is not an omission. Elsewhere this switch + also adds 169.254.7.127/32 as a probe source, because inside the mesh the + kubelet's probes are redirected away from the worker node's address. + This chart offers no networkPolicy.probeSources at all, so the rule + carrying the probe port is never restricted by source - and an + unrestricted rule needs no probe address. Measured in the render: three + exec probes, which run inside the container through the kubelet's + runtime and put no packet on the network at all. + WHICH RULE IT JOINS: + the only rule this policy has, the one carrying 6379. That rule is + ALWAYS restricted to the release's own namespace plus allowedNamespaces, + so 15008 is restricted with it - and whether ztunnel is reached by those + peers is the open question of #143. ztunnel runs with its own pod IPs in + istio-system (measured 2026-10-19: no hostNetwork on the DaemonSet, + Istio 1.30.6), so if a peer of its own proves necessary it is + expressible as a namespaceSelector plus podSelector and not as an + unknowable node CIDR. It cannot be settled before a namespace joins the + mesh, and none has. + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- end }} diff --git a/valkey/values.schema.json b/valkey/values.schema.json index 888950b..3a09beb 100644 --- a/valkey/values.schema.json +++ b/valkey/values.schema.json @@ -108,6 +108,17 @@ "type": "object", "additionalProperties": false, "properties": { + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "enabled": { "description": "On by default. Only a real boundary where the cluster's CNI enforces NetworkPolicies - an unenforced policy is accepted by the API server and filters nothing.", "type": "boolean" diff --git a/valkey/values.yaml b/valkey/values.yaml index 76896f8..698703e 100644 --- a/valkey/values.yaml +++ b/valkey/values.yaml @@ -279,3 +279,39 @@ networkPolicy: # no typo protection: a namespace name that does not exist matches nothing and # is silently ineffective. allowedNamespaces: [] + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # It renders 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod whose + # namespace has joined the mesh is unreachable. + # + # ONE HALF SHORTER THAN ELSEWHERE, deliberately. In other charts this switch + # also adds 169.254.7.127/32 as a probe source, because inside the mesh the + # kubelet's probes are redirected away from the worker node's address. This + # chart has no networkPolicy.probeSources at all, so the rule carrying the + # probe port is never restricted by source - and an unrestricted rule needs + # no probe address. An ipBlock for one would match nothing while making the + # rule look like it did something, which is the kind of strict-looking empty + # rule ci/run-networkpolicy-test.sh exists to catch; that script ties the + # exemption to the absence of probeSources rather than to a chart name. + # + # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens, and no + # namespace carries istio.io/dataplane-mode yet. + ambientMesh: false + -- 2.54.0 From 1e28d1a99286fcf6df5321940f151bc747c9520e Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:11:37 +0000 Subject: [PATCH 31/63] feat(satisfactory): networkPolicy.ingress.ambientMesh Gitea #143. Additive, default false, so the render is byte-identical to the previous release - proven by diffing both renders against origin/main rather than asserted. Switched on it adds 15008/TCP, ztunnel's HBONE tunnel, to the rule carrying the probe port, and - inside that rule's source-restricted branch - 169.254.7.127/32, the address the kubelet's probes arrive from once the pod is in the mesh. Ambient does not bypass NetworkPolicy enforcement, so without the port a pod whose namespace has joined the mesh is unreachable. The two halves are one value because the second is the one nobody thinks of: 15008 added by hand leaves a policy that passes review and drops every probe the first time probeSources is narrowed, and a pod that never becomes Ready reads as a broken application rather than as a policy. Minor, not major: additive value, unchanged default, unchanged render. Port half only: this chart offers no networkPolicy.probeSources - its probes put no packet on the network - so there is no redirected probe to allow and an ipBlock for one would match nothing while making the rule look like it did something. ci/run-networkpolicy-test.sh ties that exemption to the absence of probeSources, not to a chart name. --- satisfactory/Chart.yaml | 2 +- .../templates/satisfactory.networkpolicy.yaml | 35 ++++++++++++++++++ satisfactory/values.schema.json | 11 ++++++ satisfactory/values.yaml | 36 +++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) diff --git a/satisfactory/Chart.yaml b/satisfactory/Chart.yaml index d30ef8f..c6912e9 100644 --- a/satisfactory/Chart.yaml +++ b/satisfactory/Chart.yaml @@ -3,7 +3,7 @@ name: satisfactory description: A Helm chart to deploy a Satisfactory Gameserver on Kubernetes icon: https://raw.githubusercontent.com/wolveix/satisfactory-server/main/.github/logo.png type: application -version: 8.0.0+upv1.9.10 +version: 8.1.0+upv1.9.10 appVersion: "v1.9.10" maintainers: - name: jklein \ No newline at end of file diff --git a/satisfactory/templates/satisfactory.networkpolicy.yaml b/satisfactory/templates/satisfactory.networkpolicy.yaml index 4278d78..42d129f 100644 --- a/satisfactory/templates/satisfactory.networkpolicy.yaml +++ b/satisfactory/templates/satisfactory.networkpolicy.yaml @@ -57,6 +57,11 @@ every port here against the containerPorts of the pod spec this policy selects. */ -}} {{- if .Values.networkPolicy.enabled }} +{{- /* networkPolicy.ingress - the policy's INBOUND side, and not the same + thing as the top-level .Values.ingress (the Ingress object) - both match + a search for "ingress". */}} +{{- $npIngress := .Values.networkPolicy.ingress | default dict }} +{{- $ambientMesh := $npIngress.ambientMesh }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -98,4 +103,34 @@ spec: port: {{ include "satisfactory.formatNumber" .Values.server.gamePort }} - protocol: TCP port: {{ include "satisfactory.formatNumber" .Values.server.messagingPort }} + {{- if $ambientMesh }} + {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's + HBONE tunnel, the port every inbound connection arrives on once the + namespace carries istio.io/dataplane-mode - ambient "does not and will + never bypass Kubernetes NetworkPolicy enforcement", so without it a pod + in the mesh is unreachable. + + It is the one port here that is NOT a containerPort of the selected pod + spec: ztunnel terminates it, not the application. ci/run-networkpolicy- + test.sh holds a NAMED exception for exactly this number, gated on exactly + this value, and proves on a second render that it stays the only such + port and that it is absent while the value is false. + + THE PORT HALF ONLY, and that is not an omission. Elsewhere this switch + also adds 169.254.7.127/32 as a probe source, because inside the mesh the + kubelet's probes are redirected away from the worker node's address. + This chart offers no networkPolicy.probeSources at all, so the rule + carrying the probe port is never restricted by source - and an + unrestricted rule needs no probe address. The chart's networkPolicy + block has nothing but enabled. Measured in the render: two exec probes + and one tcpSocket. + WHICH RULE IT JOINS: + the only rule this policy has, the one carrying the game and messaging + ports. + NOT MEASURED: that 15008 alone suffices comes from Istio's + documentation. Measured on this cluster is only which listeners ztunnel + opens. Provable at the first namespace to join the mesh; none has. */}} + - protocol: TCP + port: 15008 + {{- end }} {{- end }} diff --git a/satisfactory/values.schema.json b/satisfactory/values.schema.json index c34ae43..1b15f1c 100644 --- a/satisfactory/values.schema.json +++ b/satisfactory/values.schema.json @@ -14,6 +14,17 @@ "type": "object", "additionalProperties": false, "properties": { + "ingress": { + "description": "The policy's INBOUND side. Three names here overlap as substrings and are not the same thing: the top-level ingress is the Ingress OBJECT this chart renders, networkPolicy.ingress is this block, and networkPolicy.ingressController is the SOURCE SELECTOR for the ingress controller's pods - a SIBLING of this block and no part of it.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "ambientMesh": { + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["boolean", "null"] + } + } + }, "enabled": { "type": "boolean" } diff --git a/satisfactory/values.yaml b/satisfactory/values.yaml index b056b27..31fe972 100644 --- a/satisfactory/values.yaml +++ b/satisfactory/values.yaml @@ -185,3 +185,39 @@ networkPolicy: # as both - a TCP-only rule would be accepted and would drop half the game # traffic. enabled: true + + # The policy's INBOUND side. Mind the three names that overlap as substrings, + # because a search for "ingress" matches all of them and they are different + # things: + # + # ingress (top level) the Ingress OBJECT this chart + # renders - host, TLS, className + # networkPolicy.ingress this block: what the policy lets IN + # networkPolicy.ingressController the SOURCE SELECTOR for the ingress + # controller's pods. A SIBLING of this block + # and NO PART OF IT, despite the name. + ingress: + # Opens the pod for Istio's ambient mesh (Gitea #143). OFF by default and + # additive: while it is false this chart renders byte for byte as it did + # before the value existed. + # + # It renders 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh + # connection arrives on it, and ambient "does not and will never bypass + # Kubernetes NetworkPolicy enforcement", so without this port a pod whose + # namespace has joined the mesh is unreachable. + # + # ONE HALF SHORTER THAN ELSEWHERE, deliberately. In other charts this switch + # also adds 169.254.7.127/32 as a probe source, because inside the mesh the + # kubelet's probes are redirected away from the worker node's address. This + # chart has no networkPolicy.probeSources at all, so the rule carrying the + # probe port is never restricted by source - and an unrestricted rule needs + # no probe address. An ipBlock for one would match nothing while making the + # rule look like it did something, which is the kind of strict-looking empty + # rule ci/run-networkpolicy-test.sh exists to catch; that script ties the + # exemption to the absence of probeSources rather than to a chart name. + # + # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; + # measured on this cluster is only which listeners ztunnel opens, and no + # namespace carries istio.io/dataplane-mode yet. + ambientMesh: false + -- 2.54.0 From f2fda15e571f5e45e33e2c34f35e1de8bb494191 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 13:14:48 +0000 Subject: [PATCH 32/63] docs(README): the ambient-mesh switch and the named exception it costs the render test Gitea #143. Section 15 gets the switch itself - what it renders, why its two halves are one value, and the two things it does not prove - plus the three names that overlap as substrings (ingress, networkPolicy.ingress, networkPolicy.ingressController), because a search for "ingress" matches all three and one run during this work mistook the third for an inbound escape hatch on that basis alone. The render-test section gets assertion 7 as a table row, the re-measured figures (30 charts, 333 assertions, ~25s), and the six fail paths that were executed - including the two that are only visible to it: an ungated ipBlock, which assertion 2 cannot see because it never looks at "from" peers, and a value declared in the schema but missing from values.yaml, which a --set render alone would accept. --- README.md | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78c0399..f1a3b95 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,7 @@ Measured locally: **30 charts, 408 scalars verified, 0 failed**, no cluster. The | no ingress rule allows every source by accident | `from: []` allows everything, identically to having no `from` at all, while reading in a diff and in `kubectl` output as a restriction — the one mistake in this class that makes a policy *weaker* while looking *stricter* | nobody | | every port a **network** probe addresses is either unrestricted, or the chart offers `networkPolicy.probeSources` **and** says in `NOTES.txt` what a wrong CIDR costs | the kubelet probes from the IP of the worker node running the pod, which no chart can know. A restricted probe port without that address drops every probe: the pod never becomes `Ready`, the rollout times out, and the symptom reads as a broken application | the install-test, in production's CNI — **but not in CI**, see below | | **cross-chart egress**: where a chart dials another chart *of this repository*, its egress rule must contain the **pod** ports behind that chart's Services (#136) | an egress rule naming the **Service** port. kube-proxy does the ClusterIP's destination NAT *before* the policy is evaluated at the pod's interface, so the policy sees `podIP:targetPort` and the rule matches nothing wherever `port` and `targetPort` differ | nobody: the symptom is a **timeout**, not `connection refused`, and no error mentions a NetworkPolicy — `paperless-ngx` 14.0.0 shipped it and cost an outage on 2026-10-15 | +| **the ambient-mesh switch**, on two renders per chart: with `networkPolicy.ingress.ambientMesh` off, neither `15008` nor `169.254.7.127/32` appears; with it on, `15008` is present and is still the **only** ingress port that is no `containerPort`, and a source-restricted rule carrying it also carries the `ipBlock` (#143) | the named exception decaying into the hole it was carved around. Without the off-half a chart could render `15008` unconditionally and nothing would notice; without the only-port half the exception becomes the free port key that was rejected, and every mistyped ingress port passes with the switch on | nobody — and in production as a pod unreachable in the mesh, or one whose every probe is dropped the first time `probeSources` is narrowed | Egress ports are otherwise **deliberately not** held against the pod's `containerPort`s: an egress port is a port on the *destination*, and that comparison would fail every chart that reaches a database. @@ -245,7 +246,9 @@ Four things to know before touching it: - **Subchart documents are skipped by the comment assertion**, for a structural reason and not a lenient one: a dependency is fetched from the Helm registry rather than from the copy in this repo, so the policy `paperless-ngx` and `stalwart` bundle is whatever `valkey` version is *published*, and nothing in the parent chart can change its rendered comments. The subchart's own PR is where it is caught, because this test runs over `valkey` too. - **Anti-vacuum guard**, as in the tests above: `ci/networkpolicy/.yaml` is a render-only overlay applied **last**, whose job is to render the **source-restricted probe rule** — a branch no chart's own defaults ever reach, and the one containing the expensive mistake (a `namespaceSelector` and a `podSelector` written as two list entries instead of one would be an OR, opening the port to every pod in `kube-system`). A chart that *has* an overlay and still renders no restricted **probe** rule fails. That counter is specifically the probe rule and not "any restricted rule", because `keycloak` restricts two rules in its own defaults and would have satisfied a looser guard while the branch went unrendered. -Measured locally: **30 charts, 210 assertions held, 0 failed**, no cluster, ~15 seconds. All twelve fail paths were exercised rather than assumed — and one earned its keep immediately: the `probeSources` check was a substring test, so a chart whose key had been **deleted** while the long comment above it still discussed the value passed it. It now looks for the key. The cross-chart assertion added two of those 210, and its own six fail paths were executed the same way: a required port removed from the rule, a wrong number in its place, a peer chart renamed away, the policy's name no longer following the `--` convention, the peers rendering no Service at all (which would make the requirement empty, and is treated as a failure because an empty requirement reads exactly like a satisfied one), and the gated rule made unconditional — which is the failure path of the *gate-off* half, since with the gate off and the rule properly gated the assertion must stay silent. +Measured locally: **30 charts, 333 assertions held, 0 failed**, no cluster, ~25 seconds (the ambient-mesh assertion added a second render per chart, and roughly 10 seconds with it). All twelve fail paths were exercised rather than assumed — and one earned its keep immediately: the `probeSources` check was a substring test, so a chart whose key had been **deleted** while the long comment above it still discussed the value passed it. It now looks for the key. The cross-chart assertion added two of those 210, and its own six fail paths were executed the same way: a required port removed from the rule, a wrong number in its place, a peer chart renamed away, the policy's name no longer following the `--` convention, the peers rendering no Service at all (which would make the requirement empty, and is treated as a failure because an empty requirement reads exactly like a satisfied one), and the gated rule made unconditional — which is the failure path of the *gate-off* half, since with the gate off and the rule properly gated the assertion must stay silent. + +**The ambient-mesh assertion (#143) was built the same way, and six of its fail paths were executed**: a second, different port added under the exception (rejected — the exception covers exactly one number); `15008` rendered ungated, caught by assertion 2 in the main loop, which renders the switch at its default; the `ipBlock` rendered ungated, which **only** this assertion can catch, because assertion 2 never looks at `from` peers; the `ipBlock` half deleted while `15008` stayed, i.e. the half-set switch the design exists to make impossible, reachable because `ci/networkpolicy/template-chart.yaml` fills `probeSources`; the policy's name taken off the `--` convention; and the value present in the schema but absent from `values.yaml`, which a `--set` render alone would not have noticed. The name check earned its keep on the first run rather than in a rehearsal: it read the **directory** name, and `template-chart`'s chart is called `xxx`. **What this test does not and cannot cover, named so the gap is not mistaken for coverage:** that traffic still flows. No ingress controller runs in the install-test cluster, so the path from Traefik to a pod is proven nowhere in this pipeline — and, measured on the PR for #5, **k3s exempts the kubelet's probe traffic from policy**, so a blocked probe path does *not* turn the install-test red either (`cyberchef` became `Ready` in 69s with its probe port restricted to an address no probe can come from). What the install-test *does* prove is enforcement of everything the application itself needs: with `outline`'s DNS egress rule removed, its pod never became `Ready` and the job failed after 5m59s. So the net under this convention is real, but it is an **egress** net — which raises the stakes for assertion 2, since a wrong ingress port is caught mechanically here or not at all. @@ -693,7 +696,9 @@ Every port of a workload falls into one of four, and this is the part to get rig #### Values -`networkPolicy.enabled`, `.allowedNamespaces`, `.probeSources`, `.ingressController`, `.egress.extraPorts` — and a chart ships **only the ones that reach a rule**. A chart may add a **named** egress switch where a port depends on something the values cannot see: `paperless-ngx`' `.egress.mailImport` opens IMAPS `993`, off by default, because its mail accounts are rows in its own database and no value says whether a release fetches mail. The bar for one of those: it names what it opens, and it is **not** a second switch for a port some *other* value already decides — that is why the Tika/Gotenberg rule stayed gated on `paperless.files.tikaGotenberg.enabled` and got no policy switch of its own. Two values that can drift apart re-arm the outage the rule was fixed for. A value that silently does nothing is worse than one that is absent, because it reads as a control: `samba` and `satisfactory` carry `enabled` alone (every inbound rule is open to any source), and `teamspeak-server` has `allowedNamespaces` but no `probeSources`. +`networkPolicy.enabled`, `.allowedNamespaces`, `.probeSources`, `.ingressController`, `.ingress.ambientMesh`, `.egress.extraPorts` — and a chart ships **only the ones that reach a rule**, with `.enabled` and `.ingress.ambientMesh` the two every chart has. + +**Three of those names overlap as substrings and are three different things**, which is worth stating because a search for `ingress` matches all of them: top-level `ingress` is the Ingress **object** a chart renders, `networkPolicy.ingress` is the policy's **inbound side**, and `networkPolicy.ingressController` is the **source selector** for the controller's pods — a *sibling* of `networkPolicy.ingress` and no part of it. The 21 charts carrying `ingressController` had **no** inbound escape hatch before #143, and one search run during that work reported them as having one on the strength of the substring alone. A chart may add a **named** egress switch where a port depends on something the values cannot see: `paperless-ngx`' `.egress.mailImport` opens IMAPS `993`, off by default, because its mail accounts are rows in its own database and no value says whether a release fetches mail. The bar for one of those: it names what it opens, and it is **not** a second switch for a port some *other* value already decides — that is why the Tika/Gotenberg rule stayed gated on `paperless.files.tikaGotenberg.enabled` and got no policy switch of its own. Two values that can drift apart re-arm the outage the rule was fixed for. A value that silently does nothing is worse than one that is absent, because it reads as a control: `samba` and `satisfactory` carry `enabled` alone (every inbound rule is open to any source), and `teamspeak-server` has `allowedNamespaces` but no `probeSources`. **No chart ships `allowedNamespaces` pre-filled, ever.** A chart that named `mc-router`, or `paperless-jk`, or `stalwart` in its defaults would be baking in an assumption about *this* cluster's namespace layout — which is exactly the kind of claim a chart cannot keep true. So the default is the empty list and the chart is **restrictive**; filling it is the **deployment adaptation**, made where the namespace layout is actually visible. That is why a sweep like this produces a list of release-side one-liners: those are the expected procedure, not a defect list, and the breaking-change major on each chart is what announces them. @@ -701,6 +706,25 @@ Every port of a workload falls into one of four, and this is the part to get rig `ingressController` is a separate value with two keys rather than a line in `allowedNamespaces` for one reason: **its two selectors render as one `from` entry, which is an AND** — those pods in that namespace. Written as two list entries they would be an OR, and putting `kube-system` into `allowedNamespaces` instead would open the port to every pod in the cluster's own namespace. Its default is **measured, not assumed**: `ingress-nginx` is gone from this cluster, the only `IngressClass` is `traefik`, and the controller is the DaemonSet `rke2-traefik` in `kube-system` — which also serves the Gateway API here, so one source covers the Ingress and the HTTPRoute path alike. `ingress.className` says nothing about it: a class names an implementation, not the pods the traffic comes from. +#### The ambient-mesh switch, and the one named exception in the render test (#143) + +**`networkPolicy.ingress.ambientMesh`, in all 30 charts, default `false`.** Istio runs on this cluster in ambient mode, and Istio's own documentation is explicit that *"ambient does not and will never bypass Kubernetes `NetworkPolicy` enforcement"* — so until this value existed, **no chart here could put a pod into the mesh at all**. Additive, unchanged default, unchanged render: **minor** per chart, and the render was diffed against the previous release per chart rather than asserted. + +**It renders two things, and that they are one value is the whole design.** + +1. **`15008/TCP` inbound** — ztunnel's HBONE tunnel, where every inbound mesh connection arrives. +2. **`169.254.7.127/32` as an additional peer**, but only where the rule restricts sources at all. Inside the mesh the kubelet's probes are **redirected** and no longer arrive from the worker node's address. + +Point 2 is the one nobody thinks of, which is why the two halves cannot be had separately. An operator who adds `15008` to a port list by hand writes a policy that passes review — and the first time `probeSources` is narrowed, every probe is dropped, the pod never becomes `Ready`, and the symptom reads as a broken application rather than as a policy. That is the probe-path trap of this section one value further along. **A switch whose halves can only be had together cannot be set halfway**, so this is a named intention and not a port list. Charts with no `probeSources` at all render the port half only: their probe rule is never source-restricted, and an `ipBlock` for a redirected probe would match nothing while making the rule read as if it did something. + +**The interesting part is the CI, because the obvious implementation was the worst one.** Assertion 2 of `ci/run-networkpolicy-test.sh` holds every ingress port against the selected pod spec's `containerPort`s, and `15008` is deliberately none — ztunnel terminates it, not the application. A free `ingress.extraPorts` key would have had to exempt *operator-supplied ports* from that assertion, which opens it for **all 30 charts**: every mistyped ingress port, the NodePort mistake included, would then pass. So the assertion keeps its edge and gets **one named exception for one port number, gated on one value** — and it is not granted in the main loop at all, which renders every chart with the value at its default, so `15008` stays an ordinary failure there. + +**An exception without its own check is the hole it was carved around**, so assertion 7 holds it to five things on **two renders of every chart**: with the switch off, neither `15008` nor the `ipBlock` is present; with it on, `15008` is present, `15008` is still the **only** ingress port that is not a `containerPort`, and wherever the rule carrying it restricts sources it carries the `ipBlock` too. The policy is found by the `--` name convention, because an exception must not be granted to an object the test could not identify. It lives in the existing script rather than a new one for the reason #136's cross-chart assertion does, only more so: **the exception weakens assertion 2, so it belongs beside it** — a separate script can be green while the exception it is paired with is wrong, and nothing would connect the two. + +**Two things this does not prove, and they are not footnotes.** That `15008` *alone* suffices comes from Istio's documentation; what is measured on this cluster is only which listeners ztunnel opens. And the probe-source half reaches no release, because `probeSources` is empty in every chart (`stalwart` sets it to `[]` explicitly) — CI proves its render through the overlays in `ci/networkpolicy`, not its effect. Both become provable at the first namespace to join the mesh, and **none carries `istio.io/dataplane-mode`** (measured 2026-10-19, 0 of 97). Whether ztunnel needs a source selector of its own is open for the same reason — but it is *not* the unknowable case it looked like: ztunnel runs with its own pod IPs in `istio-system` (no `hostNetwork` on the DaemonSet, Istio 1.30.6), so a peer for it would be a `namespaceSelector` plus `podSelector`, not a node-IP `ipBlock`. + +**Consumer side:** switching this on for a release is a value key *and* a chart version in **one** PR in `jk-cluster-config`, because Fleet does not retry a failed apply — and `networkPolicy` carries `additionalProperties: false` in all 30 charts, so an unknown key does not get ignored, it fails the render. + #### What these policies do not do — and it belongs in every chart, not just here - **Egress is coarse: by port, never by destination.** A `NetworkPolicy` matches `ipBlock` CIDRs and pod/namespace selectors and **never hostnames**, so "only to the database host" is not expressible 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 every chart to Calico — the same argument that kept implementation-specific objects out of the Gateway-API sweep (#114) — so it is **not used**. This is a limit of the object, and each chart names what it costs *that* chart. -- 2.54.0 From 22977c183f0730229125552401b51d65b7b5395f Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:48:40 +0000 Subject: [PATCH 33/63] feat(template-chart)!: ambientMesh becomes an object, and the CI exception gets a pinned expectation Gitea #143, maintainer's instruction. Both numbers this switch renders are ISTIO CONSTANTS rather than standards, and a value with a documented default is the better home for a vendor constant than a template literal: an operator can correct it when Istio changes the number, without waiting for a chart release. networkPolicy: ingress: ambientMesh: enabled: false port: 15008 redirectedProbeSources: - 169.254.7.127/32 One object under one switch, so the coupling argument survives the change instead of being undermined by it - two separate values could drift apart, which is the thing this design exists to prevent. redirectedProbeSources is a LIST because a dual-stack cluster needs BOTH of Istio's addresses at once, not one of two; that is a nameable case rather than speculation, so it does not contradict reason 4 of the issue. Istio's IPv6 counterpart is documented as the dual-stack addition, with the reason it is absent today unchanged: SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. Named redirectedProbeSources, not probeSources, because the confusion risk with networkPolicy.probeSources one level up is larger than the ingress/ ingressController one: that value holds YOUR worker node CIDRs, is empty by default and means OPEN when empty; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. The distinction is written where both appear. PROVENANCE, which the previous round explained the coupling of but never attributed. 15008 is Istio's HBONE port - not IANA-registered for this, not prescribed by any protocol, unknown to Kubernetes. 169.254.7.127 is link-local (169.254.0.0/16, RFC 3927, never routed - the range one picks vendor magic out of); Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, which is what vendor constants look like; and Kubernetes knows the address in neither the NetworkPolicy API nor the kubelet - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6. If either changes, the failure is SILENT: a pod unreachable in the mesh, or probes dropped where probeSources is non-empty, which is nowhere today. NULL-SAFE, per #99 and because #136 shipped this exact defect one value away (its NOTES.txt branch dereferenced .networkPolicy.egress directly): every read goes through a fallback reproducing the chart's own declared defaults, so a deleted object or one carrying only "enabled: true" renders the defaults rather than failing. Clearing the probe-source list restores the constant instead of removing the ipBlock, deliberately - the halves are not separable, and enabled: false is how you open nothing. THE CONSEQUENCE, and it is the point of the CI half. With the port configurable, the exception can no longer be checked against the rendered value: "whatever the value says" is design A's hole wearing design C's name, and a mistyped port would pass by sitting in the configurable field. So the expectation is PINNED IN THE TEST - AMBIENT_PORT and AMBIENT_CIDR - and assertion 7 now checks two things instead of one: that the render at chart defaults produces exactly that port and that ipBlock, and that every chart's values.yaml DECLARES the same defaults, read back through ci/yaml2json rather than by regex. A chart that drifts is red. Changing an Istio constant means touching 30 values files AND the pinned lines, and the pinned lines are what force someone to look. That block is therefore also the single place saying what we hold Istio's numbers to be, so it carries the provenance too. The renders set only enabled=true and never a port override, on purpose: the pin is a statement about the defaults we ship, not about what a release may choose - an operator overriding a value is outside this test's reach, as for every value here. The declared-defaults check is conditional on the chart offering probeSources, like the inseparability check and assertion 4: the five charts whose probe rule is never source-restricted render no ipBlock, so for them the key must be ABSENT - a value reaching no rule reads as a control that does nothing - and that absence is asserted rather than tolerated. Their schemas drop it too. No version change: nothing of this is published, the switch stays additive with its default off, and the render at defaults is byte-identical - re-diffed per chart against origin/main, not assumed. --- ci/run-networkpolicy-test.sh | 187 ++++++++++++++---- .../templates/xxx.networkpolicy.yaml | 116 ++++++++--- template-chart/values.schema.json | 24 ++- template-chart/values.yaml | 129 ++++++++---- 4 files changed, 358 insertions(+), 98 deletions(-) diff --git a/ci/run-networkpolicy-test.sh b/ci/run-networkpolicy-test.sh index fd77156..f79c7a3 100755 --- a/ci/run-networkpolicy-test.sh +++ b/ci/run-networkpolicy-test.sh @@ -1261,17 +1261,26 @@ done # around, so the exception is held to five things at once, on TWO renders of # every chart: # +# the chart's DECLARED DEFAULTS (read back through ci/yaml2json) +# - enabled must default to false, and the port and the probe source must be +# exactly the numbers pinned below. A chart whose default drifted would +# otherwise ship a port no other chart ships while the exception kept being +# granted to the pinned one. # switch OFF (the main loop's render, reused) -# - 15008 must be ABSENT. Without this half the exception is a hole: a +# - the pinned port must be ABSENT. Without this half the exception is a hole: a # chart could render the port unconditionally and nothing would notice, # because assertion 7 would find it present and assertion 2 is where it # would have failed. # - 169.254.7.127/32 must be absent too, for the same reason. -# switch ON -# - 15008/TCP must be PRESENT, or the value is decorative. -# - 15008 must be the ONLY ingress port that is not a containerPort. This is -# the line between a named exception and design A of #143: every other -# mistyped or NodePort number still fails here, with the switch on. +# switch ON (only "enabled=true" is set, never a port override - the pin is a +# statement about the defaults WE ship) +# - the PINNED port must be PRESENT, or the value is decorative. +# - the PINNED port must be the ONLY ingress port that is not a +# containerPort. This is the line between a named exception and design A +# of #143: every other mistyped or NodePort number still fails here, with +# the switch on. Note "pinned": since #143 made the port a VALUE, checking +# the exception against the rendered value would permit whatever the value +# said, which is design A's hole under design C's name. # - wherever the rule carrying 15008 restricts sources, that rule's "from" # must contain 169.254.7.127/32. This is the inseparability of the two # halves, asserted rather than trusted - and it is REACHABLE, because @@ -1314,6 +1323,45 @@ done # mechanism as assertion 0: the mistake arrives by copy into a NEW chart whose # own diff nobody reads for it, which is exactly how garage (#81) and keycloak # (#83) arrived with no policy at all after #5 had been filed. +# THE PINNED EXPECTATION, and it is pinned HERE rather than read from the chart +# on purpose (Gitea #143). Both numbers are values with documented defaults, so +# that an operator can correct an Istio constant without waiting for a chart +# release - but an exception that permits "whatever the value says" is design A's +# hole wearing design C's name: a mistyped port would pass simply by sitting in +# the configurable field, and that is the single reason design A was rejected. +# +# So the test states what WE believe Istio's numbers to be, and asserts two +# things against that statement: the render at CHART DEFAULTS produces exactly +# this port and exactly this ipBlock, AND every chart's values.yaml DECLARES +# these same defaults. A chart that drifts is red. +# +# Which makes this block the one place in the repository that says what we hold +# Istio's constants to be - and therefore also the answer to "where does +# 169.254.7.127 come from": +# +# 15008 Istio's HBONE port. Not registered with IANA for this, +# prescribed by no protocol, unknown to Kubernetes. Istio +# picked it and Istio can change it. +# 169.254.7.127/32 the address Istio's data plane rewrites the kubelet's probe +# source onto. LINK-LOCAL (169.254.0.0/16, RFC 3927), a range +# reserved for node-local use and never routed. Istio's IPv6 +# counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries +# the SAME DIGITS plus 1337, which is what vendor constants +# look like. Neither the NetworkPolicy API nor the kubelet has +# any notion of it. +# +# BOTH READ FOR ztunnel 1.30.6. If Istio changes either, the failure is silent - +# a pod unreachable in the mesh, or probes dropped where probeSources is +# non-empty, which is nowhere today. Changing them means 30 values files AND +# these two lines, and these two lines are what force someone to look. +# +# An operator who overrides the value in their own release is outside this test's +# reach. That is true of every value in this repository and is not a new gap - +# and it is why these renders set ONLY "enabled=true" and never a port override: +# the pin is a statement about the defaults WE ship, not about what a release may +# choose. +AMBIENT_PORT=15008 +AMBIENT_CIDR="169.254.7.127/32" AMBIENT_VALUE="networkPolicy.ingress.ambientMesh" for CHART in "${CHARTS[@]}"; do # A chart whose main-loop render already failed has nothing to compare @@ -1326,10 +1374,10 @@ for CHART in "${CHARTS[@]}"; do ROW_FAIL="" ON_JSON="$WORK/ambient-$CHART-on.json" if ! RCJ_WITH_OVERLAY=1 render_chart_json "$CHART" "$ON_JSON" \ - --set "$AMBIENT_VALUE=true"; then + --set "$AMBIENT_VALUE.enabled=true"; then sed 's/^/ /' "$WORK/xerr" - echo " FAIL $CHART does not render with $AMBIENT_VALUE=true" - echo "::error title=ambient-mesh $CHART::$CHART does not render with $AMBIENT_VALUE=true. Since Gitea #143 every chart in this repository offers that value, and networkPolicy carries additionalProperties: false in all of them - so an unknown key does not get ignored, it fails the render. Add the key to values.yaml AND to values.schema.json (schema and template belong together per chart), and render the ingress port 15008 plus, inside the source-restricted branch, the ipBlock 169.254.7.127/32. Copy template-chart/templates/xxx.networkpolicy.yaml." + echo " FAIL $CHART does not render with $AMBIENT_VALUE.enabled=true" + echo "::error title=ambient-mesh $CHART::$CHART does not render with $AMBIENT_VALUE.enabled=true. Since Gitea #143 every chart in this repository offers that value, and networkPolicy carries additionalProperties: false in all of them - so an unknown key does not get ignored, it fails the render. Add the object to values.yaml AND to values.schema.json (schema and template belong together per chart), with enabled: false, port: \$AMBIENT_PORT and redirectedProbeSources: [\$AMBIENT_CIDR], and render that port plus - inside the source-restricted branch - that ipBlock. Copy template-chart/templates/xxx.networkpolicy.yaml." ROW_FAIL=1 fi @@ -1345,17 +1393,36 @@ for CHART in "${CHARTS[@]}"; do ROW_FAIL=1 fi + # The chart's DECLARED defaults, read with the same parser as everything else + # (ci/yaml2json prints a values file back as JSON). Not a regex over + # values.yaml: the pinned expectation is about numbers and a list, and a + # substring test over a commented file is how the probeSources check in + # assertion 4 was once fooled by a deleted key whose comment survived. + VALUES_JSON="$WORK/$CHART.values.json" + if [ -z "$ROW_FAIL" ]; then + if ! helm template y2j "$YAML2JSON" -f "$ROOT/$CHART/values.yaml" 2>"$WORK/xerr" \ + | awk '/^\{/ { print; exit }' >"$VALUES_JSON" || [ ! -s "$VALUES_JSON" ]; then + sed 's/^/ /' "$WORK/xerr" + echo " FAIL $CHART/values.yaml could not be re-read as JSON" + echo "::error title=ambient-mesh $CHART::$CHART/values.yaml could not be re-read by ci/yaml2json, so the chart's DECLARED ambient-mesh defaults could not be compared with the numbers this test pins. That is a parse failure, not a policy finding." + ROW_FAIL=1 + fi + fi + if [ -z "$ROW_FAIL" ]; then set +e python3 - "$CHART" "$RELEASE" "$AMBIENT_VALUE" "$WORK/$CHART.json" "$ON_JSON" \ - "$ROOT/$CHART" "$CHART_NAME" \ + "$ROOT/$CHART" "$CHART_NAME" "$AMBIENT_PORT" "$AMBIENT_CIDR" "$VALUES_JSON" \ >"$WORK/ambient-$CHART.out" 2>"$WORK/ambient-$CHART.pyerr" <<'PY' import json, re, sys -chart, release, gate, off_path, on_path, chart_dir, chart_name = sys.argv[1:8] +(chart, release, gate, off_path, on_path, chart_dir, chart_name, + pinned_port, pinned_cidr, values_json) = sys.argv[1:11] -PORT = 15008 -CIDR = "169.254.7.127/32" +# Pinned by the caller, never read from the chart - see the long note above +# AMBIENT_PORT in this script. +PORT = int(pinned_port) +CIDR = pinned_cidr lines = [] def ok(msg): lines.append("OK|%s: %s" % (chart, msg)) @@ -1451,27 +1518,77 @@ def cidrs_of(rule): return out -# --- The value is declared, in BOTH places it has to be -------------------- -# The render with the switch on already proves the SCHEMA declares it - an +# --- The chart DECLARES the pinned defaults ------------------------------- +# The render with the switch on already proves the SCHEMA declares the key - an # undeclared key cannot pass additionalProperties: false. What that render -# cannot prove is that values.yaml ships the documented default, and a value -# only reachable through --set is not a value an operator finds. +# cannot prove is what the chart's own defaults SAY, and since #143 made both +# Istio constants configurable, that is the half the pinned exception rests on: +# the exception below is granted to the number THIS SCRIPT names, so a chart +# whose default has drifted away from it must be red here rather than quietly +# carrying a different port than every other chart. values_yaml = "" try: values_yaml = open("%s/values.yaml" % chart_dir, encoding="utf-8").read() except IOError: pass -if re.search(r"^\s*ambientMesh\s*:", values_yaml, re.M): - ok("values.yaml ships the ambientMesh key with its default, and the render " - "with %s=true succeeded - which is itself the proof that " - "values.schema.json declares it, since networkPolicy carries " - "additionalProperties: false" % gate) + +declared = json.load(open(values_json)) +node = (((declared.get("networkPolicy") or {}).get("ingress") or {}) + .get("ambientMesh")) +if not isinstance(node, dict): + fail("renders with %s.enabled=true - so the schema knows the key - but the " + "chart's own values.yaml declares no ambientMesh OBJECT (found %r). A " + "value that exists only in the schema is reachable through --set and " + "findable nowhere: the defaults are what an operator reads. Ship the " + "object with enabled: false, port: %d and redirectedProbeSources: " + "[%s], as in template-chart." % (gate, node, PORT, CIDR)) else: - fail("renders with %s=true - so the schema knows the key - but values.yaml " - "contains no ambientMesh key at all. A value that exists only in the " - "schema is reachable through --set and findable nowhere: the defaults " - "are what an operator reads. Add it with its comment, default false, " - "as in template-chart." % gate) + problems = [] + if node.get("enabled") not in (False, None): + problems.append("enabled is %r and must default to false - the switch is " + "additive and no release may get it by accident" + % node.get("enabled")) + if node.get("port") != PORT: + problems.append("port defaults to %r, but this test pins Istio's HBONE " + "port at %d" % (node.get("port"), PORT)) + # Conditional on the chart offering probeSources, exactly as the + # inseparability check below and assertion 4 are. A chart whose probe rule + # is never source-restricted renders no ipBlock, so this key would reach no + # rule - and a value that silently does nothing reads as a control, which is + # worse than one that is absent (README, "Values, and shipping only the live + # ones"). So for those charts the key must be ABSENT, and that is asserted + # rather than tolerated. + sources = node.get("redirectedProbeSources") + offers_ps = bool(re.search(r"^\s*probeSources\s*:", values_yaml, re.M)) + if offers_ps and sources != [CIDR]: + problems.append("redirectedProbeSources defaults to %r, but this test " + "pins Istio's redirected probe source at [%r]" + % (sources, CIDR)) + if not offers_ps and sources is not None: + problems.append("redirectedProbeSources is declared (%r) although this " + "chart offers no networkPolicy.probeSources, so its probe " + "rule is never source-restricted and no ipBlock is ever " + "rendered - the key would reach no rule and read as a " + "control that does nothing" % sources) + if problems: + fail("the chart's declared ambient-mesh defaults do not match the " + "numbers ci/run-networkpolicy-test.sh pins: %s. Both are ISTIO " + "CONSTANTS, and they are values precisely so an operator can " + "correct them without a chart release - but the exception assertion " + "2 grants the HBONE port is pinned to the number THIS TEST knows, " + "never to whatever the value says, because a value-driven exception " + "would let a mistyped port ride in through a configurable field. " + "That is the hole design A of #143 was rejected for. So: if Istio " + "changed the constant, change it in all 30 charts AND in the pinned " + "expectation here - the drift is meant to be loud." + % "; ".join(problems)) + else: + ok("declares the pinned ambient-mesh defaults verbatim - enabled false, " + "port %d, %s - so the named exception below is granted to a number " + "this chart really ships, and not to whatever a value happened to say" + % (PORT, ("redirectedProbeSources [%s]" % CIDR) if offers_ps else + "and no redirectedProbeSources, correctly, since this chart " + "renders no ipBlock at all")) off_policies = own_policies(off_path) on_policies = own_policies(on_path) @@ -1496,20 +1613,20 @@ else: if cidr == CIDR: off_cidr_hits.append("%s ingress[%d]" % (name, i)) if off_hits: - fail("allows ingress port %d with %s at its default (false): %s. The " + fail("allows ingress port %d with %s.enabled at its default (false): %s. The " "exception assertion 2 grants for %d is gated on that value, and " "this is the half that keeps it from being a hole - a port rendered " "unconditionally would otherwise be found present by this assertion " "and never held against the pod spec by assertion 2. Gate the port " "on the value." % (PORT, gate, ", ".join(off_hits), PORT)) elif off_cidr_hits: - fail("carries the ipBlock %s with %s at its default (false): %s. The " + fail("carries the ipBlock %s with %s.enabled at its default (false): %s. The " "probe-source half of the switch is for the mesh only, where the " "kubelet's probes are redirected; outside it the address matches " "nothing and makes the rule read as if it did something." % (CIDR, gate, ", ".join(off_cidr_hits))) else: - ok("with %s at its default the policy allows neither port %d nor the " + ok("with %s.enabled at its default the policy allows neither port %d nor the " "ipBlock %s - the negative half of assertion 2's named exception, " "without which the exception would be a hole" % (gate, PORT, CIDR)) @@ -1544,18 +1661,18 @@ else: "the selected pod spec" % (name, i, value)) if not found_port: - fail("renders no ingress port %d with %s=true, so the value is " + fail("renders no ingress port %d with %s.enabled=true, so the value is " "decorative: the chart accepts it, the schema declares it, and the " "policy that comes out is the same one as before. A pod whose " "namespace has joined the ambient mesh is then unreachable, because " "ambient does not bypass NetworkPolicy enforcement." % (PORT, gate)) else: - ok("with %s=true the policy allows %s - ztunnel's HBONE tunnel, the one " + ok("with %s.enabled=true the policy allows %s - ztunnel's HBONE tunnel, the one " "port assertion 2 grants a named exception for" % (gate, ", ".join(found_port))) if bad_ports: - fail("with %s=true the policy allows ingress port(s) that are NOT " + fail("with %s.enabled=true the policy allows ingress port(s) that are NOT " "containerPorts of the selected pod spec and are NOT the one number " "the exception covers (%d): %s. This is the line between a named " "exception and the free port key that was rejected as design A of " @@ -1563,7 +1680,7 @@ else: "including a NodePort number written where a pod port belongs." % (gate, PORT, "; ".join(bad_ports))) elif found_port: - ok("with %s=true, port %d is still the ONLY ingress port of this chart " + ok("with %s.enabled=true, port %d is still the ONLY ingress port of this chart " "that is not a containerPort of the selected pod spec - the exception " "covers exactly one number and nothing rode in beside it" % (gate, PORT)) @@ -1599,7 +1716,7 @@ else: "%s ingress[%d] (peers: %s)" % (name, i, ", ".join(cidrs_of(rule)) or "no ipBlock at all")) if restricted_without_cidr: - fail("with %s=true the rule carrying port %d restricts its sources but " + fail("with %s.enabled=true the rule carrying port %d restricts its sources but " "does not include the ipBlock %s: %s. Inside the mesh the kubelet's " "probes are redirected and arrive from that address instead of from " "the worker node's, so a restricted rule without it drops every " diff --git a/template-chart/templates/xxx.networkpolicy.yaml b/template-chart/templates/xxx.networkpolicy.yaml index 90051ce..80843d2 100644 --- a/template-chart/templates/xxx.networkpolicy.yaml +++ b/template-chart/templates/xxx.networkpolicy.yaml @@ -113,7 +113,23 @@ spec this policy selects. round of this work already reported ingressController as an inbound escape hatch on the strength of the substring alone. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE, and not as decoration (#99). ambientMesh is an + OBJECT, so every read below goes through a fallback that reproduces THIS + chart's declared defaults - because an operator who deletes the object, + or who writes only "enabled: true" and leaves the rest out, must land on + the defaults and not in a nil dereference. #136 shipped exactly that + defect one value away from here: the NOTES.txt branch dereferenced + .networkPolicy.egress directly, and a release that had dropped the key + failed to render. + + Note which fallback the probe-source list gets: "default" treats an EMPTY + list as unset, so clearing it restores the constant rather than removing + the ipBlock. That is deliberate - the two halves of this switch are not + separable, and the way to not open the port at all is enabled: false. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} {{- $routed := or .Values.ingress.enabled .Values.httpRoute.enabled }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy @@ -147,20 +163,36 @@ spec: - protocol: {{ .protocol | default "TCP" }} port: {{ .containerPort }} {{- end }} - {{- if $ambientMesh }} - {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh. 15008/TCP is - ztunnel's HBONE tunnel, the port every inbound mesh connection - arrives on once the namespace carries istio.io/dataplane-mode. - Istio's own documentation is explicit that ambient "does not and - will never bypass Kubernetes NetworkPolicy enforcement", so - without this port a pod in the mesh is simply unreachable. + {{- if $ambientEnabled }} + {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh. The default is + 15008/TCP: ztunnel's HBONE tunnel, the port every inbound mesh + connection arrives on once the namespace carries + istio.io/dataplane-mode. Istio's own documentation is explicit + that ambient "does not and will never bypass Kubernetes + NetworkPolicy enforcement", so without this port a pod in the + mesh is simply unreachable. + + 15008 IS AN ISTIO CONSTANT, NOT A STANDARD. It is not registered + with IANA for this, Kubernetes knows nothing about it, and no + protocol prescribes it - Istio picked it for HBONE and Istio can + change it. That is precisely why it sits in a VALUE with a + documented default rather than as a literal here: an operator can + correct it without waiting for a chart release. It is the one port in this rule that is NOT a containerPort of the selected pod spec - ztunnel terminates it, not the application - so ci/run-networkpolicy-test.sh holds a NAMED - exception for exactly this number, gated on exactly this value. - Anything else that is not a containerPort still fails, and so - does 15008 itself while the value is off. + exception for it. Note what that exception is pinned to: the + number 15008 as written in THE TEST, plus an assertion that this + chart's values.yaml declares the same default. It is NOT "whatever + this value happens to say" - that would let a mistyped port ride + in through a configurable field, which is the hole design A of + #143 was rejected for. + + READ FOR ztunnel 1.30.6. If Istio changes the port, or the mesh is + replaced: the failure is a pod that cannot be reached inside the + mesh. Change the default in all 30 charts AND the pinned number in + the test - which is the thing that forces someone to look. NOT MEASURED, and the chart should not pretend otherwise: that 15008 ALONE suffices comes from Istio's documentation. What has @@ -168,7 +200,7 @@ spec: opens. Provable at the first namespace that joins the mesh, and no namespace carries the label yet. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* RESTRICTED, because the operator supplied probeSources. The @@ -209,34 +241,64 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason - the switch is one value instead of two. Inside the mesh the - kubelet's probes are redirected and no longer arrive from the - worker node's address: they arrive from 169.254.7.127, which is - therefore the probeSources entry nobody would think to add. + the switch is one value instead of two. Inside ISTIO's ambient + mesh the kubelet's probes are redirected BY ISTIO and no longer + arrive from the worker node's address: they arrive from + 169.254.7.127, which is therefore the probe source nobody would + think to add. - The two halves are deliberately inseparable. An operator who - only opens 15008 by hand has a policy that passes every review - - and the moment probeSources is non-empty, every probe is dropped, - the pod never becomes Ready, and the symptom reads as a broken + WHERE THAT ADDRESS COMES FROM, because it is worth being able to + recognise as vendor magic rather than as protocol: + + - It is LINK-LOCAL, 169.254.0.0/16 (RFC 3927) - a range + reserved for node-local use that is never routed. Exactly + the range one picks a vendor's magic address out of. + - Istio's IPv6 counterpart gives the game away: + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME + DIGITS - 9254:7127 is 169.254.7.127 - plus 1337. Numbers + built that way are vendor constants, not protocols. + - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the + NetworkPolicy API nor the kubelet has any notion of it; only + Istio's own data plane rewrites the probe source onto it. + + READ FOR ztunnel 1.30.6. If Istio changes it, or the mesh is + replaced, THE FAILURE IS SILENT: probes would be dropped, and + only where probeSources is non-empty - which is nowhere today. + Nothing in this repository or in Kubernetes would report it. That + is why the address is a VALUE with a documented default, so it can + be corrected without a chart release, and why the test pins the + number it expects: changing it means touching 30 values files and + the test, and the test is what makes someone look. + + A LIST, not a string, and this is the one case where that does not + contradict "the value is a constant, not a configuration": on a + DUAL-STACK cluster BOTH entries are needed at the same time, not + one of two. This cluster is SingleStack IPv4, so the IPv6 + counterpart above is left out of the default - an ipBlock of the + wrong family is accepted by the API server and matches nothing. + Add it alongside, never instead, if the cluster gains IPv6. + + The two halves stay inseparable. An operator who only opens the + port by hand has a policy that passes every review - and the + moment probeSources is non-empty, every probe is dropped, the pod + never becomes Ready, and the symptom reads as a broken application rather than as a policy. That is assertion 4 of ci/run-networkpolicy-test.sh, re-created one value further along. - One switch cannot be set halfway. Only rendered in this branch, because this is the only branch with a "from" list at all: with probeSources empty the rule is - open and needs no peer. The IPv6 counterpart - fd16:9254:7127:1337:ffff:ffff:ffff:ffff is deliberately absent - - this cluster is SingleStack IPv4, and an ipBlock of the wrong - family is accepted by the API server and matches nothing. + open and needs no peer. UNPROVEN IN OPERATION: probeSources is empty in every chart of this repository (stalwart sets it to the empty list explicitly), so no release reaches this branch today. What CI proves is its render, through the overlay in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* NO "from" SELECTOR, WHICH MEANS OPEN - deliberately, and only diff --git a/template-chart/values.schema.json b/template-chart/values.schema.json index a1fcc0b..125e4f7 100644 --- a/template-chart/values.schema.json +++ b/template-chart/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/template-chart/values.yaml b/template-chart/values.yaml index 0812102..83ed712 100644 --- a/template-chart/values.yaml +++ b/template-chart/values.yaml @@ -283,50 +283,111 @@ networkPolicy: # pods. A SIBLING of this block and NO PART # OF IT, despite the name. ingress: - # Opens the pod for Istio's ambient mesh. OFF by default, and additive: - # with it off this chart renders byte for byte as it did before the value - # existed. + # Opens the pod for Istio's ambient mesh. OFF by default, and additive: with + # it off this chart renders byte for byte as it did before the value existed. # # WHAT IT RENDERS, and the two halves are the point: # - # 1. 15008/TCP inbound - ztunnel's HBONE tunnel. Every inbound mesh - # connection arrives on it, and Istio's documentation is explicit that - # ambient "does not and will never bypass Kubernetes NetworkPolicy - # enforcement". Without this port a pod whose namespace has joined the - # mesh is unreachable. - # 2. 169.254.7.127/32 as an additional probe source - but ONLY where - # probeSources is non-empty, because only then does the rule have a - # "from" list at all. Inside the mesh the kubelet's probes are - # redirected and no longer come from the worker node's address. + # 1. The HBONE port inbound (default 15008/TCP) - ztunnel's tunnel. Every + # inbound mesh connection arrives on it, and Istio's documentation is + # explicit that ambient "does not and will never bypass Kubernetes + # NetworkPolicy enforcement". Without this port a pod whose namespace + # has joined the mesh is unreachable. + # 2. The redirected probe source (default 169.254.7.127/32) - but ONLY + # where networkPolicy.probeSources is non-empty, because only then does + # the rule have a "from" list at all. Inside the mesh the kubelet's + # probes are redirected and no longer come from the worker node's + # address. # # WHY ONE SWITCH AND NOT TWO. Point 2 is the one nobody thinks of. An # operator who adds "15008" to a port list by hand writes a policy that # passes review, and the first time probeSources is narrowed every probe is # dropped: the pod never becomes Ready and the symptom reads as a broken - # application rather than as a policy. A switch whose halves can only be - # had together cannot be set halfway - which is why this is a named - # intention and not a free port list. + # application rather than as a policy. A switch whose halves can only be had + # together cannot be set halfway - which is why the two live in ONE object + # under one "enabled", and not as separate values that can drift apart. # - # TWO THINGS IT DOES NOT PROVE, and they belong next to the value rather - # than in a commit message: + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. # - # - That 15008 alone is enough. That comes from Istio's documentation. - # Measured on this cluster is only which listeners ztunnel opens - # ([::]:15008 inbound, plus its own admin and stats ports). Provable at - # the first namespace that joins the mesh; none carries - # istio.io/dataplane-mode yet. - # - That the probe-source half works. probeSources is empty in every - # chart here, so no release reaches that branch. CI proves its render, - # not its effect. - # - # If the policy restricts sources, note that 15008 is restricted with them: - # the port joins the rule that carries the probe port, so it is reachable - # from that rule's peers only. Whether ztunnel needs a peer of its own is - # open - it runs with its own pod IPs in istio-system (measured: no - # hostNetwork on the DaemonSet, Istio 1.30.6), so if one turns out to be - # needed it is expressible as a namespaceSelector plus podSelector and does - # not need an unknowable node-IP CIDR. - ambientMesh: false + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else in this object is inert while it is false, + # and the chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: it is not + # registered with IANA for this, no protocol prescribes it, and Kubernetes + # knows nothing about it. Read for ztunnel 1.30.6 (measured on this + # cluster: ztunnel opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh simply cannot be reached. Change + # it here in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately the thing that + # forces someone to look, because the exception it grants this port is + # pinned to the number IT knows and not to whatever this value says. A + # value-driven exception would let a mistyped port through, which is the + # hole design A of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens. + # Provable at the first namespace to join the mesh; none carries + # istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM, so it is recognisable as vendor magic + # rather than as protocol: + # + # - It is LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, and this list in all 30 charts + # plus the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and this is the one case where that does not + # contradict "a constant, not a configuration": on a DUAL-STACK cluster + # BOTH entries are needed at the same time, not one of two. This cluster is + # SingleStack IPv4, so the IPv6 counterpart is left out - an ipBlock of the + # wrong family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the + # ipBlock, deliberately: the two halves of this switch are not separable. + # To not open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 07563849a57c08c5b83151600f43bfaa4e5d83d8 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:00 +0000 Subject: [PATCH 34/63] refactor(apache-tika): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../templates/apache-tika.networkpolicy.yaml | 50 ++++++++++-- apache-tika/values.schema.json | 24 +++++- apache-tika/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/apache-tika/templates/apache-tika.networkpolicy.yaml b/apache-tika/templates/apache-tika.networkpolicy.yaml index 39ab28d..ab230be 100644 --- a/apache-tika/templates/apache-tika.networkpolicy.yaml +++ b/apache-tika/templates/apache-tika.networkpolicy.yaml @@ -57,7 +57,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -91,7 +102,7 @@ spec: - ports: - protocol: TCP port: 9998 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -105,11 +116,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -138,7 +160,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -156,11 +178,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/apache-tika/values.schema.json b/apache-tika/values.schema.json index f08fc46..7298850 100644 --- a/apache-tika/values.schema.json +++ b/apache-tika/values.schema.json @@ -37,8 +37,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/apache-tika/values.yaml b/apache-tika/values.yaml index 76ce2fe..9ae1fab 100644 --- a/apache-tika/values.yaml +++ b/apache-tika/values.yaml @@ -164,7 +164,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 4d6e746e792eaae1d76333d81ed2fdb7651045de Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 35/63] refactor(bentopdf): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../templates/bentopdf.networkpolicy.yaml | 50 ++++++++++-- bentopdf/values.schema.json | 24 +++++- bentopdf/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/bentopdf/templates/bentopdf.networkpolicy.yaml b/bentopdf/templates/bentopdf.networkpolicy.yaml index 06f7697..d176a6e 100644 --- a/bentopdf/templates/bentopdf.networkpolicy.yaml +++ b/bentopdf/templates/bentopdf.networkpolicy.yaml @@ -51,7 +51,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -84,7 +95,7 @@ spec: - ports: - protocol: TCP port: 8080 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -98,11 +109,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -146,7 +168,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -164,11 +186,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/bentopdf/values.schema.json b/bentopdf/values.schema.json index 61168a2..8e4ad2e 100644 --- a/bentopdf/values.schema.json +++ b/bentopdf/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/bentopdf/values.yaml b/bentopdf/values.yaml index 25831ee..e0bd4b7 100644 --- a/bentopdf/values.yaml +++ b/bentopdf/values.yaml @@ -411,7 +411,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 5c4b7c71f4ad41a00b35b128fe5f22a2d680896f Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 36/63] refactor(bulwark): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- bulwark/templates/bulwark.networkpolicy.yaml | 50 ++++++++++-- bulwark/values.schema.json | 24 +++++- bulwark/values.yaml | 80 +++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/bulwark/templates/bulwark.networkpolicy.yaml b/bulwark/templates/bulwark.networkpolicy.yaml index 1e189c2..71ff6fa 100644 --- a/bulwark/templates/bulwark.networkpolicy.yaml +++ b/bulwark/templates/bulwark.networkpolicy.yaml @@ -57,7 +57,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -92,7 +103,7 @@ spec: - ports: - protocol: TCP port: 3000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -106,11 +117,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -154,7 +176,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -172,11 +194,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/bulwark/values.schema.json b/bulwark/values.schema.json index 4f2d858..0097ae5 100644 --- a/bulwark/values.schema.json +++ b/bulwark/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/bulwark/values.yaml b/bulwark/values.yaml index 2802305..783e12c 100644 --- a/bulwark/values.yaml +++ b/bulwark/values.yaml @@ -932,7 +932,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 7ce0a0c63cabda632a517dfd96524e4d9c91634e Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 37/63] refactor(calibre-web-automated): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../calibre-web-automated.networkpolicy.yaml | 50 ++++++++++-- calibre-web-automated/values.schema.json | 24 +++++- calibre-web-automated/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/calibre-web-automated/templates/calibre-web-automated.networkpolicy.yaml b/calibre-web-automated/templates/calibre-web-automated.networkpolicy.yaml index 7fb7e89..f176f9e 100644 --- a/calibre-web-automated/templates/calibre-web-automated.networkpolicy.yaml +++ b/calibre-web-automated/templates/calibre-web-automated.networkpolicy.yaml @@ -52,7 +52,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -87,7 +98,7 @@ spec: - ports: - protocol: TCP port: 8083 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -101,11 +112,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -149,7 +171,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -167,11 +189,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/calibre-web-automated/values.schema.json b/calibre-web-automated/values.schema.json index 5695744..cc5f825 100644 --- a/calibre-web-automated/values.schema.json +++ b/calibre-web-automated/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/calibre-web-automated/values.yaml b/calibre-web-automated/values.yaml index 11d505b..f30ec46 100644 --- a/calibre-web-automated/values.yaml +++ b/calibre-web-automated/values.yaml @@ -299,7 +299,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From f6cbaedda19531daea4cf0a3833d43ad5b850d96 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 38/63] refactor(cyberchef): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../templates/cyberchef.networkpolicy.yaml | 50 ++++++++++-- cyberchef/values.schema.json | 24 +++++- cyberchef/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/cyberchef/templates/cyberchef.networkpolicy.yaml b/cyberchef/templates/cyberchef.networkpolicy.yaml index e364808..0bcb73d 100644 --- a/cyberchef/templates/cyberchef.networkpolicy.yaml +++ b/cyberchef/templates/cyberchef.networkpolicy.yaml @@ -58,7 +58,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -93,7 +104,7 @@ spec: - ports: - protocol: TCP port: 8080 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -107,11 +118,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -156,7 +178,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -174,11 +196,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/cyberchef/values.schema.json b/cyberchef/values.schema.json index cd477ab..87755b6 100644 --- a/cyberchef/values.schema.json +++ b/cyberchef/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/cyberchef/values.yaml b/cyberchef/values.yaml index 60cd5cb..da06a9c 100644 --- a/cyberchef/values.yaml +++ b/cyberchef/values.yaml @@ -238,7 +238,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 8db757103aec1d23001e7cbf0fe7da1a63bade30 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 39/63] refactor(freesailarr): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../templates/freesailarr.networkpolicy.yaml | 50 ++++++++++-- freesailarr/values.schema.json | 24 +++++- freesailarr/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/freesailarr/templates/freesailarr.networkpolicy.yaml b/freesailarr/templates/freesailarr.networkpolicy.yaml index 502e9f9..a65cc76 100644 --- a/freesailarr/templates/freesailarr.networkpolicy.yaml +++ b/freesailarr/templates/freesailarr.networkpolicy.yaml @@ -75,7 +75,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -133,7 +144,7 @@ spec: port: 6767 - protocol: TCP port: 5007 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -147,11 +158,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -196,7 +218,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -214,11 +236,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/freesailarr/values.schema.json b/freesailarr/values.schema.json index 476fb3b..5aac2bc 100644 --- a/freesailarr/values.schema.json +++ b/freesailarr/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/freesailarr/values.yaml b/freesailarr/values.yaml index 044d217..c1c5fed 100644 --- a/freesailarr/values.yaml +++ b/freesailarr/values.yaml @@ -1461,7 +1461,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 90ea3eae44fd9da2da2092df86234b168e1e9b2a Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 40/63] refactor(garage): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- garage/templates/garage.networkpolicy.yaml | 50 ++++++++++++-- garage/values.schema.json | 24 ++++++- garage/values.yaml | 80 +++++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/garage/templates/garage.networkpolicy.yaml b/garage/templates/garage.networkpolicy.yaml index 0f79429..285adf7 100644 --- a/garage/templates/garage.networkpolicy.yaml +++ b/garage/templates/garage.networkpolicy.yaml @@ -61,7 +61,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -148,7 +159,7 @@ spec: - ports: - protocol: TCP port: {{ $settings.ports.admin | int64 }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -162,11 +173,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -195,7 +217,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -213,11 +235,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/garage/values.schema.json b/garage/values.schema.json index 9572869..2654f84 100644 --- a/garage/values.schema.json +++ b/garage/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/garage/values.yaml b/garage/values.yaml index 80efe98..bdd32a7 100644 --- a/garage/values.yaml +++ b/garage/values.yaml @@ -625,7 +625,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 824b71c1ff8e67a58362772aa378606be61f7d3c Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 41/63] refactor(gotenberg): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../templates/gotenberg.networkpolicy.yaml | 50 ++++++++++-- gotenberg/values.schema.json | 24 +++++- gotenberg/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/gotenberg/templates/gotenberg.networkpolicy.yaml b/gotenberg/templates/gotenberg.networkpolicy.yaml index 121f758..8da3f15 100644 --- a/gotenberg/templates/gotenberg.networkpolicy.yaml +++ b/gotenberg/templates/gotenberg.networkpolicy.yaml @@ -61,7 +61,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -94,7 +105,7 @@ spec: - ports: - protocol: TCP port: 3000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -108,11 +119,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -141,7 +163,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -159,11 +181,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/gotenberg/values.schema.json b/gotenberg/values.schema.json index 9b86507..5fc452b 100644 --- a/gotenberg/values.schema.json +++ b/gotenberg/values.schema.json @@ -37,8 +37,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/gotenberg/values.yaml b/gotenberg/values.yaml index 476964b..5ab18e7 100644 --- a/gotenberg/values.yaml +++ b/gotenberg/values.yaml @@ -189,7 +189,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # REMOTE ASSETS: lets Chromium fetch over TCP 80 and 443, to any -- 2.54.0 From 371909a92864c244e7fb868276a1125c18fbaaa9 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 42/63] refactor(homeassistant): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../homeassistant.networkpolicy.yaml | 50 ++++++++++-- homeassistant/values.schema.json | 24 +++++- homeassistant/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/homeassistant/templates/homeassistant.networkpolicy.yaml b/homeassistant/templates/homeassistant.networkpolicy.yaml index a43ae4e..a45fcae 100644 --- a/homeassistant/templates/homeassistant.networkpolicy.yaml +++ b/homeassistant/templates/homeassistant.networkpolicy.yaml @@ -63,7 +63,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -94,7 +105,7 @@ spec: - ports: - protocol: TCP port: 8123 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -108,11 +119,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -138,7 +160,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -156,11 +178,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/homeassistant/values.schema.json b/homeassistant/values.schema.json index ebe2611..367d217 100644 --- a/homeassistant/values.schema.json +++ b/homeassistant/values.schema.json @@ -20,8 +20,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/homeassistant/values.yaml b/homeassistant/values.yaml index 7a191df..c004f13 100644 --- a/homeassistant/values.yaml +++ b/homeassistant/values.yaml @@ -337,5 +337,83 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 -- 2.54.0 From 2d7c11d137bce1274f70f7252b3703486b4e4091 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 43/63] refactor(keycloak): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../templates/keycloak.networkpolicy.yaml | 50 ++++++++++-- keycloak/values.schema.json | 24 +++++- keycloak/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/keycloak/templates/keycloak.networkpolicy.yaml b/keycloak/templates/keycloak.networkpolicy.yaml index a391fee..8d64518 100644 --- a/keycloak/templates/keycloak.networkpolicy.yaml +++ b/keycloak/templates/keycloak.networkpolicy.yaml @@ -88,7 +88,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -174,7 +185,7 @@ spec: - ports: - protocol: TCP port: {{ $settings.ports.management | int64 }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -188,11 +199,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -221,7 +243,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -239,11 +261,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/keycloak/values.schema.json b/keycloak/values.schema.json index f61bdc1..403ed74 100644 --- a/keycloak/values.schema.json +++ b/keycloak/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/keycloak/values.yaml b/keycloak/values.yaml index d342bf3..d766169 100644 --- a/keycloak/values.yaml +++ b/keycloak/values.yaml @@ -1016,7 +1016,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 5527ebfc15bea324eca7350b983b17ac1ddd2560 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:01 +0000 Subject: [PATCH 44/63] refactor(matrix-synapse): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../matrix-synapse.networkpolicy.yaml | 50 ++++++++++-- matrix-synapse/values.schema.json | 24 +++++- matrix-synapse/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/matrix-synapse/templates/matrix-synapse.networkpolicy.yaml b/matrix-synapse/templates/matrix-synapse.networkpolicy.yaml index c568c64..34fffa4 100644 --- a/matrix-synapse/templates/matrix-synapse.networkpolicy.yaml +++ b/matrix-synapse/templates/matrix-synapse.networkpolicy.yaml @@ -56,7 +56,18 @@ WHAT THESE POLICIES DO NOT DO: for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -84,7 +95,7 @@ spec: - ports: - protocol: TCP port: 8008 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -98,11 +109,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -149,7 +171,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -167,11 +189,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/matrix-synapse/values.schema.json b/matrix-synapse/values.schema.json index a7ced6a..2915e78 100644 --- a/matrix-synapse/values.schema.json +++ b/matrix-synapse/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/matrix-synapse/values.yaml b/matrix-synapse/values.yaml index c97840a..7893c14 100644 --- a/matrix-synapse/values.yaml +++ b/matrix-synapse/values.yaml @@ -413,7 +413,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports FOR SYNAPSE ONLY. These deliberately do NOT -- 2.54.0 From 5a6dedd9835c1817298deaadc25a9a394d3df763 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:02 +0000 Subject: [PATCH 45/63] refactor(minecraft-router): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../minecraft-router.networkpolicy.yaml | 50 ++++++++++-- minecraft-router/values.schema.json | 24 +++++- minecraft-router/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/minecraft-router/templates/minecraft-router.networkpolicy.yaml b/minecraft-router/templates/minecraft-router.networkpolicy.yaml index 2a19a8d..144d8f5 100644 --- a/minecraft-router/templates/minecraft-router.networkpolicy.yaml +++ b/minecraft-router/templates/minecraft-router.networkpolicy.yaml @@ -61,7 +61,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -113,7 +124,7 @@ spec: - ports: - protocol: TCP port: {{ .Values.server.apiPort | int64 }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -127,11 +138,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -157,7 +179,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -175,11 +197,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/minecraft-router/values.schema.json b/minecraft-router/values.schema.json index 16d9363..2cf7c12 100644 --- a/minecraft-router/values.schema.json +++ b/minecraft-router/values.schema.json @@ -37,8 +37,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/minecraft-router/values.yaml b/minecraft-router/values.yaml index 3a61564..b30e877 100644 --- a/minecraft-router/values.yaml +++ b/minecraft-router/values.yaml @@ -251,7 +251,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 471c7b848dbc5c6c09d96ee0336f34721dbd184b Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:02 +0000 Subject: [PATCH 46/63] refactor(outline): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- outline/templates/outline.networkpolicy.yaml | 50 ++++++++++-- outline/values.schema.json | 24 +++++- outline/values.yaml | 80 +++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/outline/templates/outline.networkpolicy.yaml b/outline/templates/outline.networkpolicy.yaml index 13a7d51..9ae9c9b 100644 --- a/outline/templates/outline.networkpolicy.yaml +++ b/outline/templates/outline.networkpolicy.yaml @@ -58,7 +58,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -93,7 +104,7 @@ spec: - ports: - protocol: TCP port: 3000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -107,11 +118,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -155,7 +177,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -173,11 +195,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/outline/values.schema.json b/outline/values.schema.json index 3913c54..44900c7 100644 --- a/outline/values.schema.json +++ b/outline/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/outline/values.yaml b/outline/values.yaml index 309a9f4..0b01618 100644 --- a/outline/values.yaml +++ b/outline/values.yaml @@ -290,7 +290,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 0bf865ffb4f6d00bd2c7968787adad4838af3827 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:02 +0000 Subject: [PATCH 47/63] refactor(paperless-ngx): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../paperless-server.networkpolicy.yaml | 50 ++++++++++-- paperless-ngx/values.schema.json | 24 +++++- paperless-ngx/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/paperless-ngx/templates/paperless-server.networkpolicy.yaml b/paperless-ngx/templates/paperless-server.networkpolicy.yaml index 6e33f23..6687d62 100644 --- a/paperless-ngx/templates/paperless-server.networkpolicy.yaml +++ b/paperless-ngx/templates/paperless-server.networkpolicy.yaml @@ -86,7 +86,18 @@ that the egress rule below contains their containerPorts. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -121,7 +132,7 @@ spec: - ports: - protocol: TCP port: 8000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -135,11 +146,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -183,7 +205,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -201,11 +223,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/paperless-ngx/values.schema.json b/paperless-ngx/values.schema.json index d5d4c02..e7f5d27 100644 --- a/paperless-ngx/values.schema.json +++ b/paperless-ngx/values.schema.json @@ -93,8 +93,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/paperless-ngx/values.yaml b/paperless-ngx/values.yaml index 2e693cc..27b5cef 100644 --- a/paperless-ngx/values.yaml +++ b/paperless-ngx/values.yaml @@ -685,7 +685,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Outbound IMAPS (993) for paperless' mail import. OFF, and it has to be a -- 2.54.0 From 60c281882e1a89124c67604041f86b748604cdc0 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:02 +0000 Subject: [PATCH 48/63] refactor(patchmon): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../templates/patchmon.networkpolicy.yaml | 50 ++++++++++-- patchmon/values.schema.json | 24 +++++- patchmon/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/patchmon/templates/patchmon.networkpolicy.yaml b/patchmon/templates/patchmon.networkpolicy.yaml index a6e52f2..b99976c 100644 --- a/patchmon/templates/patchmon.networkpolicy.yaml +++ b/patchmon/templates/patchmon.networkpolicy.yaml @@ -52,7 +52,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -88,7 +99,7 @@ spec: - ports: - protocol: TCP port: 3000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -102,11 +113,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -150,7 +172,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -168,11 +190,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/patchmon/values.schema.json b/patchmon/values.schema.json index 001cf31..62dc4ea 100644 --- a/patchmon/values.schema.json +++ b/patchmon/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/patchmon/values.yaml b/patchmon/values.yaml index 5ede5c3..ce1f9c9 100644 --- a/patchmon/values.yaml +++ b/patchmon/values.yaml @@ -337,7 +337,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From e201ea950277bdc039f5d246a47d1c91cdd7e8e3 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:02 +0000 Subject: [PATCH 49/63] refactor(rallly): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- rallly/templates/rallly.networkpolicy.yaml | 50 ++++++++++++-- rallly/values.schema.json | 24 ++++++- rallly/values.yaml | 80 +++++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/rallly/templates/rallly.networkpolicy.yaml b/rallly/templates/rallly.networkpolicy.yaml index 646cd56..a6380dd 100644 --- a/rallly/templates/rallly.networkpolicy.yaml +++ b/rallly/templates/rallly.networkpolicy.yaml @@ -52,7 +52,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -87,7 +98,7 @@ spec: - ports: - protocol: TCP port: 3000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -101,11 +112,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -149,7 +171,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -167,11 +189,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/rallly/values.schema.json b/rallly/values.schema.json index 63cdea6..c2e7b35 100644 --- a/rallly/values.schema.json +++ b/rallly/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/rallly/values.yaml b/rallly/values.yaml index b1ce5ce..af43d5f 100644 --- a/rallly/values.yaml +++ b/rallly/values.yaml @@ -301,7 +301,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 04e531e6018fcaf140fb8ba540a76b7c81b1d08b Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:02 +0000 Subject: [PATCH 50/63] refactor(rss-bridge): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../templates/rss-bridge.networkpolicy.yaml | 50 ++++++++++-- rss-bridge/values.schema.json | 24 +++++- rss-bridge/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/rss-bridge/templates/rss-bridge.networkpolicy.yaml b/rss-bridge/templates/rss-bridge.networkpolicy.yaml index e21ebcd..acc38a2 100644 --- a/rss-bridge/templates/rss-bridge.networkpolicy.yaml +++ b/rss-bridge/templates/rss-bridge.networkpolicy.yaml @@ -55,7 +55,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -93,7 +104,7 @@ spec: - ports: - protocol: TCP port: 80 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -107,11 +118,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -155,7 +177,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -173,11 +195,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/rss-bridge/values.schema.json b/rss-bridge/values.schema.json index c2851e3..377b4a4 100644 --- a/rss-bridge/values.schema.json +++ b/rss-bridge/values.schema.json @@ -52,8 +52,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/rss-bridge/values.yaml b/rss-bridge/values.yaml index 997be38..4d0b327 100644 --- a/rss-bridge/values.yaml +++ b/rss-bridge/values.yaml @@ -468,7 +468,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From e67dba21a3f8d6d72a71f21deab259175c2a828a Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:02 +0000 Subject: [PATCH 51/63] refactor(searxng): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- searxng/templates/searxng.networkpolicy.yaml | 50 ++++++++++-- searxng/values.schema.json | 24 +++++- searxng/values.yaml | 80 +++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/searxng/templates/searxng.networkpolicy.yaml b/searxng/templates/searxng.networkpolicy.yaml index e2ac245..d5af784 100644 --- a/searxng/templates/searxng.networkpolicy.yaml +++ b/searxng/templates/searxng.networkpolicy.yaml @@ -53,7 +53,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -88,7 +99,7 @@ spec: - ports: - protocol: TCP port: 8080 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -102,11 +113,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -150,7 +172,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -168,11 +190,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/searxng/values.schema.json b/searxng/values.schema.json index e6b89e8..755bcad 100644 --- a/searxng/values.schema.json +++ b/searxng/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/searxng/values.yaml b/searxng/values.yaml index cc4a4a3..a9b6e25 100644 --- a/searxng/values.yaml +++ b/searxng/values.yaml @@ -514,7 +514,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 8dd15d42d59434f9607c3051ad03eab4cf86a929 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:02 +0000 Subject: [PATCH 52/63] refactor(stalwart): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../templates/stalwart.networkpolicy.yaml | 50 ++++++++++-- stalwart/values.schema.json | 24 +++++- stalwart/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/stalwart/templates/stalwart.networkpolicy.yaml b/stalwart/templates/stalwart.networkpolicy.yaml index 718a94a..2368ffb 100644 --- a/stalwart/templates/stalwart.networkpolicy.yaml +++ b/stalwart/templates/stalwart.networkpolicy.yaml @@ -68,7 +68,18 @@ unenforced policy is accepted and filters nothing. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := .Values.networkPolicy.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} {{- range $group, $spec := fromYaml (include "stalwart.nodes" .) }} {{- $name := printf "%s-%s" (include "stalwart.fullname" $root) $group }} --- @@ -113,7 +124,7 @@ spec: - ports: - protocol: {{ $http.protocol | default "TCP" }} port: {{ int $http.containerPort }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -127,11 +138,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if or $probeSources $allowedNs }} {{- /* @@ -175,7 +197,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -193,11 +215,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/stalwart/values.schema.json b/stalwart/values.schema.json index 27f1d8c..b025dc2 100644 --- a/stalwart/values.schema.json +++ b/stalwart/values.schema.json @@ -340,8 +340,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/stalwart/values.yaml b/stalwart/values.yaml index 91e191c..2e38d46 100644 --- a/stalwart/values.yaml +++ b/stalwart/values.yaml @@ -590,7 +590,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 # Egress is restricted BY PORT, not by destination, and the reason is a limit # of the object rather than laziness: a NetworkPolicy matches ipBlock CIDRs -- 2.54.0 From 41d51b3966ac9a51685ea85f8b3dff5c320823ef Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:02 +0000 Subject: [PATCH 53/63] refactor(sure): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- sure/templates/sure.networkpolicy.yaml | 50 ++++++++++++++-- sure/values.schema.json | 24 +++++++- sure/values.yaml | 80 +++++++++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/sure/templates/sure.networkpolicy.yaml b/sure/templates/sure.networkpolicy.yaml index e69289c..3eab307 100644 --- a/sure/templates/sure.networkpolicy.yaml +++ b/sure/templates/sure.networkpolicy.yaml @@ -59,7 +59,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -94,7 +105,7 @@ spec: - ports: - protocol: TCP port: 3000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -108,11 +119,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -156,7 +178,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -174,11 +196,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/sure/values.schema.json b/sure/values.schema.json index 46d2f53..672c02e 100644 --- a/sure/values.schema.json +++ b/sure/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/sure/values.yaml b/sure/values.yaml index 8b50e1b..48cec07 100644 --- a/sure/values.yaml +++ b/sure/values.yaml @@ -492,7 +492,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 388ff8c1a1d217e93765085f064fbb3e9ede10c8 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:03 +0000 Subject: [PATCH 54/63] refactor(urlaubsverwaltung): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../urlaubsverwaltung.networkpolicy.yaml | 50 ++++++++++-- urlaubsverwaltung/values.schema.json | 24 +++++- urlaubsverwaltung/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/urlaubsverwaltung/templates/urlaubsverwaltung.networkpolicy.yaml b/urlaubsverwaltung/templates/urlaubsverwaltung.networkpolicy.yaml index 306f00c..dc47031 100644 --- a/urlaubsverwaltung/templates/urlaubsverwaltung.networkpolicy.yaml +++ b/urlaubsverwaltung/templates/urlaubsverwaltung.networkpolicy.yaml @@ -55,7 +55,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -90,7 +101,7 @@ spec: - ports: - protocol: TCP port: 8080 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -104,11 +115,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -152,7 +174,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -170,11 +192,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/urlaubsverwaltung/values.schema.json b/urlaubsverwaltung/values.schema.json index 6e2c098..63fb905 100644 --- a/urlaubsverwaltung/values.schema.json +++ b/urlaubsverwaltung/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/urlaubsverwaltung/values.yaml b/urlaubsverwaltung/values.yaml index d756b88..974f519 100644 --- a/urlaubsverwaltung/values.yaml +++ b/urlaubsverwaltung/values.yaml @@ -311,7 +311,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 9b6ce007d519eed142b55aefefe91e4d9ab2e96b Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:03 +0000 Subject: [PATCH 55/63] refactor(voucher-vault): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../voucher-vault.networkpolicy.yaml | 50 ++++++++++-- voucher-vault/values.schema.json | 24 +++++- voucher-vault/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/voucher-vault/templates/voucher-vault.networkpolicy.yaml b/voucher-vault/templates/voucher-vault.networkpolicy.yaml index 07a645d..7977c94 100644 --- a/voucher-vault/templates/voucher-vault.networkpolicy.yaml +++ b/voucher-vault/templates/voucher-vault.networkpolicy.yaml @@ -48,7 +48,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -83,7 +94,7 @@ spec: - ports: - protocol: TCP port: 8000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -97,11 +108,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -145,7 +167,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -163,11 +185,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/voucher-vault/values.schema.json b/voucher-vault/values.schema.json index 0edddac..892d1b7 100644 --- a/voucher-vault/values.schema.json +++ b/voucher-vault/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/voucher-vault/values.yaml b/voucher-vault/values.yaml index 4ab217f..2acc305 100644 --- a/voucher-vault/values.yaml +++ b/voucher-vault/values.yaml @@ -387,7 +387,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From b50e9d80dd943f8a123555c0e6dd1c615faf22ec Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:03 +0000 Subject: [PATCH 56/63] refactor(ytdlp-web-player): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- .../ytdlp-web-player.networkpolicy.yaml | 50 ++++++++++-- ytdlp-web-player/values.schema.json | 24 +++++- ytdlp-web-player/values.yaml | 80 ++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/ytdlp-web-player/templates/ytdlp-web-player.networkpolicy.yaml b/ytdlp-web-player/templates/ytdlp-web-player.networkpolicy.yaml index f8c8549..2f7d5bc 100644 --- a/ytdlp-web-player/templates/ytdlp-web-player.networkpolicy.yaml +++ b/ytdlp-web-player/templates/ytdlp-web-player.networkpolicy.yaml @@ -58,7 +58,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -94,7 +105,7 @@ spec: - ports: - protocol: TCP port: 5000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -108,11 +119,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -157,7 +179,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -175,11 +197,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/ytdlp-web-player/values.schema.json b/ytdlp-web-player/values.schema.json index 145490e..d4a0b71 100644 --- a/ytdlp-web-player/values.schema.json +++ b/ytdlp-web-player/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/ytdlp-web-player/values.yaml b/ytdlp-web-player/values.yaml index b536845..1b0a497 100644 --- a/ytdlp-web-player/values.yaml +++ b/ytdlp-web-player/values.yaml @@ -573,7 +573,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 7e9b1ed08f8d2e6edc1ca274d959e99147b52c24 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:03 +0000 Subject: [PATCH 57/63] refactor(zipline): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. --- zipline/templates/zipline.networkpolicy.yaml | 50 ++++++++++-- zipline/values.schema.json | 24 +++++- zipline/values.yaml | 80 +++++++++++++++++++- 3 files changed, 146 insertions(+), 8 deletions(-) diff --git a/zipline/templates/zipline.networkpolicy.yaml b/zipline/templates/zipline.networkpolicy.yaml index f6a85a2..e6d8788 100644 --- a/zipline/templates/zipline.networkpolicy.yaml +++ b/zipline/templates/zipline.networkpolicy.yaml @@ -49,7 +49,18 @@ selects. for "ingress"; one round of this work already mistook the second for an inbound escape hatch on the strength of the substring. */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. Clearing the probe-source list restores + the constant rather than removing the ipBlock, deliberately: the two + halves are not separable, and enabled: false is how you open nothing. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} +{{- $ambientProbeSources := $ambient.redirectedProbeSources | default (list "169.254.7.127/32") }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -85,7 +96,7 @@ spec: - ports: - protocol: TCP port: 3000 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* FIRST HALF OF networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not @@ -99,11 +110,22 @@ spec: second render, that 15008 stays the ONLY such port and that it is absent while the value is false. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- if $probeSources }} {{- /* @@ -147,7 +169,7 @@ spec: - ipBlock: cidr: {{ . | quote }} {{- end }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* SECOND HALF OF networkPolicy.ingress.ambientMesh, and the reason it is one value and not two. Inside the mesh the kubelet's probes are redirected and no longer arrive from the worker node's address: they @@ -165,11 +187,29 @@ spec: counterpart is deliberately absent - this cluster is SingleStack IPv4, and an ipBlock of the wrong family is accepted and matches nothing. + WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + it is LINK-LOCAL (169.254.0.0/16, RFC 3927), a range reserved for + node-local use and never routed; Istio's IPv6 counterpart + fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + plus 1337; and KUBERNETES DOES NOT KNOW THE ADDRESS - neither the + NetworkPolicy API nor the kubelet - only Istio's data plane + rewrites the probe source onto it. Read for ztunnel 1.30.6, and if + Istio changes it THE FAILURE IS SILENT: probes dropped, and only + where probeSources is non-empty, which is nowhere today. + + A LIST, not a string, and the one case where that does not + contradict "a constant, not a configuration": a DUAL-STACK cluster + needs BOTH entries at once. This cluster is SingleStack IPv4, so + the IPv6 counterpart is left out - an ipBlock of the wrong family + is accepted and matches nothing. Add it ALONGSIDE, never instead. + UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no release reaches this branch. CI proves its render, through the overlays in ci/networkpolicy. */}} + {{- range $ambientProbeSources }} - ipBlock: - cidr: 169.254.7.127/32 + cidr: {{ . | quote }} + {{- end }} {{- end }} {{- else }} {{- /* diff --git a/zipline/values.schema.json b/zipline/values.schema.json index 0fa9c02..c272a0b 100644 --- a/zipline/values.schema.json +++ b/zipline/values.schema.json @@ -51,8 +51,28 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + }, + "redirectedProbeSources": { + "description": "Sources the kubelet's probes arrive from once Istio has redirected them, default [169.254.7.127/32]. NOT the same value as networkPolicy.probeSources one level up: that one holds YOUR worker node CIDRs, is empty by default and means OPEN when empty, and the chart cannot know those addresses; this one holds ISTIO's rewrite address and ships filled, because unlike the node IPs it is a constant the chart can know. Recognisable as vendor magic rather than protocol: it is link-local (169.254.0.0/16, RFC 3927, never routed), Istio's IPv6 counterpart fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the same digits plus 1337, and Kubernetes itself knows the address in neither the NetworkPolicy API nor the kubelet. A list and not a string because a dual-stack cluster needs BOTH entries at once; this cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default - an ipBlock of the wrong family is accepted and matches nothing. If Istio changes the address the failure is SILENT: probes dropped, and only where probeSources is non-empty.", + "type": ["array", "null"], + "items": { + "type": "string" + } + } + } } } }, diff --git a/zipline/values.yaml b/zipline/values.yaml index 2f19ecd..497e7dd 100644 --- a/zipline/values.yaml +++ b/zipline/values.yaml @@ -281,7 +281,85 @@ networkPolicy: # IPs in istio-system (measured: no hostNetwork on the DaemonSet, Istio # 1.30.6), so if one proves necessary it is expressible as a # namespaceSelector plus podSelector and needs no unknowable node CIDR. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 + + # SOURCES THE KUBELET'S PROBES ARRIVE FROM ONCE ISTIO HAS REDIRECTED THEM. + # + # NOT THE SAME VALUE AS networkPolicy.probeSources one level up, and the + # two are easy to confuse, so: + # + # networkPolicy.probeSources + # YOUR worker node CIDRs. Empty by default, and empty means the + # probe rule is OPEN. The chart cannot know these addresses. + # networkPolicy.ingress.ambientMesh.redirectedProbeSources + # ISTIO's rewrite address. Shipped filled, because unlike the node + # IPs it is a constant the chart CAN know. Only rendered where the + # rule above is restricted, i.e. where probeSources is non-empty. + # + # WHERE 169.254.7.127 COMES FROM - vendor magic, not protocol: + # + # - LINK-LOCAL, 169.254.0.0/16 (RFC 3927): a range reserved for + # node-local use that is never routed - exactly the range one picks a + # vendor's magic address out of. + # - Istio's IPv6 counterpart gives it away: + # fd16:9254:7127:1337:ffff:ffff:ffff:ffff carries the SAME DIGITS + # (9254:7127 is 169.254.7.127) plus 1337. Numbers built that way are + # vendor constants, not protocols. + # - KUBERNETES DOES NOT KNOW THIS ADDRESS. Neither the NetworkPolicy API + # nor the kubelet has any notion of it; only Istio's own data plane + # rewrites the probe source onto it. + # + # Read for ztunnel 1.30.6. IF ISTIO CHANGES IT, THE FAILURE IS SILENT: + # probes would be dropped, and only where probeSources is non-empty - which + # is nowhere in this repository today - so nothing here and nothing in + # Kubernetes would report it. What to re-check then: the address Istio's + # release notes name for probe redirection, this list in all 30 charts, and + # the pinned expectation in ci/run-networkpolicy-test.sh. + # + # A LIST, not a string, and the one case where that does not contradict "a + # constant, not a configuration": on a DUAL-STACK cluster BOTH entries are + # needed at the same time, not one of two. This cluster is SingleStack + # IPv4, so the IPv6 counterpart is left out - an ipBlock of the wrong + # family is accepted by the API server and matches nothing. Add it + # ALONGSIDE, never instead, if the cluster gains IPv6. + # + # Clearing this list restores the default rather than removing the ipBlock, + # deliberately: the two halves of this switch are not separable. To not + # open the port at all, set enabled: false. + # + # UNPROVEN IN OPERATION: probeSources is empty in every chart here, so no + # release reaches this branch. CI proves the render, not the effect. + redirectedProbeSources: + - 169.254.7.127/32 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From fba03d13f979db036e71c4c1c0f81b076b420089 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:07 +0000 Subject: [PATCH 58/63] refactor(samba): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. No redirectedProbeSources here, in neither values.yaml nor the schema: this chart offers no networkPolicy.probeSources, so its probe rule is never source-restricted and no ipBlock is ever rendered. The key would reach no rule and read as a control that does nothing, which is worse than one that is absent. ci/run-networkpolicy-test.sh asserts that absence rather than tolerating it. --- samba/templates/samba.networkpolicy.yaml | 25 ++++++++++++++++--- samba/values.schema.json | 17 +++++++++++-- samba/values.yaml | 31 +++++++++++++++++++++++- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/samba/templates/samba.networkpolicy.yaml b/samba/templates/samba.networkpolicy.yaml index fad813c..6888a35 100644 --- a/samba/templates/samba.networkpolicy.yaml +++ b/samba/templates/samba.networkpolicy.yaml @@ -53,7 +53,15 @@ selects. thing as the top-level .Values.ingress (the Ingress object) - both match a search for "ingress". */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -95,7 +103,7 @@ spec: port: 445 - protocol: TCP port: 139 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not and will @@ -118,11 +126,22 @@ spec: restricted here, and 445 is additionally published as a NodePort. WHICH RULE IT JOINS: the only rule this policy has, the one carrying 445 and 139. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} egress: {{- /* diff --git a/samba/values.schema.json b/samba/values.schema.json index 34b2dcc..c080de6 100644 --- a/samba/values.schema.json +++ b/samba/values.schema.json @@ -23,8 +23,21 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + } + } } } }, diff --git a/samba/values.yaml b/samba/values.yaml index 5673954..905679a 100644 --- a/samba/values.yaml +++ b/samba/values.yaml @@ -162,7 +162,36 @@ networkPolicy: # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; # measured on this cluster is only which listeners ztunnel opens, and no # namespace carries istio.io/dataplane-mode yet. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 8afb0011a5fee0cd978cf67b8bdcfdf8162e5484 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:07 +0000 Subject: [PATCH 59/63] refactor(satisfactory): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. No redirectedProbeSources here, in neither values.yaml nor the schema: this chart offers no networkPolicy.probeSources, so its probe rule is never source-restricted and no ipBlock is ever rendered. The key would reach no rule and read as a control that does nothing, which is worse than one that is absent. ci/run-networkpolicy-test.sh asserts that absence rather than tolerating it. --- .../templates/satisfactory.networkpolicy.yaml | 25 +++++++++++++-- satisfactory/values.schema.json | 17 ++++++++-- satisfactory/values.yaml | 31 ++++++++++++++++++- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/satisfactory/templates/satisfactory.networkpolicy.yaml b/satisfactory/templates/satisfactory.networkpolicy.yaml index 42d129f..6b9c09d 100644 --- a/satisfactory/templates/satisfactory.networkpolicy.yaml +++ b/satisfactory/templates/satisfactory.networkpolicy.yaml @@ -61,7 +61,15 @@ selects. thing as the top-level .Values.ingress (the Ingress object) - both match a search for "ingress". */}} {{- $npIngress := .Values.networkPolicy.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -103,7 +111,7 @@ spec: port: {{ include "satisfactory.formatNumber" .Values.server.gamePort }} - protocol: TCP port: {{ include "satisfactory.formatNumber" .Values.server.messagingPort }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not and will @@ -127,10 +135,21 @@ spec: WHICH RULE IT JOINS: the only rule this policy has, the one carrying the game and messaging ports. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- end }} diff --git a/satisfactory/values.schema.json b/satisfactory/values.schema.json index 1b15f1c..daa5e9b 100644 --- a/satisfactory/values.schema.json +++ b/satisfactory/values.schema.json @@ -20,8 +20,21 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + } + } } } }, diff --git a/satisfactory/values.yaml b/satisfactory/values.yaml index 31fe972..ceaee15 100644 --- a/satisfactory/values.yaml +++ b/satisfactory/values.yaml @@ -219,5 +219,34 @@ networkPolicy: # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; # measured on this cluster is only which listeners ztunnel opens, and no # namespace carries istio.io/dataplane-mode yet. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 -- 2.54.0 From 073289c3b1a0b2ecd7ea3bf8c03d2ed8a7a57517 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:07 +0000 Subject: [PATCH 60/63] refactor(teamspeak-server): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. No redirectedProbeSources here, in neither values.yaml nor the schema: this chart offers no networkPolicy.probeSources, so its probe rule is never source-restricted and no ipBlock is ever rendered. The key would reach no rule and read as a control that does nothing, which is worse than one that is absent. ci/run-networkpolicy-test.sh asserts that absence rather than tolerating it. --- .../teamspeak-server.networkpolicy.yaml | 25 +++++++++++++-- teamspeak-server/values.schema.json | 17 ++++++++-- teamspeak-server/values.yaml | 31 ++++++++++++++++++- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/teamspeak-server/templates/teamspeak-server.networkpolicy.yaml b/teamspeak-server/templates/teamspeak-server.networkpolicy.yaml index ad462ba..f292e5f 100644 --- a/teamspeak-server/templates/teamspeak-server.networkpolicy.yaml +++ b/teamspeak-server/templates/teamspeak-server.networkpolicy.yaml @@ -54,7 +54,15 @@ selects. thing as the top-level .Values.ingress (the Ingress object) - both match a search for "ingress". */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -101,7 +109,7 @@ spec: - ports: - protocol: TCP port: {{ .Values.server.fileTransferPort | int64 }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not and will @@ -128,11 +136,22 @@ spec: OR over its rules, so which rule carries the port decides only which sources reach it - and an open TCP rule is the honest default while it is unknown where ztunnel dials from. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- /* THE WEB QUERY INTERFACE, and the one rule in this chart with real security diff --git a/teamspeak-server/values.schema.json b/teamspeak-server/values.schema.json index af54a82..7f4b471 100644 --- a/teamspeak-server/values.schema.json +++ b/teamspeak-server/values.schema.json @@ -30,8 +30,21 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + } + } } } }, diff --git a/teamspeak-server/values.yaml b/teamspeak-server/values.yaml index 2375b1d..a2d6fea 100644 --- a/teamspeak-server/values.yaml +++ b/teamspeak-server/values.yaml @@ -166,7 +166,36 @@ networkPolicy: # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; # measured on this cluster is only which listeners ztunnel opens, and no # namespace carries istio.io/dataplane-mode yet. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From f88e443a2e87188ba07da00753dad2564a2c7de6 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:07 +0000 Subject: [PATCH 61/63] refactor(minecraft-server): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. No redirectedProbeSources here, in neither values.yaml nor the schema: this chart offers no networkPolicy.probeSources, so its probe rule is never source-restricted and no ipBlock is ever rendered. The key would reach no rule and read as a control that does nothing, which is worse than one that is absent. ci/run-networkpolicy-test.sh asserts that absence rather than tolerating it. --- .../minecraft-server.networkpolicy.yaml | 40 ++++++++++++++++--- minecraft-server/values.schema.json | 17 +++++++- minecraft-server/values.yaml | 31 +++++++++++++- 3 files changed, 80 insertions(+), 8 deletions(-) diff --git a/minecraft-server/templates/minecraft-server.networkpolicy.yaml b/minecraft-server/templates/minecraft-server.networkpolicy.yaml index 4e4795e..8366395 100644 --- a/minecraft-server/templates/minecraft-server.networkpolicy.yaml +++ b/minecraft-server/templates/minecraft-server.networkpolicy.yaml @@ -57,7 +57,15 @@ selects. thing as the top-level .Values.ingress (the Ingress object) - both match a search for "ingress". */}} {{- $npIngress := $np.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -97,7 +105,7 @@ spec: - ports: - protocol: TCP port: {{ .Values.server.port | int64 }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not and will @@ -123,11 +131,22 @@ spec: and restricted to the namespace otherwise, and the two branches are separate rules in this template - so the port is added twice, or it would be missing from whichever branch a release happens to render. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- end }} {{- if not (eq .Values.server.service.type "NodePort") }} @@ -144,7 +163,7 @@ spec: - ports: - protocol: TCP port: {{ .Values.server.port | int64 }} - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not and will @@ -170,11 +189,22 @@ spec: and restricted to the namespace otherwise, and the two branches are separate rules in this template - so the port is added twice, or it would be missing from whichever branch a release happens to render. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} from: - namespaceSelector: diff --git a/minecraft-server/values.schema.json b/minecraft-server/values.schema.json index 2620365..8d9eb0a 100644 --- a/minecraft-server/values.schema.json +++ b/minecraft-server/values.schema.json @@ -30,8 +30,21 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + } + } } } }, diff --git a/minecraft-server/values.yaml b/minecraft-server/values.yaml index 34be1f1..6b1670e 100644 --- a/minecraft-server/values.yaml +++ b/minecraft-server/values.yaml @@ -386,7 +386,36 @@ networkPolicy: # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; # measured on this cluster is only which listeners ztunnel opens, and no # namespace carries istio.io/dataplane-mode yet. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 egress: # Additional outbound TCP ports. DNS (UDP and TCP 53) is always allowed and -- 2.54.0 From 320b4ef2c5430d805b289229a7e6b6b7f1c431e4 Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:49:07 +0000 Subject: [PATCH 62/63] refactor(valkey): ambientMesh becomes an object carrying Istio's two constants Gitea #143, maintainer's instruction. The HBONE port and the redirected probe source are ISTIO CONSTANTS, not standards, so they move from template literals into values with documented defaults - an operator can then correct them when Istio changes a number, without waiting for a chart release. One object under one "enabled", so the two halves still cannot be set separately; two values could drift apart, which is what this design exists to prevent. Every read is null-safe (#99), so a deleted object or one carrying only "enabled: true" falls back to the defaults declared here rather than dereferencing nil - the defect #136 shipped one value away. Provenance now written on each constant: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), its IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address - only Istio's data plane rewrites the probe source onto it. Both read for ztunnel 1.30.6, and a change in either fails silently. The exception in ci/run-networkpolicy-test.sh stays pinned to the numbers that test names, and additionally asserts this chart declares the same defaults - never "whatever the value says", which would let a mistyped port in through a configurable field. No version change: nothing is published yet, the default is still off, and the render at defaults is byte-identical - re-diffed against origin/main. No redirectedProbeSources here, in neither values.yaml nor the schema: this chart offers no networkPolicy.probeSources, so its probe rule is never source-restricted and no ipBlock is ever rendered. The key would reach no rule and read as a control that does nothing, which is worse than one that is absent. ci/run-networkpolicy-test.sh asserts that absence rather than tolerating it. --- valkey/templates/valkey.networkpolicy.yaml | 25 ++++++++++++++--- valkey/values.schema.json | 17 ++++++++++-- valkey/values.yaml | 31 +++++++++++++++++++++- 3 files changed, 67 insertions(+), 6 deletions(-) diff --git a/valkey/templates/valkey.networkpolicy.yaml b/valkey/templates/valkey.networkpolicy.yaml index 03a1ff6..c995c0d 100644 --- a/valkey/templates/valkey.networkpolicy.yaml +++ b/valkey/templates/valkey.networkpolicy.yaml @@ -36,7 +36,15 @@ a port that is genuinely closed to everything but the namespaces named below. thing as the top-level .Values.ingress (the Ingress object) - both match a search for "ingress". */}} {{- $npIngress := .Values.networkPolicy.ingress | default dict }} -{{- $ambientMesh := $npIngress.ambientMesh }} +{{- /* NULL-SAFE ON PURPOSE (#99): ambientMesh is an OBJECT, so every read goes + through a fallback reproducing THIS chart's declared defaults. An + operator who deletes the object, or writes only "enabled: true", must + land on the defaults and not in a nil dereference - #136 shipped exactly + that defect one value away, where the NOTES.txt branch dereferenced + .networkPolicy.egress directly. */}} +{{- $ambient := $npIngress.ambientMesh | default dict }} +{{- $ambientEnabled := $ambient.enabled }} +{{- $ambientPort := $ambient.port | default 15008 }} apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: @@ -73,7 +81,7 @@ spec: ports: - protocol: TCP port: 6379 - {{- if $ambientMesh }} + {{- if $ambientEnabled }} {{- /* networkPolicy.ingress.ambientMesh (Gitea #143). 15008/TCP is ztunnel's HBONE tunnel, the port every inbound connection arrives on once the namespace carries istio.io/dataplane-mode - ambient "does not and will @@ -104,10 +112,21 @@ spec: expressible as a namespaceSelector plus podSelector and not as an unknowable node CIDR. It cannot be settled before a namespace joins the mesh, and none has. + AN ISTIO CONSTANT, NOT A STANDARD: 15008 is not registered with + IANA for this, no protocol prescribes it, and Kubernetes knows + nothing about it - Istio picked it for HBONE and Istio can change + it. Hence a VALUE with a documented default rather than a literal + here, so an operator can correct it without a chart release. Read + for ztunnel 1.30.6. The exception ci/run-networkpolicy-test.sh + grants is pinned to the number THAT TEST names, plus a check that + this chart declares the same default - never to whatever the value + says, because that would let a mistyped port ride in through a + configurable field (design A of #143). + NOT MEASURED: that 15008 alone suffices comes from Istio's documentation. Measured on this cluster is only which listeners ztunnel opens. Provable at the first namespace to join the mesh; none has. */}} - protocol: TCP - port: 15008 + port: {{ int $ambientPort }} {{- end }} {{- end }} diff --git a/valkey/values.schema.json b/valkey/values.schema.json index 3a09beb..b173b5b 100644 --- a/valkey/values.schema.json +++ b/valkey/values.schema.json @@ -114,8 +114,21 @@ "additionalProperties": false, "properties": { "ambientMesh": { - "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while it is false. Renders 15008/TCP inbound - ztunnel's HBONE tunnel, which ambient explicitly does not let bypass NetworkPolicy enforcement - AND, where probeSources is non-empty, 169.254.7.127/32 as an additional probe source, because inside the mesh the kubelet's probes are redirected away from the worker node's address. The two halves are inseparable on purpose: whoever opens 15008 by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Unproven in operation: that 15008 alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", - "type": ["boolean", "null"] + "description": "Opens the pod for Istio's ambient mesh; off by default and additive, so the render is byte-identical while enabled is false. Renders the HBONE port inbound AND, where networkPolicy.probeSources is non-empty, the redirected probe source in the same \"from\" list. The two halves live in one object under one switch on purpose: whoever opens the port by hand has not added the probe address, and the first narrowed probeSources then drops every probe, so the pod never becomes Ready and the symptom reads as a broken application rather than as a policy. Both numbers are ISTIO CONSTANTS rather than standards, which is why they are values with documented defaults and not template literals - a vendor constant can change, and an operator should be able to correct it without waiting for a chart release. Deleting the whole object is safe: every read falls back to the defaults the chart declares. Unproven in operation: that the HBONE port alone suffices comes from Istio's documentation, and no namespace of this cluster carries istio.io/dataplane-mode yet.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "enabled": { + "description": "The switch. Everything else in this object is inert while it is false.", + "type": ["boolean", "null"] + }, + "port": { + "description": "Istio's HBONE port, default 15008. A constant Istio chose, not a standard: not registered with IANA for this, prescribed by no protocol, and unknown to Kubernetes. Read for ztunnel 1.30.6. It is the one ingress port of these charts that is deliberately no containerPort - ztunnel terminates it, not the application - and the exception ci/run-networkpolicy-test.sh grants it is pinned to the number THAT TEST knows, plus an assertion that this chart declares the same default. Not to whatever this value says: a value-driven exception would let a mistyped port ride in through a configurable field.", + "type": ["integer", "null"], + "minimum": 1, + "maximum": 65535 + } + } } } }, diff --git a/valkey/values.yaml b/valkey/values.yaml index 698703e..6909b32 100644 --- a/valkey/values.yaml +++ b/valkey/values.yaml @@ -313,5 +313,34 @@ networkPolicy: # NOT PROVEN: that 15008 alone is enough comes from Istio's documentation; # measured on this cluster is only which listeners ztunnel opens, and no # namespace carries istio.io/dataplane-mode yet. - ambientMesh: false + # BOTH NUMBERS BELOW ARE ISTIO CONSTANTS, NOT STANDARDS, and they are values + # rather than template literals for exactly that reason: a vendor constant + # can change, and an operator should be able to correct it without waiting + # for a chart release. Their provenance is written on each one. + # + # DELETING THIS OBJECT IS SAFE. Every read falls back to the defaults + # declared here (#99), so "ambientMesh: null", or an object carrying only + # "enabled: true", renders the documented defaults instead of failing. + ambientMesh: + # The switch. Everything else here is inert while it is false, and the + # chart renders exactly as it did before the object existed. + enabled: false + + # ISTIO'S HBONE PORT. A constant Istio chose, not a standard: not + # registered with IANA for this, prescribed by no protocol, and unknown to + # Kubernetes. Read for ztunnel 1.30.6 (measured on this cluster: ztunnel + # opens [::]:15008 as its inbound listener). + # + # If Istio changes it, a pod in the mesh cannot be reached. Change it here + # in all 30 charts AND in the pinned expectation inside + # ci/run-networkpolicy-test.sh - the test is deliberately what forces + # someone to look, because the exception it grants this port is pinned to + # the number IT knows and not to whatever this value says. A value-driven + # exception would let a mistyped port through, which is the hole design A + # of #143 was rejected for. + # + # NOT MEASURED: that this port ALONE suffices comes from Istio's + # documentation. Measured here is only which listeners ztunnel opens, and + # no namespace carries istio.io/dataplane-mode yet. + port: 15008 -- 2.54.0 From 95e0722d0c14eabaf3b60ada511c611fe5f4382e Mon Sep 17 00:00:00 2001 From: Justin Klein Date: Mon, 19 Oct 2026 17:53:55 +0000 Subject: [PATCH 63/63] docs(README): the provenance of Istio's two constants, and the pinned expectation that keeps the exception narrow Gitea #143. Section 15 gains the object shape and - the part the previous round explained the coupling of but never attributed - where both numbers come from: 15008 is Istio's HBONE port, not IANA-registered for this and unknown to Kubernetes; 169.254.7.127 is link-local (RFC 3927, never routed), Istio's IPv6 counterpart carries the same digits plus 1337, and neither the NetworkPolicy API nor the kubelet knows the address. Both read for ztunnel 1.30.6, with the silent failure mode and what to re-check spelled out. The render-test section gains the pinned expectation and why it is pinned: once the port is configurable, checking the exception against the rendered value permits whatever the value says, which is design A's hole under design C's name. Six things on two renders plus the declared defaults, ten fail paths executed. And a fourth name joins the substring table, whose confusion risk is the larger one: networkPolicy.probeSources holds YOUR node CIDRs and means OPEN when empty, while ambientMesh.redirectedProbeSources holds ISTIO's rewrite address and ships filled. --- README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index f1a3b95..74bf255 100644 --- a/README.md +++ b/README.md @@ -231,7 +231,7 @@ Measured locally: **30 charts, 408 scalars verified, 0 failed**, no cluster. The | no ingress rule allows every source by accident | `from: []` allows everything, identically to having no `from` at all, while reading in a diff and in `kubectl` output as a restriction — the one mistake in this class that makes a policy *weaker* while looking *stricter* | nobody | | every port a **network** probe addresses is either unrestricted, or the chart offers `networkPolicy.probeSources` **and** says in `NOTES.txt` what a wrong CIDR costs | the kubelet probes from the IP of the worker node running the pod, which no chart can know. A restricted probe port without that address drops every probe: the pod never becomes `Ready`, the rollout times out, and the symptom reads as a broken application | the install-test, in production's CNI — **but not in CI**, see below | | **cross-chart egress**: where a chart dials another chart *of this repository*, its egress rule must contain the **pod** ports behind that chart's Services (#136) | an egress rule naming the **Service** port. kube-proxy does the ClusterIP's destination NAT *before* the policy is evaluated at the pod's interface, so the policy sees `podIP:targetPort` and the rule matches nothing wherever `port` and `targetPort` differ | nobody: the symptom is a **timeout**, not `connection refused`, and no error mentions a NetworkPolicy — `paperless-ngx` 14.0.0 shipped it and cost an outage on 2026-10-15 | -| **the ambient-mesh switch**, on two renders per chart: with `networkPolicy.ingress.ambientMesh` off, neither `15008` nor `169.254.7.127/32` appears; with it on, `15008` is present and is still the **only** ingress port that is no `containerPort`, and a source-restricted rule carrying it also carries the `ipBlock` (#143) | the named exception decaying into the hole it was carved around. Without the off-half a chart could render `15008` unconditionally and nothing would notice; without the only-port half the exception becomes the free port key that was rejected, and every mistyped ingress port passes with the switch on | nobody — and in production as a pod unreachable in the mesh, or one whose every probe is dropped the first time `probeSources` is narrowed | +| **the ambient-mesh switch** (#143), on two renders per chart plus the chart's declared defaults: those defaults equal the port and `ipBlock` the test **pins**; with the switch off neither appears; with it on the pinned port is present and is still the **only** ingress port that is no `containerPort`, and a source-restricted rule carrying it also carries the `ipBlock` | the named exception decaying into the hole it was carved around. Without the off-half a chart could render the port unconditionally and nothing would notice; without the only-port half the exception becomes the free port key that was rejected; and **without the pin** — now that the port is a value — the exception would permit whatever the value said, so a typo would ride in through a configurable field | nobody — and in production as a pod unreachable in the mesh, or one whose every probe is dropped the first time `probeSources` is narrowed | Egress ports are otherwise **deliberately not** held against the pod's `containerPort`s: an egress port is a port on the *destination*, and that comparison would fail every chart that reaches a database. @@ -248,7 +248,7 @@ Four things to know before touching it: Measured locally: **30 charts, 333 assertions held, 0 failed**, no cluster, ~25 seconds (the ambient-mesh assertion added a second render per chart, and roughly 10 seconds with it). All twelve fail paths were exercised rather than assumed — and one earned its keep immediately: the `probeSources` check was a substring test, so a chart whose key had been **deleted** while the long comment above it still discussed the value passed it. It now looks for the key. The cross-chart assertion added two of those 210, and its own six fail paths were executed the same way: a required port removed from the rule, a wrong number in its place, a peer chart renamed away, the policy's name no longer following the `--` convention, the peers rendering no Service at all (which would make the requirement empty, and is treated as a failure because an empty requirement reads exactly like a satisfied one), and the gated rule made unconditional — which is the failure path of the *gate-off* half, since with the gate off and the rule properly gated the assertion must stay silent. -**The ambient-mesh assertion (#143) was built the same way, and six of its fail paths were executed**: a second, different port added under the exception (rejected — the exception covers exactly one number); `15008` rendered ungated, caught by assertion 2 in the main loop, which renders the switch at its default; the `ipBlock` rendered ungated, which **only** this assertion can catch, because assertion 2 never looks at `from` peers; the `ipBlock` half deleted while `15008` stayed, i.e. the half-set switch the design exists to make impossible, reachable because `ci/networkpolicy/template-chart.yaml` fills `probeSources`; the policy's name taken off the `--` convention; and the value present in the schema but absent from `values.yaml`, which a `--set` render alone would not have noticed. The name check earned its keep on the first run rather than in a rehearsal: it read the **directory** name, and `template-chart`'s chart is called `xxx`. +**The ambient-mesh assertion (#143) was built the same way, and ten of its fail paths were executed**: a second, different port added under the exception (rejected — the exception covers exactly one number); `15008` rendered ungated, caught by assertion 2 in the main loop, which renders the switch at its default; the `ipBlock` rendered ungated, which **only** this assertion can catch, because assertion 2 never looks at `from` peers; the `ipBlock` half deleted while `15008` stayed, i.e. the half-set switch the design exists to make impossible, reachable because `ci/networkpolicy/template-chart.yaml` fills `probeSources`; the policy's name taken off the `--` convention; the value present in the schema but absent from `values.yaml`, which a `--set` render alone would not have noticed; a chart whose **declared default port deviates** (three independent checks fire at once: the declared-defaults comparison, the pinned port missing from the render, and the deviating port not being covered by the exception); a port-only chart **declaring `redirectedProbeSources`**, which reaches no rule there; a **deleted `ambientMesh` object** and one carrying only `enabled: true` (both must *render*, on the chart's own defaults — and the guards that make that work were each shown to be load-bearing by removing them); and a **release-level port override**, which must render and does, while the pinned exception demonstrably does **not** stretch to cover it. The name check earned its keep on the first run rather than in a rehearsal: it read the **directory** name, and `template-chart`'s chart is called `xxx`. **What this test does not and cannot cover, named so the gap is not mistaken for coverage:** that traffic still flows. No ingress controller runs in the install-test cluster, so the path from Traefik to a pod is proven nowhere in this pipeline — and, measured on the PR for #5, **k3s exempts the kubelet's probe traffic from policy**, so a blocked probe path does *not* turn the install-test red either (`cyberchef` became `Ready` in 69s with its probe port restricted to an address no probe can come from). What the install-test *does* prove is enforcement of everything the application itself needs: with `outline`'s DNS egress rule removed, its pod never became `Ready` and the job failed after 5m59s. So the net under this convention is real, but it is an **egress** net — which raises the stakes for assertion 2, since a wrong ingress port is caught mechanically here or not at all. @@ -696,9 +696,18 @@ Every port of a workload falls into one of four, and this is the part to get rig #### Values -`networkPolicy.enabled`, `.allowedNamespaces`, `.probeSources`, `.ingressController`, `.ingress.ambientMesh`, `.egress.extraPorts` — and a chart ships **only the ones that reach a rule**, with `.enabled` and `.ingress.ambientMesh` the two every chart has. +`networkPolicy.enabled`, `.allowedNamespaces`, `.probeSources`, `.ingressController`, `.ingress.ambientMesh.*`, `.egress.extraPorts` — and a chart ships **only the ones that reach a rule**, with `.enabled` and `.ingress.ambientMesh` the two every chart has. -**Three of those names overlap as substrings and are three different things**, which is worth stating because a search for `ingress` matches all of them: top-level `ingress` is the Ingress **object** a chart renders, `networkPolicy.ingress` is the policy's **inbound side**, and `networkPolicy.ingressController` is the **source selector** for the controller's pods — a *sibling* of `networkPolicy.ingress` and no part of it. The 21 charts carrying `ingressController` had **no** inbound escape hatch before #143, and one search run during that work reported them as having one on the strength of the substring alone. A chart may add a **named** egress switch where a port depends on something the values cannot see: `paperless-ngx`' `.egress.mailImport` opens IMAPS `993`, off by default, because its mail accounts are rows in its own database and no value says whether a release fetches mail. The bar for one of those: it names what it opens, and it is **not** a second switch for a port some *other* value already decides — that is why the Tika/Gotenberg rule stayed gated on `paperless.files.tikaGotenberg.enabled` and got no policy switch of its own. Two values that can drift apart re-arm the outage the rule was fixed for. A value that silently does nothing is worse than one that is absent, because it reads as a control: `samba` and `satisfactory` carry `enabled` alone (every inbound rule is open to any source), and `teamspeak-server` has `allowedNamespaces` but no `probeSources`. +**Three of those names overlap as substrings and are three different things**, which is worth stating because a search for `ingress` matches all of them: top-level `ingress` is the Ingress **object** a chart renders, `networkPolicy.ingress` is the policy's **inbound side**, and `networkPolicy.ingressController` is the **source selector** for the controller's pods — a *sibling* of `networkPolicy.ingress` and no part of it. The 21 charts carrying `ingressController` had **no** inbound escape hatch before #143, and one search run during that work reported them as having one on the strength of the substring alone. + +**And a fourth name, whose confusion risk is the larger one**, because these two are both lists of probe sources and sit two levels apart: + +| Name | Whose addresses | Default | Empty means | +| --- | --- | --- | --- | +| `networkPolicy.probeSources` | **yours** — the worker node CIDRs, which no chart can know | `[]` | the probe rule is **open** | +| `networkPolicy.ingress.ambientMesh.redirectedProbeSources` | **Istio's** — the address its data plane rewrites the probe source onto | `[169.254.7.127/32]`, i.e. shipped *filled*, because unlike the node IPs it is a constant the chart **can** know | n/a — clearing it restores the default, deliberately: the two halves of the switch are not separable | + +It is named `redirectedProbeSources` and not `probeSources` for exactly that reason: one is where probes come from normally, the other where they come from *after Istio redirects them*. A chart may add a **named** egress switch where a port depends on something the values cannot see: `paperless-ngx`' `.egress.mailImport` opens IMAPS `993`, off by default, because its mail accounts are rows in its own database and no value says whether a release fetches mail. The bar for one of those: it names what it opens, and it is **not** a second switch for a port some *other* value already decides — that is why the Tika/Gotenberg rule stayed gated on `paperless.files.tikaGotenberg.enabled` and got no policy switch of its own. Two values that can drift apart re-arm the outage the rule was fixed for. A value that silently does nothing is worse than one that is absent, because it reads as a control: `samba` and `satisfactory` carry `enabled` alone (every inbound rule is open to any source), and `teamspeak-server` has `allowedNamespaces` but no `probeSources`. **No chart ships `allowedNamespaces` pre-filled, ever.** A chart that named `mc-router`, or `paperless-jk`, or `stalwart` in its defaults would be baking in an assumption about *this* cluster's namespace layout — which is exactly the kind of claim a chart cannot keep true. So the default is the empty list and the chart is **restrictive**; filling it is the **deployment adaptation**, made where the namespace layout is actually visible. That is why a sweep like this produces a list of release-side one-liners: those are the expected procedure, not a defect list, and the breaking-change major on each chart is what announces them. @@ -708,7 +717,30 @@ Every port of a workload falls into one of four, and this is the part to get rig #### The ambient-mesh switch, and the one named exception in the render test (#143) -**`networkPolicy.ingress.ambientMesh`, in all 30 charts, default `false`.** Istio runs on this cluster in ambient mode, and Istio's own documentation is explicit that *"ambient does not and will never bypass Kubernetes `NetworkPolicy` enforcement"* — so until this value existed, **no chart here could put a pod into the mesh at all**. Additive, unchanged default, unchanged render: **minor** per chart, and the render was diffed against the previous release per chart rather than asserted. +**`networkPolicy.ingress.ambientMesh`, in all 30 charts, switched off by default.** Istio runs on this cluster in ambient mode, and Istio's own documentation is explicit that *"ambient does not and will never bypass Kubernetes `NetworkPolicy` enforcement"* — so until this value existed, **no chart here could put a pod into the mesh at all**. Additive, unchanged default, unchanged render: **minor** per chart, and the render was diffed against the previous release per chart rather than asserted. + +**The shape, and why both numbers are values rather than literals:** + +```yaml +networkPolicy: + ingress: + ambientMesh: + enabled: false + port: 15008 + redirectedProbeSources: + - 169.254.7.127/32 +``` + +**Both numbers are Istio *constants*, not standards** — and a value with a documented default is the better home for a vendor constant than a template literal, because an operator can correct it when Istio changes the number without waiting for a chart release. Their provenance, written on each one in every chart rather than assumed: + +- **`15008`** is Istio's HBONE port. Not registered with IANA for this, prescribed by no protocol, and **unknown to Kubernetes**. Istio picked it; Istio can change it. +- **`169.254.7.127`** is the address Istio's data plane rewrites the kubelet's probe source onto, and it is recognisable as vendor magic rather than protocol three ways: it is **link-local**, `169.254.0.0/16` (RFC 3927), a range reserved for node-local use and **never routed** — exactly the range one picks a magic address out of; Istio's IPv6 counterpart `fd16:9254:7127:1337:ffff:ffff:ffff:ffff` carries the **same digits** (`9254:7127` *is* `169.254.7.127`) plus `1337`, which is what vendor constants look like; and **Kubernetes knows the address in neither the NetworkPolicy API nor the kubelet** — only Istio's own data plane does. + +**Both read for ztunnel 1.30.6.** If Istio changes either, **the failure is silent**: a pod unreachable inside the mesh, or — for the address — probes dropped, and only where `probeSources` is non-empty, which is nowhere today. Nothing in this repository and nothing in Kubernetes would report it. What to re-check then: the port and address Istio's release notes name, this default in all 30 charts, **and** the pinned expectation in `ci/run-networkpolicy-test.sh`. + +`redirectedProbeSources` is a **list** and not a string, and this is the one case where that does not contradict "the value is a constant, not a configuration": a **dual-stack** cluster needs **both** of Istio's addresses at the same time, not one of two. That is a nameable case rather than speculation. This cluster is SingleStack IPv4, so the IPv6 counterpart is left out of the default — an `ipBlock` of the wrong family is accepted by the API server and matches nothing — and it is documented as the addition to make *alongside*, never *instead*. + +**Deleting the object is safe**, per the null-safety rule (#99): every read goes through a fallback reproducing the chart's own declared defaults, so `ambientMesh: null`, or an object carrying only `enabled: true`, renders the documented defaults. Both guards are load-bearing and were executed, not assumed — the direct chained read `$npIngress.ambientMesh.enabled` fails with `nil pointer evaluating interface {}.enabled` on a deleted object, which is the shape of the defect #136 shipped one value away; and without the port fallback a nilled `port` renders **`port: 0`**, an accepted and silently wrong policy. **It renders two things, and that they are one value is the whole design.** @@ -719,7 +751,14 @@ Point 2 is the one nobody thinks of, which is why the two halves cannot be had s **The interesting part is the CI, because the obvious implementation was the worst one.** Assertion 2 of `ci/run-networkpolicy-test.sh` holds every ingress port against the selected pod spec's `containerPort`s, and `15008` is deliberately none — ztunnel terminates it, not the application. A free `ingress.extraPorts` key would have had to exempt *operator-supplied ports* from that assertion, which opens it for **all 30 charts**: every mistyped ingress port, the NodePort mistake included, would then pass. So the assertion keeps its edge and gets **one named exception for one port number, gated on one value** — and it is not granted in the main loop at all, which renders every chart with the value at its default, so `15008` stays an ordinary failure there. -**An exception without its own check is the hole it was carved around**, so assertion 7 holds it to five things on **two renders of every chart**: with the switch off, neither `15008` nor the `ipBlock` is present; with it on, `15008` is present, `15008` is still the **only** ingress port that is not a `containerPort`, and wherever the rule carrying it restricts sources it carries the `ipBlock` too. The policy is found by the `--` name convention, because an exception must not be granted to an object the test could not identify. It lives in the existing script rather than a new one for the reason #136's cross-chart assertion does, only more so: **the exception weakens assertion 2, so it belongs beside it** — a separate script can be green while the exception it is paired with is wrong, and nothing would connect the two. +**And since the port is a value, the exception cannot be checked against the rendered number.** "Whatever the value says" is design A's hole wearing design C's name: a mistyped port would pass simply by sitting in the configurable field. So **the expectation is pinned in the test** — `AMBIENT_PORT` and `AMBIENT_CIDR` — and assertion 7 checks two things against that statement: + +1. the render **at chart defaults** produces exactly that port and exactly that `ipBlock`, and +2. **every chart's `values.yaml` declares the same defaults**, read back through `ci/yaml2json` rather than by regex — because a substring test over a commented file is how the `probeSources` check was once fooled by a deleted key whose comment survived. + +**A chart that drifts is red.** If Istio changes a constant, it takes 30 values files **and** the two pinned lines — and the pinned lines are what force someone to look. That block is therefore also the single place in the repository that states what we hold Istio's numbers to be, which makes it the answer to "where does `169.254.7.127` come from" as well; the provenance is written there. The renders set **only** `enabled=true` and never a port override, deliberately: the pin is a statement about the defaults *we ship*, not about what a release may choose. An operator overriding the value in their own release is outside this test's reach, as is true of every value here — and that is not a new gap. + +**An exception without its own check is the hole it was carved around**, so assertion 7 holds it to six things on **two renders of every chart**: the declared defaults match the pin; with the switch off, neither the pinned port nor the `ipBlock` is present; with it on, the pinned port is present, it is still the **only** ingress port that is not a `containerPort`, and wherever the rule carrying it restricts sources it carries the `ipBlock` too. The probe-source half is conditional on the chart offering `probeSources`: the five charts whose probe rule is never source-restricted render no `ipBlock`, so for them the key must be **absent** — a value reaching no rule reads as a control that does nothing — and that absence is asserted rather than tolerated. The policy is found by the `--` name convention, because an exception must not be granted to an object the test could not identify. It lives in the existing script rather than a new one for the reason #136's cross-chart assertion does, only more so: **the exception weakens assertion 2, so it belongs beside it** — a separate script can be green while the exception it is paired with is wrong, and nothing would connect the two. **Two things this does not prove, and they are not footnotes.** That `15008` *alone* suffices comes from Istio's documentation; what is measured on this cluster is only which listeners ztunnel opens. And the probe-source half reaches no release, because `probeSources` is empty in every chart (`stalwart` sets it to `[]` explicitly) — CI proves its render through the overlays in `ci/networkpolicy`, not its effect. Both become provable at the first namespace to join the mesh, and **none carries `istio.io/dataplane-mode`** (measured 2026-10-19, 0 of 97). Whether ztunnel needs a source selector of its own is open for the same reason — but it is *not* the unknowable case it looked like: ztunnel runs with its own pod IPs in `istio-system` (no `hostNetwork` on the DaemonSet, Istio 1.30.6), so a peer for it would be a `namespaceSelector` plus `podSelector`, not a node-IP `ipBlock`. -- 2.54.0