rss-bridge: pin the unprivileged-port sysctl, quote env values

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.
This commit is contained in:
jklein committed 2026-08-23 23:39:49 +02:00
1 parent e016650f91
commit 7e6b3beed5
4 files changed
+53 -10

No files matched your search

+32
View File
@@ -54,6 +54,38 @@ rssBridge:
fsGroupChangePolicy: Always
seccompProfile:
type: RuntimeDefault
# Why this chart listens on the privileged port 80, and why that is safe.
#
# nginx is configured for port 80 inside the image. The only lever the
# entrypoint offers is HTTP_PORT, which runs a sed against
# /etc/nginx/sites-enabled/default - impossible with a read-only root
# filesystem. So the choice is port 80 or readOnlyRootFilesystem, and
# port 80 is the cheaper of the two. (The Service selects the port by
# NAME, so the number is invisible to it either way.)
#
# Kubernetes sets net.ipv4.ip_unprivileged_port_start=0 in the pod
# network namespace, so binding 80 needs no capability at all. But that
# is a runtime default, not a guarantee, and relying on it silently is
# exactly the trap the template-chart warns about - so the chart pins the
# value instead of inheriting it.
#
# Measured in k3d (k3s v1.35.5), same pod spec, only this value changed:
# sysctl 1024, capabilities.drop [ALL] -> nginx: bind() to
# 0.0.0.0:80 failed (13: Permission denied)
# sysctl 1024, plus NET_BIND_SERVICE added -> identical failure.
# Adding the capability does NOT help: for a non-root process it
# lands in the permitted set but not the effective/ambient one, so
# it buys nothing here. That is why this chart does not add it.
# sysctl 0 (this default) -> binds, HTTP 200.
#
# Note how that failure looks without probes: the entrypoint starts nginx
# in the background and then execs php-fpm, so a failed bind leaves a
# container that reports Running with no web server at all. The probes on
# /?action=health turn that silent failure into a pod that never becomes
# Ready.
sysctls:
- name: net.ipv4.ip_unprivileged_port_start
value: "0"
container:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true