This documents a hardened setup for running iroh-ssh server as an unprivileged+chrooted systemd service. Sharing for knowledge, not sure how confident I am recommending it as a default on systems I can't fix :)
Why
- Runs as a dedicated headless user (
irohssh) with no capabilities (vs root using the recommended systemd setup).
- Isolated in a chroot (
/var/lib/iroh-ssh-chroot) with only the minimal host paths bind-mounted.
- Separate key directory (
/etc/iroh-ssh) so it does not reuse /root/.ssh.
- Scores 1.1 OK on
systemd-analyze security, and more details.
Setup
# 1. Create dedicated headless user
sudo useradd --system --no-create-home --home-dir /nonexistent --shell /usr/sbin/nologin irohssh
# 2. Create key directory and generate persistent keys
sudo install -d -m 750 -o irohssh -g irohssh /etc/iroh-ssh
sudo -u irohssh timeout 8s /usr/local/bin/iroh-ssh server --persist --key-dir /etc/iroh-ssh --ssh-port 22
sudo chmod 600 /etc/iroh-ssh/irohssh_ed25519
# 3. Create empty chroot root directory
sudo mkdir /var/lib/iroh-ssh-chroot
# 4. Install service file and start
sudo tee /etc/systemd/system/iroh-ssh-server.service > /dev/null <<'UNIT'
... paste service below ...
UNIT
sudo systemctl daemon-reload
sudo systemctl enable --now iroh-ssh-server.service
systemd service file
# /etc/systemd/system/iroh-ssh-server.service
[Unit]
Description=SSH over Iroh (headless chrooted instance)
After=network.target
[Service]
Type=simple
User=irohssh
Group=irohssh
# Chroot root directory. systemd mounts all required paths into it at runtime.
# Only this empty directory needs to exist on disk beforehand.
WorkingDirectory=/etc/iroh-ssh
RootDirectory=/var/lib/iroh-ssh-chroot
# Run in a private user namespace: the service user is mapped to root inside
# the namespace and cannot see other users' UIDs on the host.
PrivateUsers=yes
# Binary and read-only host config mounted into the chroot.
BindReadOnlyPaths=/usr/local/bin/iroh-ssh:/usr/local/bin/iroh-ssh
BindReadOnlyPaths=/etc/passwd:/etc/passwd
BindReadOnlyPaths=/etc/resolv.conf:/etc/resolv.conf
BindReadOnlyPaths=/etc/hosts:/etc/hosts
BindReadOnlyPaths=/etc/ssl/certs:/etc/ssl/certs
# If your iroh-ssh binary is dynamically linked (e.g. custom arm64 build),
# uncomment these so shared libraries are available inside the chroot.
# BindReadOnlyPaths=/lib:/lib
# BindReadOnlyPaths=/lib64:/lib64
# BindReadOnlyPaths=/usr/lib:/usr/lib
# BindReadOnlyPaths=/usr/lib64:/usr/lib64
# Keys directory — writable inside the chroot.
BindPaths=/etc/iroh-ssh:/etc/iroh-ssh
ExecStart=/usr/local/bin/iroh-ssh server -p --key-dir /etc/iroh-ssh --ssh-port 22
Restart=on-failure
RestartSec=3s
PrivateTmp=yes
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/etc/iroh-ssh
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
ProtectClock=yes
ProtectHostname=yes
ProtectProc=invisible
ProcSubset=pid
CapabilityBoundingSet=
RestrictSUIDSGID=yes
RestrictRealtime=yes
RestrictNamespaces=yes
LockPersonality=yes
RemoveIPC=yes
PrivateDevices=yes
MemoryDenyWriteExecute=yes
SystemCallFilter=@system-service
SystemCallFilter=~@resources @privileged
SystemCallErrorNumber=EPERM
SystemCallArchitectures=native
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK
UMask=0077
[Install]
WantedBy=multi-user.target
Verify
sudo systemctl status iroh-ssh-server.service
sudo systemd-analyze security iroh-ssh-server.service
This documents a hardened setup for running
iroh-ssh serveras an unprivileged+chrooted systemd service. Sharing for knowledge, not sure how confident I am recommending it as a default on systems I can't fix :)Why
irohssh) with no capabilities (vsrootusing the recommended systemd setup)./var/lib/iroh-ssh-chroot) with only the minimal host paths bind-mounted./etc/iroh-ssh) so it does not reuse/root/.ssh.systemd-analyze security, and more details.Setup
systemd service file
Verify