[Unit] Description=lchat public fleet hardening posture (one collect pass) After=network-online.target Wants=network-online.target [Service] Type=oneshot # Optional overrides: STATUS_BUCKET / STATUS_REGION (mirror status-publish.sh). EnvironmentFile=-/data/status.env # "+" = outside the sandbox: both ReadWritePaths must exist before # ProtectSystem=strict mounts them, or the first-ever run fails to namespace. ExecStartPre=+/usr/bin/mkdir -p /data/hardening /opt/lchat/status-pages ExecStart=/opt/lchat/hardening-posture.sh # --- hardening (see docs/instance-router.md § Hardening) --- # Runs as ROOT deliberately: the LOCAL box's own posture reads are root-only # (sshd -T's effective config, ufw status, ip link), and /data/hardening + # /opt/lchat/status-pages are its only writes — ProtectSystem=strict pins # exactly those. It is fired by lchat-hardening.timer, never routed or # listening, and publishes NO hostnames (see the script's header). The peer :22 # probe is an outbound connect only. ProtectSystem=strict ReadWritePaths=/data/hardening /opt/lchat/status-pages ProtectHome=yes PrivateTmp=yes PrivateDevices=yes ProtectClock=yes ProtectHostname=yes ProtectKernelTunables=yes ProtectKernelModules=yes ProtectKernelLogs=yes NoNewPrivileges=yes # AF_NETLINK: `ufw status` and `ip link show wg0` are netlink queries — without # it both probes fail silently and the box publishes false "firewall inactive"/ # "wireguard absent" alarms (the dashboard's first real pass, 2026-07-19). # Read-only in effect: the collector never issues netlink writes. RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK RestrictNamespaces=yes RestrictRealtime=yes LockPersonality=yes MemoryDenyWriteExecute=yes MemoryMax=256M TasksMax=64