Create an exception for a regular user to access service socket using systemd.
I will use HAProxy to show the standard group
based technique and the systemd/ACLs
approach.
Prerequisites
Create a dedicated user and group that will be used in this example.
$ sudo groupadd --gid 2000 milosz
$ sudo useradd --uid 2000 --gid 2000 --shell /bin/bash --create-home milosz
Install utilities needed to manipulate access control lists.
$ sudo apt install acl
Standard approach
Inspect HAProxy service.
$ sudo systemctl status haproxy
● haproxy.service - HAProxy Load Balancer Loaded: loaded (/lib/systemd/system/haproxy.service; enabled; vendor preset: enabled) Active: active (running) since Sun 2021-07-11 14:17:08 UTC; 3min 9s ago Docs: man:haproxy(1) file:/usr/share/doc/haproxy/configuration.txt.gz Process: 589 ExecStartPre=/usr/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS (code=exited, status=0/SUCCESS) Main PID: 595 (haproxy) Tasks: 33 (limit: 1123) Memory: 96.7M CPU: 1.511s CGroup: /system.slice/haproxy.service ├─595 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock └─602 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock Jul 11 14:17:08 bullseye systemd[1]: Starting HAProxy Load Balancer... Jul 11 14:17:08 bullseye haproxy[595]: [NOTICE] 191/141708 (595) : New worker #1 (602) forked Jul 11 14:17:08 bullseye systemd[1]: Started HAProxy Load Balancer.
This is the default HAProxy configuration that describes the socket parameters.
stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners
This configuration is reflected in the filesystem.
$ ls -l /run/haproxy/admin.sock
srw-rw---- 1 root haproxy 0 Jul 11 13:06 /run/haproxy/admin.sock
Regular user cannot access it.
$ echo "show pools" | sudo -u milosz socat stdio unix-connect:/var/run/haproxy/admin.sock
2021/07/11 13:21:08 socat[1261] E connect(5, AF=1 "/var/run/haproxy/admin.sock", 29): Permission denied
As it needs to belong to the haproxy
group.
$ sudo usermod -G haproxy milosz
$ echo "show pools" | sudo -u milosz socat stdio unix-connect:/var/run/haproxy/admin.sock
Dumping pools usage. Use SIGQUIT to flush them. - Pool comp_state (32 bytes) : 9 allocated (288 bytes), 9 used, needed_avg 5, 0 failures, 6 users, @0x55e4cb9ef940=09 [SHARED] - Pool filter (64 bytes) : 44 allocated (2816 bytes), 44 used, needed_avg 40, 0 failures, 9 users, @0x55e4cb9ef8c0=08 [SHARED] - Pool pendconn (96 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 4 users, @0x55e4cb9ef9c0=10 [SHARED] - Pool ssl_sock_ct (128 bytes) : 3 allocated (384 bytes), 3 used, needed_avg 1, 0 failures, 4 users, @0x55e4cb9ef4c0=00 [SHARED] - Pool h1c (160 bytes) : 41 allocated (6560 bytes), 41 used, needed_avg 37, 0 failures, 5 users, @0x55e4cb9ef640=03 [SHARED] - Pool fcgi_strm (192 bytes) : 6 allocated (1152 bytes), 6 used, needed_avg 2, 0 failures, 4 users, @0x55e4cb9ef540=01 [SHARED] - Pool tcpcheck_ru (224 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 2 users, @0x55e4cb9ef7c0=06 [SHARED] - Pool authority (256 bytes) : 3 allocated (768 bytes), 3 used, needed_avg 1, 0 failures, 2 users, @0x55e4cb9efb40=13 [SHARED] - Pool spoe_ctx (320 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9ef840=07 [SHARED] - Pool dns_resolut (480 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9efa40=11 [SHARED] - Pool dns_answer_ (576 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9efac0=12 [SHARED] - Pool requri (1024 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9efc40=15 [SHARED] - Pool stream (1088 bytes) : 3 allocated (3264 bytes), 3 used, needed_avg 1, 0 failures, 1 users, @0x55e4cb9ef740=05 [SHARED] - Pool fcgi_conn (1216 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9ef5c0=02 [SHARED] - Pool h2c (1312 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9ef6c0=04 [SHARED] - Pool hpack_tbl (4096 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9efd40=17 [SHARED] - Pool buffer (16384 bytes) : 7 allocated (114688 bytes), 6 used, needed_avg 3, 0 failures, 1 users, @0x55e4cb9efcc0=16 [SHARED] - Pool trash (16416 bytes) : 3 allocated (49248 bytes), 3 used, needed_avg 1, 0 failures, 1 users, @0x55e4cb9efdc0=18 Total: 18 pools, 179168 bytes allocated, 162784 used.
You can configure multiple sockets using different permissions.
stats socket /run/haproxy/admin.sock mode 600 level admin stats socket /run/haproxy/operator.sock mode 660 level operator user haproxy group haproxy stats socket /run/haproxy/user.sock mode 660 level user uid 106 gid 112
This is enough to cover almost every usage scenario, but there is a different approach that proved to be useful in some specific edge cases inside short-lived containers.
systemd/ACLs approach
Ensure that the user used in this example is removed from supplementary groups.
$ sudo usermod -G "" milosz
There will be no additional entries in the file access control list.
$ getfacl --absolute-names /run/haproxy/admin.sock
# file: /run/haproxy/admin.sock # owner: root # group: haproxy user::rw- group::rw- other::---
The sample user will have no access to the socket.
$ echo "show pools" | sudo -u milosz -i socat stdio unix-connect:/run/haproxy/admin.sock
2021/07/11 14:18:42 socat[860] E connect(5, AF=1 "/run/haproxy/admin.sock", 25): Permission denied
Set ACL on a socket file for this particular user.
$ sudo setfacl -m "u:milosz:rw-" /run/haproxy/admin.sock
Notice the plus sign in file list.
$ ls -l /run/haproxy/admin.sock
srw-rw----+ 1 root haproxy 0 Jul 11 14:24 /run/haproxy/admin.sock
Inspect altered socket ACLs.
$ getfacl --absolute-names /run/haproxy/admin.sock
# file: /run/haproxy/admin.sock # owner: root # group: haproxy user::rw- user:milosz:rw- group::rw- mask::rw- other::---
Socket operations will work as expected.
$ echo "show pools" | sudo -u milosz -i socat stdio unix-connect:/run/haproxy/admin.sock
Dumping pools usage. Use SIGQUIT to flush them. - Pool comp_state (32 bytes) : 11 allocated (352 bytes), 11 used, needed_avg 7, 0 failures, 6 users, @0x55e4cb9ef940=09 [SHARED] - Pool filter (64 bytes) : 52 allocated (3328 bytes), 52 used, needed_avg 48, 0 failures, 9 users, @0x55e4cb9ef8c0=08 [SHARED] - Pool pendconn (96 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 4 users, @0x55e4cb9ef9c0=10 [SHARED] - Pool ssl_sock_ct (128 bytes) : 5 allocated (640 bytes), 5 used, needed_avg 2, 0 failures, 4 users, @0x55e4cb9ef4c0=00 [SHARED] - Pool h1c (160 bytes) : 45 allocated (7200 bytes), 45 used, needed_avg 41, 0 failures, 5 users, @0x55e4cb9ef640=03 [SHARED] - Pool fcgi_strm (192 bytes) : 10 allocated (1920 bytes), 10 used, needed_avg 6, 0 failures, 4 users, @0x55e4cb9ef540=01 [SHARED] - Pool tcpcheck_ru (224 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 2 users, @0x55e4cb9ef7c0=06 [SHARED] - Pool authority (256 bytes) : 5 allocated (1280 bytes), 5 used, needed_avg 2, 0 failures, 2 users, @0x55e4cb9efb40=13 [SHARED] - Pool spoe_ctx (320 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9ef840=07 [SHARED] - Pool dns_resolut (480 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9efa40=11 [SHARED] - Pool dns_answer_ (576 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9efac0=12 [SHARED] - Pool requri (1024 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9efc40=15 [SHARED] - Pool stream (1088 bytes) : 5 allocated (5440 bytes), 5 used, needed_avg 2, 0 failures, 1 users, @0x55e4cb9ef740=05 [SHARED] - Pool fcgi_conn (1216 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9ef5c0=02 [SHARED] - Pool h2c (1312 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9ef6c0=04 [SHARED] - Pool hpack_tbl (4096 bytes) : 0 allocated (0 bytes), 0 used, needed_avg 0, 0 failures, 1 users, @0x55e4cb9efd40=17 [SHARED] - Pool buffer (16384 bytes) : 11 allocated (180224 bytes), 10 used, needed_avg 6, 0 failures, 1 users, @0x55e4cb9efcc0=16 [SHARED] - Pool trash (16416 bytes) : 5 allocated (82080 bytes), 5 used, needed_avg 2, 0 failures, 1 users, @0x55e4cb9efdc0=18 Total: 18 pools, 282464 bytes allocated, 266080 used.
Use systemd to apply this configuration every time after the service will start.
$ sudo mkdir /etc/systemd/system/haproxy.service.d
$ cat <<EOF | sudo tee /etc/systemd/system/haproxy.service.d/allow_socket_access_by_milosz.conf [Service] ExecStartPost=/usr/bin/setfacl -m "u:milosz:rw-" /run/haproxy/admin.sock EOF
Reload systemd daemon configuration.
$ sudo systemctl daemon-reload
Inspect HAProxy service after it will start.
$ sudo systemctl status haproxy
● haproxy.service - HAProxy Load Balancer Loaded: loaded (/lib/systemd/system/haproxy.service; enabled; vendor preset: enabled) Drop-In: /etc/systemd/system/haproxy.service.d └─allow_socket_access_by_milosz.conf Active: active (running) since Sun 2021-07-11 14:24:57 UTC; 15s ago Docs: man:haproxy(1) file:/usr/share/doc/haproxy/configuration.txt.gz Process: 582 ExecStartPre=/usr/sbin/haproxy -f $CONFIG -c -q $EXTRAOPTS (code=exited, status=0/SUCCESS) Process: 594 ExecStartPost=/usr/bin/setfacl -m u:milosz:rw- /run/haproxy/admin.sock (code=exited, status=0/SUCCESS) Main PID: 591 (haproxy) Tasks: 33 (limit: 1123) Memory: 50.8M CPU: 425ms CGroup: /system.slice/haproxy.service ├─591 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock └─593 /usr/sbin/haproxy -Ws -f /etc/haproxy/haproxy.cfg -p /run/haproxy.pid -S /run/haproxy-master.sock Jul 11 14:24:56 bullseye systemd[1]: Starting HAProxy Load Balancer... Jul 11 14:24:57 bullseye haproxy[591]: [NOTICE] 191/142456 (591) : New worker #1 (593) forked Jul 11 14:24:57 bullseye systemd[1]: Started HAProxy Load Balancer.