[systemd-devel] Design patterns for privilege separating systemd services?

Lennart Poettering lennart at poettering.net
Thu Feb 18 21:33:53 UTC 2021


On Mi, 17.02.21 12:50, Colin Walters (walters at verbum.org) wrote:

> I'm having a debate with the SSSD team over here around multiple
> systemd units and privilege separation:
> https://github.com/SSSD/sssd/issues/3412
>
> And we also had a related topic come up in Fedora CoreOS where we
> have a privileged service (rpm-ostreed.service) and we want a
> separate unprivileged management service (zincati.service) to be
> able to perform privileged operations; right now we install a polkit
> rule to authorize the `zincati` user, but I think that's quite
> hacky.  I'd actually like to use DynamicUser=yes for zincati.
>
> I think what I want is a way to tell systemd to create something
> like a private socketpair() between the two services dynamically.

Interesting idea. Some thoughts:

1. So we have another RFE which I am very sympathetic to which is to
   add an Open= setting to service unit files, which could be used to
   open any kind of file at activation time and pass it via our usual
   socket activation fd passing protocol over. (Usecase for that was
   giving access to privileged files to unprivileged processes)

2. Now, we recently reworked a good chunk of our code that reads user
   provided files off disk, so that when an AF_UNIX/SOCK_STREAM socket
   in the file system is specified we detect that and automatically
   connect() to it, thus having a very simple way to attach some other
   service to our file reading logic, simply by placing an
   AF_UNIX/SOCK_STREAM in place of the file. (example where this is
   used: the password file column in /etc/crypttab, i.e. you can now
   provide paspshrases easily via an IPC service that way). Now, the
   idea for the Open= thing would be to include exactly this behaviour
   here: if you specify Open=/run/some/unix/socket in the service unit
   file, we'd connect to that path, and pass it over as any other fd.

3. Now, to circle back to your idea: how about beefing up .socket
   units so that you can do "ListenStream=anonymous" or so, in which
   case we'd listen on an "anonymous" AF_UNIX/SOCK_STREAM socket
   (which would probably be one which we bind in some dir and
   immediately delete the inode of, but keep an O_PATH fd to, so that
   it may still be referenced by /proc/self/fd/ but not
   otherwise). This sounds pretty useless at first, but then:

4. Beef up the Open= idea for .service units as described in step 2
   above, but now in addition to absolute paths in the file system
   accept a syntax where we reference arbitrary socket unit
   files. Which would work both for regular sockets/fifos in the file
   system, but also work for the new anonymous kind, as proposed in
   step 3 above.

To put these four ideas together to implement what you want:

  myserver.socket:

    [Socket]
    ListenStream=anyonymous

  myserver.service:

    [Service]
    ExecStart=/usr/lib/myserverd

  myclient.service:

    [Service]
    Open=@myserver.socket
    ExecStart=/usr/bin/myclient

The above sounds simple enough, no?

Of course, there's one thing missing still: as I understand you'd
prefer a socketpair() (i.e. connected socket), instead of a listening
one to be passed to both sides. But that would be an easy extension,
i.e. add ListenStream=socketpair or so as another value, which would
then do the right thing.

I kinda like this model, I must say.

Lennart

--
Lennart Poettering, Berlin


More information about the systemd-devel mailing list