bulwark
Justin Klein edited this page 2026-10-14 14:57:00 +00:00
Clone

Wiki Page Revisions

4 Commits

Author SHA1 Message Date
jklein 81980279c4 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.
2026-10-14 14:57:00 +00:00
jklein 6c1c01453e The chart bump follows the app bump (Gitea #128)
The decision from issue #128: app major -> chart major, app minor ->
chart MINOR, patch/digest -> chart patch, as a maximum against the
chart's own classification rather than a replacement. The level comes
from Renovate's update type, since an appVersion comparison is blind to
the update that prompted it - freesailarr's appVersion never moves.

Versioning carries the rule, Renovate the hook and its measurements,
Workflow the wiki exception where "plain" is now mechanically defined,
CI-CD the new bump-level job, and Lessons-Learned the decision together
with its limit: the reading duty was rejected, so the incident that
lesson is about stays repeatable and the honest number is the whole of
the remedy.

Chart-Style-Guide, ytdlp-web-player, bulwark and stalwart each carried
a sentence calling the hook's result a chart patch, which it no longer
always is.
2026-10-13 18:48:54 +00:00
jklein 2adfe91a52 bulwark: the Gateway API as an alternative to the Ingress (#114)
One route, one rule, at the root - and three expectations #114 had of this
chart that it does not meet, written down because they are what somebody will
copy from. It has ONE Ingress object rather than two; it has NO rewrite-target
and cannot have one, because NEXT_PUBLIC_BASE_PATH is compiled into the
bundle; and the #118 prefix/redirect assurance therefore has no precondition
to meet here. Every occurrence of the string rewrite-target in the chart is an
explanation of why the annotation does not help, which is how a grep produced
the opposite impression.

The loss in Gateway mode is proxy-body-size: 25m, measured on the live Ingress
on 2026-10-12 and set by the operator rather than by the chart - values.yaml
only recommends it. The Gateway API has no body-size field, so the limit
becomes the Gateway's, and the failure arrives as a 413 from the proxy after
the user has written the mail and attached the file.

Corrected claim: this page said nothing outside the Ingress template
functionally reads ingress.domain. The HTTPRoute reads it, so gating the
Ingress off frees clusterIssuer but frees domain only with both modes off.
2026-10-12 21:30:26 +00:00
jklein 996f8c0dde bulwark: new chart page, index, sidebar, stalwart cross-reference, and four rules it bought
The chart page leads with the two things that are not visible from outside and
that govern everything else: Bulwark is a pure CLIENT, so with the mail server
gone the release is Ready and green and nothing in it can tell; and the VOLUME
BEATS THE CHART for every value, silently, because the application resolves
"admin override > env var > default" and its admin dashboard may write any key.
Upstream documents the second only for the server URL, which is what makes it a
trap rather than a footnote.

Also on the page, each measured against the source at tag 1.12.0 rather than
taken from the documentation: the real way out of an OIDC lockout (remove the
override in the dashboard - a helm upgrade with OAUTH_ONLY=false goes through the
same precedence chain and can do nothing), the three-part price of
adminConfigReadOnly, the redirect URI that carries the UI language across 27
locales, the two-part backup statement (the volume alone is not a backup, and it
may hold the OAuth secret in cleartext), why rotation needs no restart and which
mount detail that depends on, and what the probes do and do not carry.

Charts: new row, and the counter is now measured against */Chart.yaml instead of
carried forward - it had drifted. The table also still lists convertx, for which
there is no chart directory in the repository; flagged in a note rather than
deleted, because removing it is a decision about that chart and not a
documentation fix.

Lessons learned, four entries, all of them transferable beyond this chart:
a recovery instruction that ran through the fault it was meant to repair; a
read-only switch whose write barrier also blocked the one-time bootstrap write
that creates the admin password; a probe method the platform does not offer at
all (an HTTP probe is always GET); and a 0400 secret file that only works
because the kubelet silently widens it.
2026-10-05 21:27:25 +00:00