0

I am hardening my systemd service file for openconnect(8). In my setup, I am using vpn-slice to setup routes (I pass arguments such that it doesn't write to any files) and use various files to define connection parameters and credentials. My target system is Debian Buster (systemd version 241).

This is what I came up with:

[Unit]
Description=Openconnect VPN to %i
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=-/etc/openconnect/_default.conf
EnvironmentFile=/etc/openconnect/%i.conf
StandardInput=file:/etc/openconnect/%i.pwd
# StandardOutput is changed to `inherit` when setting StandardInput.
StandardOutput=journal
ExecStart=/usr/sbin/openconnect --non-inter --setuid=nobody --passwd-on-stdin --user "${OC_USER}" --script "${OC_SCRIPT}" ${OC_OPTIONS} "${OC_SERVER}"

Restart=always
RestartSec=1s

## security
CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE
# required to downgrade to `nobody`
CapabilityBoundingSet=CAP_SETGID CAP_SETUID
NoNewPrivileges=true
RestrictAddressFamilies=AF_INET AF_INET6 AF_NETLINK
LockPersonality=true
MemoryDenyWriteExecute=true
#PrivateDevices=yes
#DeviceAllow=/dev/net/tun rwm
#DeviceAllow=char-misc rwm
#ReadWritePaths=/dev/net/
#PrivateUsers=true
PrivateTmp=true
ProtectSystem=strict
ProtectControlGroups=true
ProtectHome=true
ProtectHostname=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ReadWritePaths=/proc/sys/net/ipv4/route/flush
RestrictNamespaces=true
RestrictRealtime=true
SystemCallArchitectures=native
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
SystemCallFilter=@setuid
RemoveIPC=true

[Install]
WantedBy=multi-user.target

Basically I see two issues with my current unit definiton:

  1. I couldn't get the PrivateDevices running. When activating the DeviceAllow and ReadWritePaths above, the unit fails early:
openconnect@abc.service: Failed to set up mount namespacing: No such file or directory
openconnect@abc.service: Failed at step NAMESPACE spawning /usr/sbin/openconnect: No such file or directory

When I leave out the ReadWritePaths, the vpn-slice script fails instead:

Traceback (most recent call last):
   File "/usr/bin/vpn-slice", line 11, in <module>
     load_entry_point('vpn-slice==0.15', 'console_scripts', 'vpn-slice')()
   File "/usr/lib/python3/dist-packages/vpn_slice/__main__.py", line 592, in main
     do_pre_init(env, args)
   File "/usr/lib/python3/dist-packages/vpn_slice/__main__.py", line 112, in do_pre_init
     providers.prep.create_tunnel()
   File "/usr/lib/python3/dist-packages/vpn_slice/linux.py", line 106, in create_tunnel
     os.makedirs(os.path.dirname(node), exist_ok=True)
   File "/usr/lib/python3.7/os.py", line 221, in makedirs
     mkdir(name, mode)
 OSError: [Errno 30] Read-only file system: '/dev/net'
  1. Right now I am passing --setuid=nobody to openconnect. However, when I activate PrivateUsers=true openconnect fails:
Failed to bind local tun device (TUNSETIFF): Operation not permitted
To configure local networking, openconnect must be running as root

So, I was wondering if this is the best approach in the first place and experimented with running the entire unit as User=nobody, but couldn't get this working either (I tried setting AmbientCapabilities=).

Since the --setuid requires several privileges I am also wondering if it could be more secure in the end to just let it run as root formally while having all the limitations above.

Any advice for a more secure setup is highly appreciated.

steiny
  • 163
  • 2
  • 8

0 Answers0