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

291 lines
13 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
# Replica count for this workload; 0 is allowed and scales it down.
# The chart-global scaleDown flag takes precedence.
replicas: 1
# This is for setting up a service more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/
service:
# This sets the service type more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#publishing-services-service-types
type: ClusterIP
# This sets the ports more information can be found here: https://kubernetes.io/docs/concepts/services-networking/service/#field-spec-ports
port: 80
# This block is for setting up the ingress for more information can be found here: https://kubernetes.io/docs/concepts/services-networking/ingress/
ingress:
# MUTUALLY EXCLUSIVE WITH httpRoute.enabled - the render aborts when both are
# true, and the message names both value paths.
enabled: false
# Ingress class; set to "" to omit ingressClassName from the rendered Ingress.
# NOT READ IN GATEWAY MODE: the class is a property of the Gateway there, and
# the Gateway belongs to the platform.
className: nginx
# NOT READ IN GATEWAY MODE either, and this is the one to be aware of: the
# Gateway API has no annotation channel for controller behaviour, so
# everything put here is simply gone in that mode. This chart sets nothing
# itself, and the installation this repository serves does not deploy
# homeassistant at all (checked 2026-10-12), so there is no operator-supplied
# annotation here today that the switch would drop - unlike its siblings in
# the same sweep. That is an absence of evidence about this installation, not
# a guarantee about yours: whatever you put here disappears in Gateway mode.
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
# THE PUBLIC NAMES OF THIS RELEASE, AND READ IN BOTH MODES.
#
# This chart is the documented ingress exception (README): it keeps upstream's
# flexible host list rather than the repository's required
# domain/clusterIssuer pair, and that exception stays. The consequence for
# Gitea #114 is that httpRoute has NO hostnames key - a chart-global hostname
# list could not say which host entry, and therefore which set of paths, it
# belonged to. So this list is where the hostnames live in BOTH modes, and in
# Gateway mode the chart renders ONE HTTPRoute PER ENTRY, with one rule per
# path of that entry.
#
# pathType IN GATEWAY MODE, because the default below is the awkward value.
# Ingress has Exact, Prefix and ImplementationSpecific; the Gateway API has
# Exact, PathPrefix and RegularExpression. Exact and Prefix map cleanly.
# ImplementationSpecific means "ask the controller" and has NO counterpart,
# so:
# - on the path "/" it is rendered as PathPrefix (every controller treats a
# root ImplementationSpecific path as a prefix match, so the default
# below works in Gateway mode unchanged);
# - on ANY OTHER path the render ABORTS and asks you to say which you mean.
# In ingress-nginx an ImplementationSpecific sub-path is a REGEX and
# PathPrefix is not one, so mapping it silently would change how the path
# matches while the render stayed green.
# RegularExpression is deliberately not offered: its support level is
# Implementation-specific and would bind this chart to one Gateway
# implementation.
hosts:
- host: chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
# NOT READ IN GATEWAY MODE: the certificate sits on the Gateway's listener.
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
# THE GATEWAY-API ALTERNATIVE TO THE INGRESS (Gitea #114).
#
# Mutually exclusive with ingress.enabled - the render aborts when both are
# true. The chart renders ONLY HTTPRoutes with parentRefs: no Gateway, no
# class, no Certificate, and no implementation-specific policy object. Class
# and certificate sit on the Gateway and its listeners and belong to the
# platform (cluster-config), which is what keeps the Gateway implementation
# replaceable without touching this chart again.
#
# TWO KEYS ONLY, and the missing third one is deliberate: there is no
# httpRoute.hostnames here, because this chart has no ingress.domain either.
# The hostnames and the paths come from ingress.hosts above - see the comment
# there, including the pathType mapping. This is the freesailarr shape from
# group 2 of the sweep, for the same reason.
#
# ONE HTTPRoute PER ENTRY of ingress.hosts, named
# <release>-homeassistant-httproute for the first and ...-httproute-<index> for
# the rest. REORDERING ingress.hosts RENAMES ROUTES and so deletes and
# recreates them on the next upgrade instead of editing them - harmless,
# because a route holds no state and no certificate, but worth knowing before
# it looks like a bug.
#
# THE GATEWAY-API CRDs MUST BE PRESENT. The chart cannot detect them
# (.Capabilities.APIVersions is unreliable at template time) and an HTTPRoute
# without them fails at install, so this is a value and not a guess.
httpRoute:
enabled: false
# The Gateway(s) EVERY route of this release attaches to - a platform object,
# usually in another namespace, whose listener must allow this namespace in
# allowedRoutes. Entries 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. The same list is used for every route
# this chart renders; the hosts differ, the Gateway does not.
parentRefs: []
# Documented deviation from the repo-wide hardening standard: the official
# Home Assistant container must start as root — it boots via the s6-overlay
# init system, which performs its setup as uid 0 (the image supports no
# non-root user), so runAsNonRoot/runAsUser are deliberately not set.
# Everything else is hardened: no privilege escalation, all capabilities
# dropped and a read-only root filesystem with writable emptyDirs for
# /tmp and /run (s6-overlay state). NET_RAW is added back because Home
# Assistant's device discovery (DHCP watcher, ping/arp integrations) uses
# raw sockets and logs "Operation not permitted" without it.
securityContext:
pod: {}
container:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_RAW
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.2
memory: 512Mi
ephemeralStorage: 128Mi
# This is to setup the liveness, readiness and startup probes more information can be found here: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
livenessProbe:
httpGet:
path: /
port: http
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
httpGet:
path: /
port: http
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
# The startup probe gates liveness/readiness. Home Assistant can take a while to
# start up, so grant a generous budget: 10s * 30 = 300s.
startupProbe:
httpGet:
path: /
port: http
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 30
# Extra environment variables, appended to the ones the chart always sets.
# Values are rendered through tpl; value, secretKeyRef and configMapKeyRef
# forms are supported.
env: []
# Additional volumes on the output Deployment definition.
volumes: []
# - name: foo
# secret:
# secretName: mysecret
# optional: false
# Additional volumeMounts on the output Deployment definition.
volumeMounts: []
# - name: foo
# mountPath: "/etc/foo"
# readOnly: true
nodeSelector: {}
tolerations: []
affinity: {}
core:
timezone: "UTC"
storage:
size: 5Gi
storageClass: "default"
# Must include the following keys:
# - config
# - http-config
# - recorder-config
#
# Changing the op:// path below IS covered: the StatefulSet carries a
# checksum/secret-ref annotation over the rendered OnePasswordItem, so pointing
# this value at a different vault item restarts the pod.
#
# Known limitation (#82): changing the CONTENT of that item in the vault does
# NOT restart Home Assistant. The operator updates the Kubernetes Secret, but
# the running pod keeps the old files - they are mounted with subPath, which the
# kubelet never refreshes, and Home Assistant reads its configuration only at
# start. The change then takes effect at some unrelated later restart, possibly
# months afterwards, and a broken value surfaces long after the edit that caused
# it. That is especially likely for this release, which is currently kept at
# zero replicas via scaleDown: whoever brings it back up gets whatever the vault
# holds at that moment.
#
# The checksum/config mechanism the other charts use cannot close this gap:
# Helm never sees the content (the operator fills the Secret in-cluster after
# rendering), so there is nothing to hash. The operator's own
# "operator.1password.io/auto-restart" annotation does not close it either - it
# only ever restarts Deployments, never StatefulSets. Verified against
# onepassword-operator 1.12.0: pkg/onepassword/secret_update_handler.go lists
# exactly one workload type (appsv1.DeploymentList), and the PR adding
# StatefulSets is still open. Setting the annotation here would look like a fix
# and do nothing, so this chart deliberately does not set it.
#
# After changing the item, restart Home Assistant by hand:
# kubectl rollout restart statefulset <release>-homeassistant-sfs
secrets:
configSecretRef: null
# ---------------------------------------------------------------------------
# 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 8123 only. THERE IS NO EGRESS SECTION, and
# that is the decision in this chart rather than an omission.
#
# Home Assistant's outbound traffic is the union of every integration enabled
# in a release: mDNS/SSDP discovery (5353/UDP, 1900/UDP), MQTT (1883/8883), a
# Zigbee or Z-Wave bridge on whatever port its firmware chose, ESPHome (6053),
# printers, speakers, TVs, inverters, cloud APIs on 443, NTP on 123/UDP. A
# chart cannot know that set, a cap would protect little - the devices sit on
# the node's own LAN - and it would break integrations one at a time, months
# apart, each looking like a flaky device rather than like a policy. So
# policyTypes names Ingress only, for the same reason valkey names Ingress
# only: a rule that has to be widened until it permits everything makes the
# object look stricter than it is.
#
# An installation that wants an egress cap should write one against its own
# integrations, at release level, where they are visible.
#
# Inbound 8123 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 8123, 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 and allowedNamespaces to the same rule for exactly that reason.
probeSources: []