Files
jklein e1adcef71f
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 11s
Install-test / Install-test patchmon (pull_request) Successful in 1m41s
Validate Helm Charts / Resource quantities (pull_request) Successful in 11s
Validate Helm Charts / Selector match (pull_request) Successful in 16s
Validate Helm Charts / Bump level (pull_request) Successful in 7s
Validate Helm Charts / NetworkPolicy (pull_request) Failing after 11s
Validate Helm Charts / YAML scalars (pull_request) Successful in 10s
Validate Helm Charts / Build apache-tika (pull_request) Successful in 6s
Validate Helm Charts / Build bentopdf (pull_request) Successful in 17s
Validate Helm Charts / Build bulwark (pull_request) Successful in 16s
Validate Helm Charts / Build calibre-web-automated (pull_request) Successful in 7s
Validate Helm Charts / Trivy config apache-tika (pull_request) Successful in 13s
Validate Helm Charts / Build cyberchef (pull_request) Successful in 4s
Validate Helm Charts / Build homeassistant (pull_request) Successful in 5s
Validate Helm Charts / Build gotenberg (pull_request) Successful in 6s
Validate Helm Charts / Build rss-bridge (pull_request) Successful in 5s
Validate Helm Charts / Build searxng (pull_request) Successful in 5s
Validate Helm Charts / Build sure (pull_request) Successful in 5s
Validate Helm Charts / Build template-chart (pull_request) Successful in 5s
Validate Helm Charts / Build outline (pull_request) Successful in 5s
Validate Helm Charts / Build patchmon (pull_request) Successful in 5s
Validate Helm Charts / Build rallly (pull_request) Successful in 5s
Validate Helm Charts / Build urlaubsverwaltung (pull_request) Successful in 15s
Validate Helm Charts / Build voucher-vault (pull_request) Successful in 7s
Validate Helm Charts / Build ytdlp-web-player (pull_request) Successful in 7s
Install-test / Install-test rallly (pull_request) Successful in 1m41s
Validate Helm Charts / Build zipline (pull_request) Successful in 7s
Validate Helm Charts / Trivy config bulwark (pull_request) Successful in 13s
Validate Helm Charts / Trivy config cyberchef (pull_request) Successful in 18s
Validate Helm Charts / Trivy config gotenberg (pull_request) Successful in 13s
Validate Helm Charts / Trivy config bentopdf (pull_request) Successful in 12s
Validate Helm Charts / Trivy config calibre-web-automated (pull_request) Successful in 16s
Validate Helm Charts / Trivy config homeassistant (pull_request) Successful in 12s
Validate Helm Charts / Trivy config outline (pull_request) Successful in 14s
Validate Helm Charts / Trivy config rss-bridge (pull_request) Successful in 12s
Validate Helm Charts / Trivy config searxng (pull_request) Successful in 13s
Validate Helm Charts / Trivy config sure (pull_request) Successful in 20s
Validate Helm Charts / Trivy config patchmon (pull_request) Successful in 24s
Validate Helm Charts / Trivy config rallly (pull_request) Successful in 15s
Validate Helm Charts / Trivy config template-chart (pull_request) Successful in 14s
Validate Helm Charts / Trivy config ytdlp-web-player (pull_request) Successful in 17s
Validate Helm Charts / Trivy config urlaubsverwaltung (pull_request) Successful in 15s
Validate Helm Charts / Trivy config voucher-vault (pull_request) Successful in 11s
Validate Helm Charts / Trivy config zipline (pull_request) Successful in 24s
Install-test / Install-test rss-bridge (pull_request) Successful in 1m9s
Install-test / Install-test searxng (pull_request) Successful in 1m9s
Install-test / Install-test sure (pull_request) Successful in 2m24s
Install-test / Install-test apache-tika (pull_request) Successful in 1m23s
Install-test / Install-test urlaubsverwaltung (pull_request) Successful in 1m44s
Install-test / Install-test bentopdf (pull_request) Successful in 59s
Install-test / Install-test bulwark (pull_request) Successful in 1m23s
Install-test / Install-test voucher-vault (pull_request) Successful in 1m58s
Install-test / Install-test calibre-web-automated (pull_request) Successful in 1m55s
Install-test / Install-test cyberchef (pull_request) Successful in 1m6s
Install-test / Install-test gotenberg (pull_request) Successful in 1m19s
Install-test / Install-test ytdlp-web-player (pull_request) Successful in 59s
Install-test / Install-test zipline (pull_request) Successful in 1m52s
Install-test / Install-test homeassistant (pull_request) Successful in 2m26s
Validate Helm Charts / Install-test (pull_request) Successful in 27m21s
Install-test / Install-test outline (pull_request) Successful in 2m7s
feat!: NetworkPolicy in nine more charts, on by default
bulwark, calibre-web-automated, patchmon, rallly, urlaubsverwaltung,
voucher-vault, zipline, sure, homeassistant. One major each.

These are the charts with real outbound dependencies, and they all share one
limitation that is now written into every one of them: THE EGRESS PORTS ARE THE
STANDARD ONES, NOT THE CONFIGURED ONES. Databases, caches and OIDC issuers
arrive as connection URLs or as fields inside 1Password items that the charts
never parse, so a chart cannot know where its dependency listens and cannot
validate a guess. A dependency somewhere else is blocked, and
networkPolicy.egress.extraPorts is the escape hatch. That is the upgrade risk
of this rollout, stated per chart instead of discovered per release.

Four of them differ in ways that mattered:

HOMEASSISTANT gets policyTypes: [Ingress] and NO egress section at all, and
that is the decision in the file rather than an omission. Its outbound traffic
is the union of every integration an operator enables - mDNS and SSDP
discovery, MQTT, a Zigbee or Z-Wave bridge on whatever port its firmware chose,
ESPHome, printers, speakers, inverters, cloud APIs, NTP - which no chart can
know. A cap would protect little, since the devices sit on the node's own LAN,
and would break integrations one at a time, months apart, each looking like a
flaky device rather than like a policy. So valkey's rule applies in the other
direction: policyTypes names only what there is something to restrict.

URLAUBSVERWALTUNG allows BOTH 5432 and 3306, deliberately. It is handed a full
JDBC URL inside a 1Password item and never parses it, so it cannot tell
PostgreSQL from MariaDB - and this cluster runs both. Allowing one would break
half the possible deployments on upgrade, with the symptom buried in a Java
stack trace. A small widening against a large and silent failure.

RALLLY derives its SMTP rule from rallly.smtp.enabled rather than always
rendering it: with mail switched off there is nothing to permit, and a rule
that permits nothing makes the object look stricter than it is.

BULWARK is the chart where the cap can fail most quietly. Its probes test its
own port and say nothing about the JMAP server, so a jmapServerUrl on a port
the policy does not allow yields a Ready pod serving an empty webmail. 443 is
allowed, Stalwart's in-cluster 8080 is not, and both halves of that are in
values.yaml and NOTES.txt.

PATCHMON and HOMEASSISTANT select their pods by the BARE release-and-chart
label, with no -deployment or -sfs suffix. Copying a selector from a
neighbouring chart would have matched nothing and left a policy that filters
nothing - which is precisely the first assertion of the render test.

Five CI fixtures gain networkPolicy.egress.extraPorts, and they are the only
place in the repository where the cap's cost is actually exercised: the CI dex
runs on 5556 rather than 443 (patchmon, rallly, urlaubsverwaltung,
voucher-vault) and the CI JMAP server on plain 8080 (bulwark).

Part of #5
2026-10-14 14:26:25 +00:00

175 lines
9.3 KiB
YAML

# CI values for the bulwark install-test.
#
# WHY THIS CHART IS TESTED RATHER THAN EXCLUDED, and what a green run is worth.
# Bulwark needs no database, no cache and no broker, and it does not need a
# reachable JMAP server to START - the connection is made per request, not at
# boot - so a dummy URL is enough to bring the pod up. That makes this one of
# the cheapest install-tests in the repo and the only place where the two
# riskiest assumptions of the chart are exercised at all. Neither is observable
# in a render.
#
# WHAT A GREEN RUN PROVES:
#
# 1. readOnlyRootFilesystem: true HOLDS, WITH EXACTLY THE TWO emptyDirs THE
# CHART RENDERS - /tmp and /app/.next/cache. This is the measurement point
# the plan for Gitea #89 left open, and it is the reason this fixture
# exists: the set of writable paths was DERIVED from next.config.ts
# (output: "standalone", and images.unoptimized applies only in the isLite
# branch, so image optimization is active and writes under
# /app/.next/cache) and could not be measured without running the image.
# A missing writable path is not subtle - it is an EROFS or EACCES on the
# first write - so a pod that reaches Ready and stays Ready is the proof.
# A SUPERFLUOUS emptyDir would NOT be caught here; that half stays open and
# is named in the chart's wiki page.
# 2. THE PROBES CAN REALLY BE SATISFIED. All three are an HTTP GET on
# /api/health, which has to exist in the built bundle and answer 200
# unauthenticated. If it did not, the startup probe would exhaust its budget
# and the rollout would fail - and no render could have told us, because a
# path only exists in the built bundle.
# 3. The uid/gid 1001 read off the Dockerfile is right, and fsGroup makes a
# freshly provisioned volume writable for it: Bulwark creates its four
# subdirectories under /app/data itself on start, and the image's own mkdir
# ran in a layer this mount covers. Wrong uid, or no fsGroup, and the
# instrumentation hook fails to write admin.json.
# 4. ADMIN_PASSWORD is really consumed on the first start - the hook hashes it
# with scrypt and writes /app/data/admin/admin.json. That write is inside
# the startup budget, so a budget that was too short would fail here.
# Plus the mechanical half: the projected volume assembles, the Service port
# name matches the containerPort, the Ingress is accepted with its extra
# annotation alongside the cert-manager one, the resource quantities are valid,
# and the schema accepts a realistic non-default values file.
#
# ONE CLAIM THIS FIXTURE DELIBERATELY DOES NOT MAKE, because it would be
# false: it does not prove that the two FILE-form secret keys are spelled
# right. The projected volume marks both sources `optional: true` on purpose -
# so that a first install does not hang in ContainerCreating while the
# 1Password operator catches up - and the consequence is that a misspelled
# session-secret or oauth-client-secret key yields a MISSING FILE and a pod
# that starts perfectly. Only `admin-password` is proven, because it arrives
# through a non-optional secretKeyRef and a wrong name there leaves the
# container in CreateContainerConfigError.
#
# WHAT IT DOES NOT PROVE, and the first two matter most:
# - ANYTHING ABOUT JMAP. The URL below points at a Service that answers
# nothing useful. Bulwark comes up regardless, which is precisely the
# property the readiness probe cannot see (chart values.yaml,
# bulwark.readinessProbe) - so this test demonstrates the limitation rather
# than closing it. A green run here says nothing about mail working.
# - ANYTHING ABOUT OIDC. No identity provider runs in CI, so the sign-in path
# is never walked. What IS checked is that the chart's render guards fire on
# a missing client id or issuer (unit-testable at render time) and that the
# secret file is mounted where OAUTH_CLIENT_SECRET_FILE points.
# - THE PRECEDENCE TRAP. config.json never exists in this run, so the
# "volume beats the chart" behaviour - the chart's single most important
# operating caveat - is not exercised. It cannot be: it needs an admin
# writing an override, which needs a browser.
# - anything about an upgrade, or about a volume that already carries data.
# This installs once, from nothing.
secrets:
# Materialized by ci/bulwark/fixtures.yaml as ci-bulwark-app-secret and
# ci-bulwark-oauth-secret (the OnePasswordItem stub CRD comes from ci/common/).
appSecretRef: op://ci/bulwark-app
oauthSecretRef: op://ci/bulwark-oauth
bulwark:
# A dummy JMAP endpoint. It does NOT have to answer - Bulwark connects per
# request - but it does have to be SET, because an empty value puts the app
# into bootstrap mode and serves the setup wizard instead. So this value also
# exercises the path that keeps the chart declarative.
#
# It points at the selector-less Service in fixtures.yaml: a name that RESOLVES
# and then refuses the connection. That is deliberately not a Deployment -
# fixtures with a workload have to roll out before the chart is installed, so a
# stub serving nothing would be one more thing able to fail a run that is not
# about it. A refused connection and a served 404 are indistinguishable to this
# chart, because nothing here contacts the JMAP server at boot.
jmapServerUrl: http://jmap-stub.default.svc.cluster.local:8080
appName: Bulwark CI
# OIDC LEFT ON, i.e. the chart's default, although no provider answers in CI.
# That is deliberate: it is what makes the run exercise the OAuth
# OnePasswordItem, the second source of the projected volume and the
# OAUTH_CLIENT_SECRET_FILE path. Bulwark does not contact the issuer at boot,
# so an unreachable one costs nothing here - it would only fail a sign-in, and
# nothing signs in.
oauth:
enabled: true
only: true
clientId: bulwark-ci
issuerUrl: https://keycloak.ci.example.com/realms/ci
# ON, i.e. the chart's default, and it is what makes /app/data/settings get
# created and the session secret actually read: with the sync off, a missing or
# misnamed session-secret field would pass unnoticed.
settingsSync: true
# OFF - the chart's default, spelled out because it is load-bearing for what
# this test means. With it ON, a fresh install cannot write admin.json and the
# admin dashboard never comes up; the pod would still be Ready, so the run
# would be green while the chart's documented rescue door was missing. Leaving
# it off is what makes step 4 of "what a green run proves" an assertion.
adminConfigReadOnly: false
# Both off, as in the chart - and here with a second reason: a CI runner should
# not send a telemetry heartbeat or an update check to a vendor endpoint on
# every pull request.
telemetry: false
updateCheck: false
# debug, so that a failed run has something to read. The install-test dumps
# container logs on failure, and Bulwark's info level says very little about
# why a start went wrong.
logLevel: debug
# text rather than the chart's json: a human reads these logs out of a failed
# job, not a collector.
logFormat: text
resources:
# Small, to stay inside the runner's budget, while still exercising the limit
# derivation: 192Mi x limitFactor 3 = 576Mi. Not smaller than this on
# purpose - the memory LIMIT is what V8's heap limit is derived from, and a
# Next.js server with too little heap fails in a way that looks like an
# application fault.
requests:
cpu: 100m
memory: 192Mi
ephemeral-storage: 64Mi
storage:
amount: 256Mi
# k3d's dynamic provisioner. The intended production value does not exist on
# the runner - and a pre-bound hostPath PV would be worse than this one,
# because the kubelet does not apply fsGroup to a hostPath volume at all, so
# the test would silently stop exercising the ownership path that matters most
# here: Bulwark has to CREATE four subdirectories in a volume it did not make.
storageClass: local-path
ingress:
# Left ON although no ingress controller answers in CI: the point is that the
# object is accepted and that the Service port it names BY NAME ("http")
# actually exists - a typo there is otherwise invisible until someone opens the
# URL.
enabled: true
domain: bulwark.ci.example.com
clusterIssuer: ci-selfsigned
annotations:
# Exercises the annotation merge: this has to arrive ALONGSIDE the
# cert-manager annotation the chart always sets, not instead of it. It is
# also the annotation this chart's NOTES.txt tells operators to add, so the
# fixture is the shape a real install has.
nginx.ingress.kubernetes.io/proxy-body-size: "25m"
# The egress cap of this chart's NetworkPolicy (Gitea #5) allows the STANDARD
# ports of its dependencies. The CI JMAP server does not: fixtures.yaml serves it over plain HTTP on 8080 rather than over HTTPS on 443 - which is exactly the asymmetry values.yaml warns about for an in-cluster jmapServerUrl.
#
# So this is not a workaround for the test: it is the chart's documented escape
# hatch, used for exactly the case values.yaml names. The fixtures are the one
# place in the repository where that case is exercised, and removing this entry
# shows what the cap costs a dependency on a non-standard port.
networkPolicy:
egress:
extraPorts:
- 8080