Tillered Docs
Maintenance

Hardening

Run the Arctic agent as a non-root user under a hardened systemd unit

The default installation runs the agent as root. The agent does not actually need root; it needs a small set of Linux capabilities, chiefly CAP_NET_ADMIN. This guide replaces the bundled installer with a hand-built systemd unit that runs the agent as a dedicated unprivileged user with only the capabilities it uses, plus systemd sandboxing to limit what a compromised agent could touch.

You take over the lifecycle

A hardened unit is incompatible with the bundled lifecycle commands. Do not run agent install or agent upgrade on a host set up this way: both require root, and agent upgrade rewrites arctic.service back to the default root unit, silently undoing your hardening. Once you go down this path, you install, upgrade, and manage the service by hand. The steps for a manual upgrade are at the end of this page.

This is an advanced, operator-maintained configuration. It is not the path the installer takes and is not exercised by the project's release testing, so validate it in staging before you rely on it.

What the agent needs

The agent process has no root check; it reads and writes kernel network state through capabilities:

  • CAP_NET_ADMIN is the one that matters. It covers committing firewall rules over netlink, creating MACVLAN and TUN devices, managing ip rules and routes, the transparent-proxy socket option, and the traffic-shaping queues used for QoS.
  • CAP_NET_RAW is needed only on hosts that fall back to the legacy xtables path because nftables is unavailable. Most hosts never use it.
  • No privileged ports are involved. The API (8080), TProxy (61000), and IP tunnel (51840) all bind above 1024, so CAP_NET_BIND_SERVICE is not required.

The in-process IP tunnel uses per-peer TUN devices rather than network namespaces, so CAP_SYS_ADMIN is not required either. That is what makes a non-root agent practical.

At runtime the agent writes to two locations only: its data directory (/opt/tillered by default, holding the database and peer.key) and /etc/arctic (the per-boot recovery token). Everything else it does is kernel state, not files on disk.

Manual setup

1. Create a service user

sudo groupadd --system arctic
sudo useradd --system --gid arctic --no-create-home \
  --shell /usr/sbin/nologin arctic

2. Install the binary

Download the agent binary and place it where the unit expects it. Keep root as the owner so the service user cannot replace its own binary:

sudo mkdir -p /opt/tillered/bin
sudo curl -fsSL https://release.tillered.com/arctic/latest/agent_amd64 \
  -o /opt/tillered/bin/arctic-agent
sudo chmod 0755 /opt/tillered/bin/arctic-agent

Use agent_arm64 on ARM hosts. Verify the binary signature before trusting it; see Installation.

3. Create and own the writable directories

The service user owns the data directory and the recovery-token directory. Pre-creating /etc/arctic matters: the agent cannot create it itself as a non-root user, and without it the recovery token is disabled.

sudo mkdir -p /opt/tillered
sudo chown arctic:arctic /opt/tillered

sudo mkdir -p /etc/arctic
sudo chown arctic:arctic /etc/arctic
sudo chmod 0700 /etc/arctic

4. Kernel modules for QoS

QoS relies on the kernel's traffic-shaping modules. Under CAP_NET_ADMIN the kernel loads them on demand, so a typical host needs no action here. On hosts that block on-demand module loading, make sure the traffic-shaping modules your kernel uses are available at boot.

5. Write the systemd unit

Create /etc/systemd/system/arctic.service:

[Unit]
Description=Arctic Network Routing Agent
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=arctic
Group=arctic
ExecStart=/opt/tillered/bin/arctic-agent
AmbientCapabilities=CAP_NET_ADMIN
CapabilityBoundingSet=CAP_NET_ADMIN
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/tillered /etc/arctic
DeviceAllow=/dev/net/tun rw
Restart=on-failure
RestartSec=5s
StandardOutput=journal
StandardError=journal
Environment=DATA_DIR=/opt/tillered
Environment=LOG_FORMAT=json

[Install]
WantedBy=multi-user.target

On hosts without nftables that need the xtables fallback, add CAP_NET_RAW to both AmbientCapabilities and CapabilityBoundingSet.

6. Enable and start

sudo systemctl daemon-reload
sudo systemctl enable --now arctic

7. Bootstrap

Bootstrapping is unchanged; it is an API call to the running agent:

arctic bootstrap --url http://localhost:8080 \
  --license-file license.json --name node-a

Verify

Confirm the agent answers and is running as the unprivileged user:

curl http://localhost:8080/livez

systemctl show arctic --property User --property MainPID
ps -o user= -p "$(systemctl show arctic --property MainPID --value)"

The ps output should read arctic, not root. Check that the recovery token was written under the service user:

sudo ls -l /etc/arctic/recovery.token

If the token is missing, the agent could not write /etc/arctic; recovery is disabled but the agent otherwise runs normally. Re-check the directory's owner and permissions from step 3.

Upgrading by hand

Because you cannot use agent upgrade, upgrade by replacing the binary and restarting. Your unit file is left untouched:

sudo systemctl stop arctic
sudo curl -fsSL https://release.tillered.com/arctic/latest/agent_amd64 \
  -o /opt/tillered/bin/arctic-agent
sudo chmod 0755 /opt/tillered/bin/arctic-agent
sudo systemctl start arctic

Verify the new version afterwards:

journalctl -u arctic --since "2 minutes ago"
arctic license status

For a multi-peer cluster, upgrade one peer at a time and confirm each is healthy before moving on, as in Upgrades.

Caveats

  • /dev/net/tun must exist and be accessible. On most distributions it is world-accessible (crw-rw-rw-). If it is missing, load the tun module.
  • Recovery depends on /etc/arctic being writable by the service user. If it is not, break-glass recovery is unavailable on that host.
  • ProtectSystem=strict makes the filesystem read-only apart from ReadWritePaths. If you move the data directory, update both the DATA_DIR environment line and ReadWritePaths.
  • Layer on more sandboxing carefully. Directives such as RestrictAddressFamilies or SystemCallFilter can tighten the unit further, but the agent needs netlink, packet, and inet socket families; test each addition in staging rather than adding them blind.
  • You own upgrades and unit changes. Nothing in the agent will rewrite or update this unit for you, which is the point, but it also means security updates to the recommended unit are yours to apply.

See also

On this page