NetworkPolicies in every chart (Gitea #5): the convention, the gate, and what it does not do
29 chart pages plus Chart-Style-Guide, CI-CD, Charts and Lessons-Learned.
Chart-Style-Guide gains section 15, and its core is the FOUR PORT CLASSES -
the part that is actually hard to get right. A port a network probe addresses
stays open until probeSources is set, because the kubelet probes from the
worker node's IP and no chart can know it. A NodePort-published port stays open
because the source is already masqueraded. Everything else can be and is
restricted by source, which is why the charts with a real inbound restriction
are exactly those whose probes use a different port. A replica-to-replica bus
gets a bare podSelector - and, since policyTypes lists Egress, its outbound
side needs a rule of its own or the cluster never forms while every node looks
healthy.
The exec-probe distinction is written down as load-bearing rather than
pedantic: it is the whole reason valkey and minecraft-server are the tightest
policies in the repository and every HTTP-probed web app is looser.
CI-CD gains the networkpolicy job as section 8 (the later two renumbered), and
it ends with the measurement rather than a claim. Two experiments ran on the PR
and were reverted: cyberchef's probe port restricted to an address no probe can
come from stayed GREEN at 1m9s, and outline with its DNS egress rule removed
went RED at 5m59s. Together those say k3s exempts the kubelet's probe traffic
while enforcing everything else - so the install-test's net is an EGRESS net,
and a wrong ingress port is caught by the render assertion or not at all.
Every chart page gets a networkPolicy values table and an operating note naming
that chart's own trap, not a boilerplate one:
- keycloak carries the blast radius: it is the cluster's identity provider, its
http rule is the only source-restricted inbound rule in the repo, and nothing
in CI can catch a wrong controller - so the page asks for a real login and a
look at consumers' issuer URLs after the first upgrade.
- garage asks the reader to check their S3 consumers, because anything in
another namespace is blocked until it is in allowedNamespaces.
- bulwark names the quietest failure in the sweep: its probes test its own port,
so a jmapServerUrl on a blocked port yields a Ready pod serving an empty
webmail.
- gotenberg explains what its DNS-only cap COSTS - HTML conversion silently
loses remote assets - rather than only what it buys.
- satisfactory and homeassistant each argue their own Ingress-only policy;
satisfactory's deciding half is that it is the one chart with no install-test
at all, so an egress cap there would be the only one with no empirical check
behind it.
- paperless-ngx names the two pods of its release that the object does NOT
cover, including the consume CronJob whose pod template carries no labels.
- freesailarr explains why ten containers behind a VPN need only three egress
rules, and that extraPorts cannot help with an alternative WireGuard port
because it is TCP-only.
valkey and stalwart are corrected rather than extended. valkey's page now says
why having no probeSources is what makes it the tightest policy here and not an
incomplete one. stalwart's carries a dated correction: this page and the chart
both advised putting the ingress controller's namespace into allowedNamespaces
with ["ingress-nginx"] as the example, and both halves are wrong - ingress-nginx
is gone from the cluster entirely, and allowedNamespaces would have opened http
to every pod in that namespace anyway.
Charts drops the claim that valkey is "the first chart with a NetworkPolicy on
by default" and says instead that every chart has one, with the two shapes a
reader needs before opening any chart page.
Lessons-Learned gains four, all transferable:
- the net that existed for a different failure than the one it was built for,
and why both experiments were needed before either could be read;
- a comment that could not describe its own syntax (a template comment must not
spell out its own closing delimiter; the render then fails pointing at the
template, not at the sentence);
- three values that reached no rule and would have read as controls - a
convention about value NAMES is not a convention about which values exist;
- the subchart whose comments the parent could not fix, and the rule it
produced: before making a check a gate, ask which failures the author of this
diff can actually fix.
paperless-ngx: the optional valkey subchart, and two findings it produced
paperless-ngx (Gitea #78) takes our own valkey chart as an optional subchart,
off by default. Its page gets the two new value blocks, the five Redis
consumers that argue the queue-vs-cache choice, the three operator traps and
the CI section, which now describes the first real installation of the
valkey-subchart path anywhere here.
Two findings are general enough to leave the chart page:
- A rule about the relationship between a parent's and a subchart's values
cannot live in a template. Helm renders a subchart's templates first, so the
subchart's own required() always wins and reports the path as the subchart
sees it. Schema validation runs before any rendering, so that is where such a
rule goes. The first draft was a fail() in the parent that could never fire.
- Erasing a dependency switch turns the subchart ON: a null override deletes
the key, an unresolvable condition counts as enabled. The one block in a
chart that must not be null-safe, and the opposite of the rule for every
other block.
Both in Chart-Style-Guide section 14 and Lessons-Learned; valkey's page names
its second consumer and the queue/cache pair of worked examples; Charts gets
the row.
stalwart: the subchart is our own valkey chart, from the published index
Follows the decision on #65 to depend on our published valkey chart rather
than upstream's. Four pages, and the choice of pages is deliberate.
stalwart.md: the subchart section rewritten - our chart, not upstream's, so
"consumed as it comes" no longer applies; the condition is
valkeySubchart.enabled with the subchart's own error message quoted and the
reason it only appears once the dependency is switched on; the auth-conf field
is a complete "requirepass <pw>" line, not a password; and the cache-vs-queue
choice argued with its cost (allkeys-lru makes the rate limiters and the
auto-ban fail open under memory pressure - size memory, do not switch to
noeviction). Plus a section on the PR-ordering rule.
valkey.md: the same rule, stated FROM THIS SIDE. This is the page someone
editing valkey/ will actually be reading, and this repo's own lesson is to put
the warning where the next change will be made. It also warns against the
tempting repair of adding an "enabled" property here for a key this chart does
not read.
Chart-Style-Guide.md, section 14: two charts carry a dependency now, and point
2 ("we consume their chart as it comes") explicitly does not apply when the
subchart is ours. Adds the PR-ordering rule, the condition-key trap, and the
two measurements that back the published-version form.
Lessons-Learned.md: two entries - the green check that tested the wrong
artifact, and the condition key the subchart rejected (including the part that
makes it dangerous: Helm skips a disabled subchart's validation, so CI stays
green and the error waits for the first operator who enables it). Three lines
in "The recurring shape".
valkey: name the two Trivy exceptions on the CI section
Both are keyword matches on the ConfigMap's comments and shell code, not on
data - and a HIGH 'ConfigMap stores secrets' on the one chart built to keep
the password out of everything Helm renders is exactly the finding a reader
should not take at face value.
valkey: chart page, index row, sidebar entry, two lessons
The new valkey chart (helm-charts #53, issue #46): one instance per
application instead of the shared cluster-redis, whose separation rested
on a single ACL file in a single 1Password field.
The page carries what an operator cannot read off the templates: that the
1Password field auth-conf holds a finished "requirepass <password>" line
rather than a password, that uid 999 / gid 1000 are the image's own user
and why fsGroup has to match them, that maxmemory is derived from the
computed memory limit and a value above it aborts the render, and that
persistence and the eviction policy are a pair rather than two switches.
Also what the install-test does not cover: one variant per chart, and
fsGroup itself is unproven on k3d's node-directory volumes.
Two Lessons-Learned entries, both from this chart's review rather than
from an outage, because both generalize: a CLI that prints an error and
exits 0 (valkey-cli without -e, so the natural ping probe cannot fail),
and a secret whose FORMAT is load-bearing - a present-but-malformed field
yields a running server with its protection off.