Day 07 got a service running. This is the version you would defend in a review: least privilege, no writable paths it does not need, and a read-only filesystem everywhere else.
1. The application, and a dedicated account
sudo mkdir -p /opt/metricsd
sudo tee /opt/metricsd/metricsd.sh >/dev/null <<'SCRIPT'
#!/bin/bash
set -euo pipefail
STATE="${STATE_DIRECTORY:?STATE_DIRECTORY not set}"
while true; do
printf '%s cpu_queue=%s\n' "$(date -Is)" "$(awk '{print $1}' /proc/loadavg)" \
>> "$STATE/metrics.log"
sleep 5
done
SCRIPT
sudo chmod 755 /opt/metricsd/metricsd.sh
sudo useradd --system --no-create-home --shell /usr/sbin/nologin metricsdNote the script reads STATE_DIRECTORY from the environment rather than hardcoding a path. systemd sets that variable, which means the unit owns the decision about where state lives.
Verify
2. A unit with the sandbox on
sudo tee /etc/systemd/system/metricsd.service >/dev/null <<'UNIT'
[Unit]
Description=Metrics collector
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/opt/metricsd/metricsd.sh
User=metricsd
Group=metricsd
# systemd creates /var/lib/metricsd, owns it to User=, and exports
# STATE_DIRECTORY. Removes the "who mkdirs this" question entirely.
StateDirectory=metricsd
Restart=on-failure
RestartSec=5
# Sandboxing. Each line removes something the service does not need.
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
PrivateDevices=true
NoNewPrivileges=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
SystemCallArchitectures=native
[Install]
WantedBy=multi-user.target
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now metricsd
systemctl status metricsd --no-pager | head -n 6ProtectSystem=strict mounts the entire filesystem read-only except /dev, /proc, /sys — and whatever StateDirectory created. That combination is the important one: the service can write exactly one place.
Verify
3. Prove the sandbox is real
sudo systemctl stop metricsd
sudo systemd-run --uid=metricsd --property=ProtectSystem=strict \
--property=ProtectHome=true --pty /bin/bash -c 'touch /etc/evil; echo "exit=$?"'The write to /etc fails inside the same sandbox the service runs in. Compare without it:
sudo systemd-run --uid=metricsd --pty \
/bin/bash -c 'touch /tmp/harmless; echo "exit=$?"'The second succeeds because nothing is protecting /tmp in that invocation — which is what PrivateTmp=true fixes in the real unit, by giving the service its own /tmp that vanishes on stop.
Verify
4. Score it
sudo systemctl start metricsd
systemd-analyze security metricsd | tail -n 20
systemd-analyze security metricsd | grep -i "overall exposure"systemd-analyze security grades a unit and lists what is still open. Do not chase a perfect score — read the list and remove what your service genuinely does not need. A collector that only reads /proc and writes one directory should land in the low exposure range.
Verify
5. Give it a resource ceiling
sudo mkdir -p /etc/systemd/system/metricsd.service.d
sudo tee /etc/systemd/system/metricsd.service.d/limits.conf >/dev/null <<'DROPIN'
[Service]
MemoryMax=128M
CPUQuota=20%
TasksMax=32
DROPIN
sudo systemctl daemon-reload
sudo systemctl restart metricsd
systemctl show metricsd -p MemoryMax -p CPUQuotaPerSecUSec -p TasksMaxThese are cgroup limits, enforced by the kernel. MemoryMax means the service gets OOM-killed instead of taking the machine down with it — the difference between one service failing and every service failing.
Verify
Clean up
sudo systemctl disable --now metricsd
sudo rm -rf /etc/systemd/system/metricsd.service \
/etc/systemd/system/metricsd.service.d
sudo rm -rf /opt/metricsd /var/lib/metricsd
sudo userdel metricsd
sudo systemctl daemon-reloadWhere this goes next
Your service is contained. Next: containing who can reach the machine at all.