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.