Files
helm-charts/ci/bulwark/fixtures.yaml
T
jklein b39f36ff5c
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Bulwark Helm Chart / Publish Bulwark Helm Chart (push) Successful in 7s
SonarQube Analysis / SonarQube Analysis (push) Successful in 27s
feat(bulwark): new chart for Bulwark Webmail (1.0.0+up1.12.0)
A JMAP webmail client for the stalwart chart (Gitea #65): StatefulSet, one PVC
at /app/data, Service plus headless Service, Ingress at a host root, two
OnePasswordItems, three probes, computed limits, replicas plus scaleDown, strict
values.schema.json.

Everything below was measured against the source at tag 1.12.0 rather than read
out of the documentation, which was wrong about three of them.

THE DEFINING PROPERTY: THE VOLUME BEATS THE CHART, FOR EVERY VALUE, SILENTLY.
configManager.get resolves "admin override > env var > default", the admin
dashboard may write any key of CONFIG_ENV_MAP, and nothing reports that it just
disconnected the chart - so a helm upgrade can succeed and change nothing.
Upstream documents this for the server URL only; it is true of everything.
Exactly three values escape it (SESSION_SECRET(_FILE), ADMIN_PASSWORD,
BULWARK_TELEMETRY). The value that closes it, bulwark.adminConfigReadOnly,
defaults to false because its price is threefold: it freezes the settings policy
(and with it the only way to move Web Push off the vendor relay), it freezes an
override that ALREADY exists instead of removing it, and on a fresh install it
stops admin.json from being written at all - so the admin dashboard never comes
up and its login answers HTTP 500 while the pod stays Ready.

OIDC-only sign-in, with the real way back out of a lockout: not helm upgrade
with OAUTH_ONLY=false (oauthOnly goes through the same precedence chain) but
removing the override in the admin dashboard. That is why ADMIN_PASSWORD is set
- the dashboard is the door. It generates no random password and logs nothing,
so the #65 analogy does not apply.

SESSION_SECRET_FILE and OAUTH_CLIENT_SECRET_FILE are both used - the better path
garage (#81) and keycloak (#83) did not have - in one projected volume mounted
WITHOUT subPath, because both are re-read on every use and a subPath mount is
never refreshed, and at mode 0440 because secret files are owned by root and
fsGroup only supplies the group.

Two backup statements, both at the value: without the session secret the
synchronized settings are unreadable out of an intact volume, and if the
dashboard's OAuth setup was ever used the client secret sits in config.json on
that volume in cleartext.

Probes: all three a plain GET on /api/health, with the limit written at the
readiness value - it carries process, HTTP acceptance, a free event loop and
Next.js routing, and nothing about the reported state or about Stalwart.

No ConfigMap, so the checksum/config rule (#82) has nothing to attach to, and
values.yaml says so explicitly; the op:// paths are covered by
checksum/secret-ref.

Renovate: no versioningTemplate, measured rather than assumed. 315 tags in eight
shapes (releases, a second v-prefixed family of the same images, an -always
flavour, floating minors and names, commit SHAs, betas, attestations) and plain
docker versioning excludes all six unwanted ones. Dry runs at 1.12.0, 1.11.0 and
1.10.0 are recorded in the block.

Part of #89 - deliberately not "Fixes", because one binding decision of the
issue was changed out of necessity and wants ratification rather than an
automatic close: the plan specifies HEAD /api/health for all three probes, and a
Kubernetes probe cannot send HEAD at all (HTTPGetAction has no method field; the
kubelet always issues GET). The alternative, an exec probe, would mean starting a
Node process every few seconds in an image that carries no curl and no wget. GET
is used instead and is marginally more capable than HEAD, so every limit the
issue argues is unchanged - but the decision is the maintainer's to confirm.
2026-10-06 10:15:27 +00:00

70 lines
3.2 KiB
YAML

# Fixtures for the bulwark install-test: the two Secrets the 1Password operator
# would otherwise materialize from the OnePasswordItems the chart renders
# (release name: ci, stub CRD in ci/common/), plus a name for the JMAP endpoint
# to resolve to.
#
# NO DATABASE, NO CACHE, NO BROKER AND NO MAIL SERVER - and that is a property of
# the chart rather than a shortcut taken here. Bulwark is a client: it holds no
# mail and contacts the JMAP server per request, not at boot, so it starts and
# becomes Ready with nothing behind that URL. The flip side is the limitation the
# chart documents at bulwark.readinessProbe, and this file is the clearest
# illustration of it: everything needed to make this chart green is in these few
# objects, and none of it is a mail server.
---
apiVersion: v1
kind: Secret
metadata:
name: ci-bulwark-app-secret
stringData:
# The field NAMES are what matter here, not the values: the operator
# materializes one Secret key per item field, and the chart consumes these two
# in DIFFERENT ways - session-secret as a projected FILE, admin-password
# through a secretKeyRef.
#
# Only the second of those is actually asserted by the run. The projected
# volume is `optional: true` by design (so a first install does not hang
# waiting for the 1Password operator), so a misspelled session-secret yields a
# missing file and a pod that starts fine. A misspelled admin-password leaves
# the container in CreateContainerConfigError and fails the run.
#
# A real base64 32-byte value rather than a placeholder word: Bulwark derives
# encryption keys from it, and a value of the wrong shape would be the kind of
# thing that works in CI and not in production.
session-secret: Y2ktb25seS1zZXNzaW9uLXNlY3JldC1ub3QtYS1yZWFsLW9uZQ==
# Visibly throwaway. It is hashed with scrypt into admin.json on the first
# start and the cluster is deleted with the job; the only thing it protects is
# a dashboard that exists for a few minutes.
admin-password: ci-bulwark-admin-password
---
apiVersion: v1
kind: Secret
metadata:
name: ci-bulwark-oauth-secret
stringData:
# The OIDC client secret. Nothing in CI performs a token exchange - no identity
# provider runs here - so this exists to exercise the SECOND source of the
# chart's projected volume and the OAUTH_CLIENT_SECRET_FILE path, not the OAuth
# flow.
oauth-client-secret: ci-bulwark-oauth-client-secret
---
# The JMAP endpoint's name. A Service with NO SELECTOR and therefore no
# endpoints: the in-cluster DNS name resolves, and a connection to it is refused.
#
# DELIBERATELY NOT A DEPLOYMENT. ci/run-install-test.sh waits for every fixture
# Deployment and StatefulSet to roll out BEFORE it installs the chart, so a stub
# web server here would be one more workload able to fail a run that is not about
# it - and it would buy nothing, because a refused connection and a served 404
# are indistinguishable to a chart that never contacts this address at boot.
#
# It exists at all so the URL in test-values.yaml is a real name rather than an
# invented hostname, which keeps the fixture honest about what the chart does
# with it.
apiVersion: v1
kind: Service
metadata:
name: jmap-stub
spec:
ports:
- port: 8080
targetPort: 8080