Follow-up to the template-chart hardening fixes in #133, which landed while this branch was open. The env block now quotes names, values and keyRef fields. Without it a value like "5" renders as a YAML number and the API server rejects the whole manifest. RSS-Bridge takes numeric settings that way (RSSBRIDGE_http_timeout), so this was reachable with the first env entry anyone adds. The port-80 question is settled by measurement rather than by the "it works in k3d" reasoning #133 warns about. Same pod spec in k3d (k3s v1.35.5), only ip_unprivileged_port_start changed: 1024, capabilities.drop [ALL] nginx: bind() to 0.0.0.0:80 failed (13: Permission denied) 1024, plus NET_BIND_SERVICE identical failure - for a non-root process the capability lands in the permitted but not the effective set, so it buys nothing 0 binds, HTTP 200 So the chart cannot buy safety with a capability the way samba does, and port 80 is not negotiable either: nginx is configured for it inside the image and the entrypoint's only lever is a sed against a read-only /etc/nginx. What it can do is stop depending on the node default - the pod securityContext now pins the sysctl to 0, so the bind no longer relies on cluster configuration. Verified: the chart-installed pod reports 0 and answers 200 through the Service. Worth recording how that failure looks without probes: the entrypoint backgrounds nginx and then execs php-fpm, so a failed bind leaves a container reporting Running with no web server at all. Both test pods above were Running. The probes on /?action=health are what turn it into a pod that never becomes Ready. trivy config still reports 0 to act on; the KSV-0117 statement is updated to the measured reasoning.
116 lines
7.7 KiB
YAML
116 lines
7.7 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. 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."
|
|
|
|
# --- 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."
|