Repository navigation
infra: harden the dashboard pod like the API (read-only root, no token, namespace) - #35
Conversation
AbdikhaniAbdirashid
left a comment
There was a problem hiding this comment.
Approving. This is my file, so I checked each of the four changes against the API's manifests and
against what nginx actually writes, instead of only reading the diff.
It matches the API now, field for field. k8s/app/deployment.yaml has namespace: relay (line
9), automountServiceAccountToken: false (32), runAsUser (37) and readOnlyRootFilesystem: true
(108); service.yaml and ingress.yaml carry the namespace too. The dashboard was the only thing
in k8s/app/ without any of it.
/tmp is genuinely the only writable place nginx needs here. I went through my own
dashboard/nginx.conf looking for anything that writes: no proxy_pass, so no proxy_temp; no
proxy_cache, so nothing under /var/cache/nginx; gzip compresses in memory and needs no temp
file; both locations are try_files on static files. That leaves the pid file and the body temp
dir, which nginx-unprivileged already points at /tmp. 16Mi is plenty for a server that only
answers GETs.
Server dry run, from the manifest on this branch:
deployment.apps/relay-dashboard configured (server dry run)
service/relay-dashboard unchanged (server dry run)
ingress.networking.k8s.io/relay-dashboard unchanged (server dry run)
So Pod Security restricted accepts it, and the two unchanged lines also confirm the namespace
field only writes down where those objects already live - it doesn't move anything.
One thing I want to state because I'll be asked about it. With a read-only root the image's
10-listen-on-ipv6-by-default.sh can no longer rewrite default.conf, which is the info: line
you saw. The consequence is that nginx listens on IPv4 only - fine for us on purpose, not by luck:
the cluster is IPv4-only and we deliberately publish no AAAA records, so Traefik reaches the pod
over IPv4. Worth remembering as the reason that log line is harmless rather than something to
silence.
runAsUser: 101 written out is right for the same reason the API writes 1000: Kubernetes never
looks inside the image, so a base-image bump that changed USER would otherwise change who we run
as without any file in git changing.
Thanks for taking the follow-ups from your own #24 review rather than handing them back to me.
What
The four follow-ups from my review of #24, so you only have to review them. One file,
k8s/app/dashboard.yaml; the dashboard then matches the API's hardening.namespace: relayon the Deployment, Service and Ingresskubectl applywith the admin kubeconfig would otherwise land indefaultautomountServiceAccountToken: falsereadOnlyRootFilesystem: true+ anemptyDiron/tmp(16Mi)nginx-unprivilegedwrites only under/tmp(pid /tmp/nginx.pid,client_body_temp_path /tmp/client_temp, …), not/varas the old comment saidrunAsUser: 101USERlineChecked
With the real image (
relay-dashboard:c145056, pulled anonymously), run the way the pod will run:--user 101 --read-only --tmpfs /tmp --cap-drop ALL --security-opt no-new-privileges:The only non-access log line is the IPv6 entrypoint script saying it can't edit the config
(
info:, then "Configuration complete; ready for start up"). The manifest parses, and all threeobjects carry
namespace: relay.Server dry run for Pod Security (from the laptop, any commit's image tag works):
If the pods ever failed at runtime anyway,
relay-deployrolls the commit back, which #34 nowtests for the dashboard.