# dashboards-routectl@.service # # The route-switch helper, root, socket-activated, one request per instance. # # The privilege model, in one line: the grant is a file mode systemd enforces # (the socket is 0600 and owned by the agent user), not a sudoers line. This # unit is how the agent's one narrow write reaches root without the agent's # own unit being given anything — NoNewPrivileges=true is kept *here* as well, # because the privilege comes from being started by systemd as root, not from # escalation. # # Write scope, stated out loud: the helper replaces exactly two kinds of # config files, and os.replace needs write on the *directory*, so # ReadWritePaths lists every directory whose file it replaces — and nothing # else: # /etc/haproxy the haproxy.cfg `server` line (the haproxy arm) # /etc/nginx/conf.d the vhost `proxy_pass` line (the vhost arm) # /etc/nginx/stream.d the `map` entry (the stream arm) and the helper's # own sink file, 00-dashboards-sink.conf, which it # creates and removes there # /etc/nginx (the main config) is deliberately not listed: the helper never # writes it. # # One connection, one instance: the socket's `Accept=yes` hands this template a # fresh connected socket on stdin per connection (StandardInput=socket / # StandardOutput=socket); the helper answers exactly one newline-delimited JSON # request and exits, so there is never a long-lived privileged process sitting # on a socket. [Service] Type=simple ExecStart=/usr/local/sbin/dashboards-routectl.py --serve \ --allow-uid dashboards-agent --state /var/lib/dashboards-routectl/state.json User=root StandardInput=socket StandardOutput=socket StateDirectory=dashboards-routectl ProtectSystem=read-only ReadWritePaths=/etc/haproxy /etc/nginx/conf.d /etc/nginx/stream.d ProtectHome=read-only PrivateTmp=yes NoNewPrivileges=true SystemCallFilter=@system-service