Sharing Unix sockets between multiple users
I am sure that if you managed a Linux system for a while, you probably have dealt with Unix sockets—special files that act like sockets. You probably also run into permission issues when dealing with these socket files.
In this post, I’ll describe some methods of dealing with these permission issues, and a situation in which each might apply.
Method 1: Unix Groups
If your Unix socket only needs to be accessed by one client, then the easiest
approach is probably to leverage the group part of Unix permissions. If the
group of the Unix socket is set to a group that contains the user the client
is running under, and the group is granted rw
permission, then the client
will be able to connect to the Unix socket. (Note that this assumes the
client is able to access all parent directories.)
The most common situation that I use this method for is the connection
between a reverse proxy server, such as nginx, and a web application server,
such as uwsgi
or a FastCGI server.
This is most easily done if the program supports creating sockets as root
and configuring the permissions on the socket before dropping root
access.
For example, spawn-fcgi
can be run as root
and supports configuring the
user (-U
), group (-G
), and mode (-M
) of the socket before switching
users. You can then set the group to nginx
(if your nginx runs with this
group) and mode to 660
and nginx will be able to access the socket.
uwsgi
is similar, although the group of the socket has to be the same as
the group to switch to after dropping privileges. This may pose a security
issue if your application is complex, but most of the time you could simply
run uwsgi --gid nginx
and solve the problem.
Sometimes, the program itself doesn’t support this, and you may need to
use external mechanisms to launch it with the correct group. Certain
launchers may not support setting the group, and in such cases, the sg(1)
command may prove helpful. Note that sg
will prompt for a password unless
the user it is running as is part of the group specified on the command line.
Method 2: Proxy Server
Sometimes, the service needs to be accessed by multiple clients and configuring the group ID may not be helpful. In such cases, it might be better to use another process to proxy the connection instead, running with the desired permissions.
To create socket /tmp/target.sock
with user quantum
, group example
, and
mode 660
which forwards to /tmp/source.sock
can be done with this command:
socat UNIX-CONNECT:/tmp/source.sock UNIX-LISTEN:/tmp/target.sock,user=quantum,group=example,mode=660,fork
Since you can create multiple sockets that refer to the same resource, you can
create as many sockets as you need, all with different permissions, as long
as you run socat
under a user that can connect to the socket.
Note that the fork
option is important, or otherwise socat
will just
handle one connection before exiting.
Method 3: ACLs
Sometimes, socat
doesn’t cut it. For example, you may be dealing with low
latency things like audio, or you are handling many clients. In such a case,
you want the exact same underlying socket to be available to multiple
different clients all with different users and groups.
In such a case, you can use access control lists, which allow you to specify
exactly what access each user and group has to a file. For example, to grant
user quantum
and group example
access to /tmp/example.sock
, you can
use the setfacl
command as follows:
setfacl -m u:quantum:rw /tmp/example.sock
setfacl -m g:example:rw /tmp/example.sock
The downside of this method is that you usually have to run a bunch of
commands after the process creates the socket, which usually requires some
sort of wait loop for the process to start or a race-prone call to sleep
.
This also does nothing when you can’t change the path of the socket, and
the directory’s permissions do not allow access by other users. Note that
whether users will be able to access files in a directory at all is
determined by the executable bit (x
) on the directory. This is separate
from being able to get the list of files, which is determined by the read
bit (r
).
Method 4: Directory Permissions and File Bind Mounts
And this brings us to the craziest scenario, which I ran into a while ago.
I was running a virtual machine on my Linux desktop, and I wanted to use
QEMU’s JACK audio support to output audio to PipeWire (with the JACK
drop-in support). I was using libvirt which ran the virtual machine under
the libvirt-qemu
user and thus was unable to access the PipeWire socket
/run/user/1000/pipewire-0
.
For security reasons, I do not wish to run QEMU under my user, and I
absolutely cannot allow other users any access to /run/user/1000
, which
may allow them to attack things like my SSH agent.
Note that PipeWire creates the socket with mode 666
and all access
control is done by the 700
mode on /run/user/1000
.
So ideally, I would like an alternative path to the PipeWire socket that QEMU could access.
An obvious thing to try would be symbolic links, but that would never work because symlinks are resolved by the user, which then needs access to the destination.
So instead, I came up with a crazy approach: use a mount point. Normally,
we associate mounting with directories, but we could in fact bind mount
a single file. So, if we create /example
which libvirt-qemu
could
access, bind mount /example/pipewire-0
to /run/user/1000/pipewire-0
,
and set the environment variable PIPEWIRE_RUNTIME_DIR
to /example
.
Then, PipeWire should be able to access the socket despite running under
a different user!
The obvious solution with root
access is thus:
# mkdir /example
# chown libvirt-qemu: /example
# chmod 700 /example
# touch /example/pipewire-0
# mount --bind /run/user/1000/pipewire-0 /example/pipewire-0
However, I do not wish to run this as root
. So instead, I created an
entry in /etc/fstab
allowing me to perform this mount as myself:
/run/user/1000/pipewire-0 /example/pipewire-0 none bind,rw,user,noauto 0 0
The important options here are user
, which allows non-root
users to
perform the mount operation, and noauto
, which tells the system to not
mount this at boot. Then, as the user quantum
, I would be allowed to
mount /example/pipewire-0
. Also note that the user quantum
would need
write access to /example
, which can be granted with the group approach
or with an ACL.
Then, after /run/user/1000/pipewire-0
is created, we simply have to run
mount /example/pipewire-0
.
Since /run/user/1000/pipewire-0
is created by systemd
, I could use
the following override unit to handle the mounting of /example/pipewire-0
automatically:
[Socket]
ExecStartPost=/bin/mount /example/pipewire-0
ExecStopPre=/bin/umount /example/pipewire-0
And this works perfectly, without any overhead created by socat
(frequent
pauses in audio resulting in popping noises), or creating any security holes
(other than exposing PipeWire to the virtual machine user).