Files
helm-charts/urlaubsverwaltung/values.yaml
T
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

279 lines
14 KiB
YAML

# Scale the whole chart down without uninstalling it: scaleDown: true renders
# every Deployment/StatefulSet with replicas: 0 (and suspends CronJobs) and
# takes precedence over any per-workload replicas value. PVCs, Secrets and
# Services stay in place; scaleDown: false brings the workloads back up.
scaleDown: false
secrets:
# REQUIRED - op:// item path carrying "jdbc-url", "username" and "password".
# This chart deploys NO database; all state lives in the external one, and
# three datasource environment variables read these keys unconditionally.
# Without the value the render aborts - required() in
# urlaubsverwaltung-db.secret.yaml is the whole enforcement, and
# values.schema.json cannot help here (README, guideline 12).
databaseSecretRef: null
# REQUIRED - op:// item path carrying "client-id", "client-secret",
# "issuer-name", "issuer-uri", "jwt-issuer-uri" and "redirect-uri". This chart
# offers no login path besides OIDC, and the identity provider is required at
# boot, not at login: the application resolves <issuer-uri>/.well-known/
# openid-configuration while building its security beans, before the HTTP port
# opens, and exits when it cannot. Without the value the render aborts -
# required() in urlaubsverwaltung-oidc.secret.yaml is the whole enforcement,
# and values.schema.json cannot help here (README, guideline 12).
oidcSecretRef: null
ingress:
# Gates the whole Ingress template. Mutually exclusive with
# httpRoute.enabled below (Gitea #114): with both true the render aborts,
# naming both values.
#
# Guideline 1, checked in both directions for this chart: NOTHING outside the
# two routing templates reads ingress.domain - no environment variable of the
# application is derived from it, and UV_MAIL_APPLICATIONURL (the host in the
# links inside notification mails) is a hardcoded placeholder, deliberately
# not derived from it. So clusterIssuer AND domain are both free while both
# routing modes are off; with either one on, domain is required and its
# required() fires in whichever template renders. clusterIssuer is only ever
# read by the Ingress - Gateway mode renders no Certificate.
enabled: true
clusterIssuer: null
# The public hostname of this release. Read by the Ingress and - when
# httpRoute.hostnames is empty - by the HTTPRoute.
domain: null
className: nginx
# Extra annotations for the Ingress; merged with the cert-manager
# cluster-issuer annotation the chart always sets.
#
# THESE HAVE NO COUNTERPART IN GATEWAY MODE. The Gateway API has no
# annotation channel for controller behaviour, so anything configured here is
# simply gone when httpRoute.enabled is used instead - it is then the
# Gateway's business, or that of an implementation-specific policy object
# this chart deliberately does not render. Nothing this chart needs lives
# here by default.
annotations: {}
# Gateway-API alternative to the Ingress above (Gitea #114). Off by default, so
# an existing release is unaffected; exactly one of ingress.enabled and
# httpRoute.enabled may be true.
#
# The chart renders ONLY an HTTPRoute with parentRefs - no Gateway, no
# gatewayClassName, no Certificate and no implementation-specific policy
# object. Class and certificate belong to the Gateway and therefore to the
# platform, which is what keeps this chart independent of whether the cluster
# runs nginx-gateway-fabric, Cilium, Envoy Gateway or Istio.
#
# The Gateway-API CRDs must be installed in the cluster: the chart does not and
# cannot detect them (.Capabilities.APIVersions is unreliable at template
# time), so with httpRoute.enabled: true and no CRDs the INSTALL fails.
httpRoute:
enabled: false
# The Gateway(s) this route attaches to - a platform object, usually in
# another namespace, which must allow this namespace in its listener's
# allowedRoutes. One entry per parent, the fields are the Gateway API's
# ParentReference (name, and optionally namespace, sectionName, port, group,
# kind), passed through unchanged:
#
# parentRefs:
# - name: public
# namespace: gateway
# sectionName: https
#
# REQUIRED while httpRoute.enabled is true - the render aborts on an empty
# list, because a route without a parent is created, is valid and routes
# nothing, with no event to say so.
parentRefs: []
# Empty means [ingress.domain], so the hostname has one source of truth. Set
# explicitly only to publish under a different or an additional name - which
# is how a migration from the Ingress runs: the HTTPRoute takes a second
# hostname while the Ingress keeps ingress.domain.
#
# For THIS chart, mind that the OIDC redirect URI is a field of the 1Password
# item and is registered at the identity provider - it does not follow the
# hostname by itself, so publishing under a new name means updating both or
# login fails on a redirect-uri mismatch while the pod stays green.
hostnames: []
urlaubsverwaltung:
# Replica count for this workload; 0 is allowed and scales it down.
# The chart-global scaleDown flag takes precedence.
replicas: 1
# The official image is built with Cloud Native Buildpacks and runs as the
# non-root cnb user (uid 1002, gid 1001). /tmp is a writable emptyDir for
# the JVM's temp/work files.
securityContext:
pod:
runAsNonRoot: true
runAsUser: 1002
runAsGroup: 1001
# RuntimeDefault applies the container runtime's default seccomp filter,
# which blocks the syscalls a normal workload never makes. It sits in the
# pod context on purpose: from here it covers every container in the pod
# including init containers, while a container-level seccompProfile would
# have to be repeated for each one (and silently missed on the next one
# added). A container that genuinely needs a different filter can still
# override it under securityContext.container, which wins over this value.
seccompProfile:
type: RuntimeDefault
container:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
capabilities:
drop:
- ALL
# HEALTH INDICATORS — TWO SETTINGS THE CHART FIXES IN THE TEMPLATE
#
# The chart sets these two as plain environment variables, not as values,
# because urlaubsverwaltung.env below already overrides anything:
#
# MANAGEMENT_ENDPOINT_HEALTH_GROUP_READINESS_INCLUDE=readinessState,db
# puts the datasource into the readiness group, so a database outage
# takes the pod out of Ready instead of leaving it serving errors (#218).
# The liveness group stays untouched on purpose — an outage must keep
# traffic away, not restart the pod in a loop.
# MANAGEMENT_HEALTH_MAIL_ENABLED=false
# removes the mail indicator, which otherwise pins the aggregate
# /actuator/health at 503 DOWN forever, because the chart's mail wiring
# points at localhost:1025 where nothing listens (#217).
#
# THE CONSEQUENCE TO KNOW BEFORE IT BITES: whoever later configures real mail
# delivery (SPRING_MAIL_HOST/SPRING_MAIL_PORT via urlaubsverwaltung.env) MUST
# switch the indicator back on in the same change:
#
# env:
# - name: MANAGEMENT_HEALTH_MAIL_ENABLED
# value: "true"
#
# Left at false, /actuator/health keeps answering 200 while the configured
# mail server is unreachable and no notification mail is delivered. The
# setting trades a permanently false alarm for silence, and silence is only
# correct as long as there is nothing to report about.
#
# That override repeats a name the chart already sets, and env is additive, so
# it renders the variable twice. At INSTALL time both entries reach the pod and
# the later one — yours — wins in the container. Added to an ALREADY INSTALLED
# release it is lost instead: Kubernetes merges env by name, the pair collapses
# to the chart's own entry, and helm still reports a successful upgrade. The
# override does not fail loudly, it fails silently. See the "env IS ADDITIVE
# ONLY" section of NOTES.txt — the switch has to go in at install time, and
# after any such change the result belongs verified on the live Deployment.
livenessProbe:
httpGet:
port: 8080
path: /actuator/health/liveness
periodSeconds: 10
failureThreshold: 3
successThreshold: 1
timeoutSeconds: 5
readinessProbe:
httpGet:
port: 8080
path: /actuator/health/readiness
periodSeconds: 5
failureThreshold: 3
successThreshold: 1
timeoutSeconds: 5
# The startup probe gates liveness/readiness. Spring Boot startup
# (JVM + Liquibase migrations) gets a budget of 10s * 30 = 300s.
startupProbe:
httpGet:
port: 8080
path: /actuator/health/liveness
periodSeconds: 10
failureThreshold: 30
timeoutSeconds: 5
# Extra environment variables, appended to the ones the chart always sets.
# Values are rendered through tpl; value, secretKeyRef and configMapKeyRef
# forms are supported.
env: []
resources:
# Limits are optional: memory and ephemeral-storage limits default to
# limitFactor x the request, an explicitly set limit always wins, and a
# CPU limit is only set when configured explicitly under "limits".
limitFactor: 3
requests:
cpu: 0.1
memory: 768Mi
ephemeralStorage: 10Mi
# ---------------------------------------------------------------------------
# NetworkPolicy (Gitea #5). On by default. The reference implementation with
# the full reasoning is template-chart; the convention is in README, "Chart
# style guidelines", and the operator's view on this chart's wiki page.
# ---------------------------------------------------------------------------
networkPolicy:
# WHAT THIS POLICY ALLOWS: inbound 8080 only, and outbound DNS plus TCP 5432
# AND 3306 (the database - see below) and 443 (the OIDC provider, which this
# application requires: there is no other login path).
#
# WHY BOTH DATABASE PORTS: the chart 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 only one would break half the
# possible deployments on upgrade, and the symptom would be a connection
# timeout inside a Java stack trace. A deliberate, small widening against a
# large and silent failure.
#
# Inbound 8080 is not restricted by source in the default (probe port), and a
# policy is only a boundary where the CNI enforces one.
enabled: true
# Namespaces whose pods may reach this workload, in addition to the release's
# own namespace, which is always allowed. Empty means: own namespace only.
#
# Matched on the "kubernetes.io/metadata.name" label Kubernetes sets on every
# namespace itself, so no labelling work is needed on a namespace this chart
# does not own - and no typo protection either: a name that does not exist
# matches nothing and is silently ineffective. Note the granularity: an entry
# opens the port to EVERY pod in that namespace.
allowedNamespaces: []
# Source CIDRs allowed to reach port 8080, which is the port all three probes address.
#
# EMPTY MEANS OPEN, NOT BLOCKED. With no entry that rule is rendered WITHOUT a
# "from" selector, so the port is reachable from anywhere the CNI routes to -
# inside the cluster, since it carries no NodePort.
#
# That is the deliberate choice between two bad defaults. The kubelet's probes
# originate from the IP of the WORKER NODE running the pod, an address this
# chart cannot know and must not guess: a CIDR that excludes the real node
# blocks every probe, the pod never becomes Ready, the rollout times out, and
# the symptom reads as a broken application rather than as a policy.
#
# Narrow it by listing the worker node IPs (e.g. ["10.0.1.11/32"]) - and mind
# the companion trap: once this list is non-empty the rule IS restricted, so
# everything else that needs the port has to be in it. The chart adds the own
# namespace, the ingressController below and allowedNamespaces to the same rule for exactly that reason.
probeSources: []
# The ingress controller allowed to reach the routed port once probeSources
# restricts it. Rendered only while ingress.enabled is true, and only in
# the restricted branch - with the default (open) rule the controller reaches
# the port anyway. The two keys become ONE "from" entry and are therefore an
# AND: pods carrying podLabels IN namespace. That is the point of having them
# at all - the controller's namespace in allowedNamespaces would open the port
# to every pod in kube-system.
#
# MEASURED ON THIS CLUSTER, NOT ASSUMED (2026-10-14): ingress-nginx is gone,
# the only IngressClass is "traefik" and it is the default, and the controller
# is the DaemonSet rke2-traefik in kube-system. The same Traefik serves the
# Gateway API here, so this one source covers the Ingress and the HTTPRoute
# path alike. A policy written against the wrong controller is accepted, looks
# strict, and silently blocks all web traffic - and ingress.className says
# nothing about it, because a class names an implementation, not the pods the
# traffic comes from.
ingressController:
namespace: kube-system
podLabels:
app.kubernetes.io/name: rke2-traefik
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
# in the pod is a trap, not a feature.
#
# PORTS, NOT DESTINATIONS, and that is a limit of the object rather than
# laziness: a NetworkPolicy matches CIDRs and selectors and never hostnames,
# so "only to the database host" cannot be written at all. Each entry
# permits its port to ANY destination. What the cap still buys is real: a
# compromised pod cannot open arbitrary outbound connections.
#
# Add the real port for a database that is on neither 5432 nor 3306, or for
# an OIDC issuer that is not on 443.
extraPorts: []