Validate Helm Charts / Detect changed charts (pull_request) Successful in 3s
Validate Helm Charts / Resource quantities (pull_request) Successful in 8s
Validate Helm Charts / Build ${{ fromJSON(needs.detect-changed-charts.outputs.charts) }} (pull_request) Successful in 4s
Validate Helm Charts / Trivy config ${{ fromJSON(needs.detect-changed-charts.outputs.scan-charts) }} (pull_request) Successful in 11s
Validate Helm Charts / Install-test ${{ fromJSON(needs.detect-changed-charts.outputs.install-charts) }} (pull_request) Successful in 1m22s
Publish Helm Chart with patched Application Version / publish-helm-chart (push) Successful in 7s
Publish Valkey Helm Chart / Publish Valkey Helm Chart (push) Successful in 7s
A new chart for a single Valkey instance per application, replacing the
shared cluster-redis whose separation rests on one ACL file in one
1Password field - a construction in which every change touches six
applications and which cost a month of Paperless standstill.
Shape: StatefulSet with one replica, ClusterIP Service, no cluster mode,
no Ingress and no value to add one.
The password arrives as a finished configuration line ("requirepass
<secret>") in the 1Password field auth-conf, is mounted as a file and
included by the main configuration - never in argv, never in the
environment. The include is rendered last, so nothing in extraConfig can
override it. Because a misspelled or empty field would leave an open
server that looks perfectly healthy, an init container greps the mounted
file for the line and refuses to start the pod without it; it never
prints the file.
maxmemory is derived from the computed memory limit (maxmemoryFactor,
default 0.75) and a configured value above that limit aborts the render:
otherwise the kubelet's OOM kill decides what the eviction policy was
configured to decide. Persistence and eviction are documented as pairs -
queue (PVC + noeviction, the default) and cache (emptyDir +
allkeys-lru) - and the combination "no persistence + noeviction" carries
a warning in values.yaml and in NOTES.txt.
Probes: valkey-cli ping as an exec probe, authenticated through
VALKEYCLI_AUTH read out of the mounted file, with "-e" and an assertion
on the answer PONG. Without -e valkey-cli exits 0 on an error reply
(valkey 9.1.2, src/valkey-cli.c: the config.set_errcode branch in
cliReadReply), which would make the probe unable to fail.
Hardening: uid 999 / gid 1000 - the image's own valkey user - with
fsGroup matching, runAsNonRoot, readOnlyRootFilesystem, all capabilities
dropped and seccompProfile RuntimeDefault in the pod context so the init
container is covered too. /data is the only writable path.
NetworkPolicy on by default (first chart in this repo with one), with a
switch and a list of additional namespaces; values.yaml names that it is
only a boundary where the CNI enforces policies.
Wiring: publish-valkey.yml, the chart-name choice list, ci/valkey
fixtures for the persistent variant, a plain docker-versioning Renovate
block, README chart row.
129 lines
9.1 KiB
YAML
129 lines
9.1 KiB
YAML
# Documented exceptions for `trivy config` (ci/run-trivy-config.sh).
|
|
#
|
|
# Trivy's native ignore-file schema, so the same file drives the future gate
|
|
# (`--ignorefile` + a non-zero exit) without being rewritten.
|
|
#
|
|
# House rule, same as ci/excluded-charts.txt: no entry without a `statement`.
|
|
# An entry without a reason reads as negligence a year from now, an entry with
|
|
# one reads as a decision. ci/run-trivy-config.sh refuses to run if a statement
|
|
# is missing, and it prints every suppressed finding together with its reason
|
|
# instead of hiding it.
|
|
#
|
|
# `paths` scopes an entry to a single chart: the scan renders each chart to
|
|
# <chart>.yaml, so "**/samba.yaml" means "samba only" and every other chart
|
|
# keeps being reported for that check.
|
|
#
|
|
# Not in here on purpose: AVD-KSV-0125 (untrusted registry). Suppressing it
|
|
# would also stay silent when an image from a genuinely foreign source appears.
|
|
# Instead the trusted-registry list is configured in ci/trivy/data/ksv0125.yaml
|
|
# and the check keeps its meaning.
|
|
|
|
misconfigurations:
|
|
# --- repo-wide conventions ------------------------------------------------
|
|
- id: AVD-KSV-0011
|
|
statement: "No CPU limits, on purpose: CPU is compressible, so a limit throttles where the missing limit merely shares - see README 'Resources (auto-limits)'. The chart helper renders a CPU limit only when one is set explicitly."
|
|
|
|
- id: AVD-KSV-0020
|
|
statement: "UIDs are pinned to the value the image expects, not raised above 10000: minecraft-server 1000 (gosu default), teamspeak-server 9987, voucher-vault 33, the matrix-synapse delegation nginx 101, sure 1000 (its Dockerfile declares USER 1000:1000). calibre-web-automated shows the risk - a runtime uid moving from 1000 to 911 makes existing volume data inaccessible, and every chart with a PVC carries that same risk class."
|
|
|
|
- id: AVD-KSV-0021
|
|
statement: "GIDs follow the pinned UIDs above; the same image expectation and the same PVC ownership risk apply."
|
|
|
|
- id: AVD-KSV-0110
|
|
statement: "Artifact of scanning rendered manifests: no chart pins a namespace (the release does), so Trivy reads every workload as living in 'default'. Nothing to fix in the charts."
|
|
|
|
# --- samba: deviations forced by the ghcr.io/dockur/samba entrypoint ------
|
|
# All four are justified in samba/values.yaml above its securityContext.
|
|
- id: AVD-KSV-0012
|
|
paths: ["**/samba.yaml"]
|
|
statement: "samba starts as root: the entrypoint creates the Unix users from users.conf and smbd impersonates the authenticated share user per session, both of which require uid 0."
|
|
|
|
- id: AVD-KSV-0014
|
|
paths: ["**/samba.yaml"]
|
|
statement: "samba writes to its root filesystem at startup (/etc/passwd, /etc/group, the /etc/samba.conf healthcheck symlink, the TDB databases under /var/lib/samba), so readOnlyRootFilesystem cannot be true."
|
|
|
|
- id: AVD-KSV-0022
|
|
paths: ["**/samba.yaml"]
|
|
statement: "samba drops ALL and adds back only what a multi-user file server needs: NET_BIND_SERVICE (445/139), SETUID/SETGID (per-session impersonation), CHOWN/FOWNER/DAC_OVERRIDE (share files owned by the users)."
|
|
|
|
- id: AVD-KSV-0106
|
|
paths: ["**/samba.yaml"]
|
|
statement: "Same added capability set as AVD-KSV-0022; the check wants NET_BIND_SERVICE alone, which a file server that impersonates users cannot do."
|
|
|
|
- id: AVD-KSV-0117
|
|
paths: ["**/samba.yaml"]
|
|
statement: "SMB is served on its registered ports 445 and 139, which are privileged by definition - that is why NET_BIND_SERVICE is added."
|
|
|
|
# --- calibre-web-automated: deviations forced by the s6-overlay image -----
|
|
# All three are justified in calibre-web-automated/values.yaml.
|
|
- id: AVD-KSV-0012
|
|
paths: ["**/calibre-web-automated.yaml"]
|
|
statement: "The CWA image boots via s6-overlay, whose stage-1/2 setup runs as uid 0 before dropping the application to PUID/PGID 1000; the supervisors stay root."
|
|
|
|
- id: AVD-KSV-0014
|
|
paths: ["**/calibre-web-automated.yaml"]
|
|
statement: "readOnlyRootFilesystem must stay false: in read-only mode the image disables its PUID/PGID remapping, the runtime uid silently moves from 1000 to the built-in 911 and existing volume data becomes inaccessible."
|
|
|
|
- id: AVD-KSV-0022
|
|
paths: ["**/calibre-web-automated.yaml"]
|
|
statement: "ALL dropped except the minimal s6 set verified against v4.0.6: CHOWN/DAC_OVERRIDE/FOWNER/SETGID/SETUID for the ownership fix-up and the root->PUID drop, KILL so the root supervisor can signal the uid-1000 services."
|
|
|
|
- id: AVD-KSV-0106
|
|
paths: ["**/calibre-web-automated.yaml"]
|
|
statement: "Same added capability set as AVD-KSV-0022; restricting it to NET_BIND_SERVICE would break the s6 init."
|
|
|
|
# --- paperless-ngx: the root init container ------------------------------
|
|
# Justified in paperless-ngx/values.yaml above its securityContext. The pod
|
|
# itself runs rootless as uid/gid 1000; only the init container is root.
|
|
- id: AVD-KSV-0105
|
|
paths: ["**/paperless-ngx.yaml"]
|
|
statement: "Only the init-run-ownership init container runs as uid 0: s6-overlay requires /run to be owned by the runtime uid (group-write is not enough), so it chowns the /run emptyDir before the hardened main container starts. The main container runs as 1000 with runAsNonRoot."
|
|
|
|
- id: AVD-KSV-0022
|
|
paths: ["**/paperless-ngx.yaml"]
|
|
statement: "The same init container drops ALL and adds back only CHOWN, which is the one capability its single chown call needs; it also keeps a read-only root filesystem and no privilege escalation."
|
|
|
|
- id: AVD-KSV-0106
|
|
paths: ["**/paperless-ngx.yaml"]
|
|
statement: "Same CHOWN-only capability set as AVD-KSV-0022; the check wants NET_BIND_SERVICE alone, which an init container that fixes file ownership cannot use."
|
|
|
|
# --- homeassistant: deviations forced by the upstream image ---------------
|
|
# All justified in homeassistant/values.yaml above its securityContext.
|
|
- id: AVD-KSV-0012
|
|
paths: ["**/homeassistant.yaml"]
|
|
statement: "The official Home Assistant container boots via s6-overlay as uid 0 and supports no non-root user, so runAsNonRoot/runAsUser are deliberately unset."
|
|
|
|
- id: AVD-KSV-0118
|
|
paths: ["**/homeassistant.yaml"]
|
|
statement: "The empty pod securityContext is the deliberate form of the same deviation: the image must start as root. The container context is hardened (no privilege escalation, read-only root filesystem, ALL dropped)."
|
|
|
|
- id: AVD-KSV-0022
|
|
paths: ["**/homeassistant.yaml"]
|
|
statement: "ALL dropped, NET_RAW added back because Home Assistant's device discovery (DHCP watcher, ping/arp integrations) uses raw sockets and logs 'Operation not permitted' without it."
|
|
|
|
- id: AVD-KSV-0106
|
|
paths: ["**/homeassistant.yaml"]
|
|
statement: "Same NET_RAW addition as AVD-KSV-0022."
|
|
|
|
- id: AVD-KSV-0119
|
|
paths: ["**/homeassistant.yaml"]
|
|
statement: "NET_RAW is the capability in question and is added on purpose for device discovery; see AVD-KSV-0022."
|
|
|
|
# --- valkey: the two keyword findings on its ConfigMap ---------------------
|
|
# Both are keyword matches on the COMMENTS and the shell script in the
|
|
# ConfigMap, not on data. Worth suppressing with a reason rather than leaving
|
|
# standing, because "ConfigMap stores secrets" on this chart of all charts
|
|
# says the opposite of what it does: the whole design exists to keep the
|
|
# password out of everything Helm renders.
|
|
- id: AVD-KSV-0109
|
|
paths: ["**/valkey.yaml"]
|
|
statement: "False positive on the rendered ConfigMap. The matched strings are the comment '# Nothing here prints the password' and the line pass=\"$(sed -n 's/^requirepass...' \"$AUTH_FILE\")\" of the probe script - i.e. the code that READS the password out of the mounted Secret file at probe time. The ConfigMap contains no credential: the password lives only in the 1Password item, is materialized into a Secret by the operator, mounted as a file and 'include'd by the configuration, which is precisely why no requirepass line is rendered here. Verify by rendering the chart - no value from the vault appears in it."
|
|
- id: AVD-KSV-01010
|
|
paths: ["**/valkey.yaml"]
|
|
statement: "Same class as AVD-KSV-0109 above: the matched keys are 'valkey.conf' and an 'echo \"valkey...' line of the validation script. Both are chart-authored configuration and diagnostics text; nothing user- or credential-derived is stored in this ConfigMap."
|
|
|
|
# --- rss-bridge: port 80 is fixed by the image ----------------------------
|
|
- id: AVD-KSV-0117
|
|
paths: ["**/rss-bridge.yaml"]
|
|
statement: "nginx inside the image listens on 80, and the only lever the entrypoint offers (HTTP_PORT) is a sed against /etc/nginx/sites-enabled/default, which a read-only root filesystem rejects - so the alternative to port 80 is giving up readOnlyRootFilesystem. The chart does not merely rely on the kubelet's ip_unprivileged_port_start=0 default, it pins the sysctl in the pod securityContext, so the bind does not depend on node configuration. Measured in k3d with only that value changed: at 1024 nginx fails with 'bind() to 0.0.0.0:80 failed (13: Permission denied)', and adding NET_BIND_SERVICE does NOT fix it (for a non-root process the capability lands in the permitted but not the effective set) - which is why this chart adds no capability, unlike samba. Justified in rss-bridge/values.yaml above securityContext.pod.sysctls."
|