systemd / systemd

The systemd System and Service Manager
https://systemd.io
GNU General Public License v2.0
13.2k stars 3.78k forks source link

Shared dynamic groups for services #7253

Open csepanda opened 6 years ago

csepanda commented 6 years ago

Submission type

DynamicUser option from systemd 235 allows to create in runtime new unique user and group for spawned service's process, however it's impossible now to organize interprocess communication via shared memory, messages queues and etc without setting them read/write grants to 'other' part of permissions mode or pass the ownership to other user, which is actually created to one another service. Such approach is not flexible and is a possible violation of access policy.

For example consider there are three services: One is master, two others are slaves and depends on master. All services have different configuration, must be isolated from other system and don't leave artifacts(ipc objects, tmp files and etc) after the end of their lifecycle, Master have to send, for example, commands to slaves via message queue. Only slaves must know what master is sending them, other processes in the system must have no access to this queue. Now when services with DynamicUser option cannot share single group and this task can not be elegantly solved without passing the ownership or calling setgroups(2), which requires corresponding capability, or other duct tape ways that must not be used in a set of different tasks.

boucman commented 6 years ago

can't you use SupplementaryGroups= for that, or create a fixed GID group and set it in Group= ?

I'm trying to understand your problem exactly...

csepanda commented 6 years ago

systemd v235 offers approach to full services lifecycle without any artifact:

Furthermore if there is fixed GID, it means that anyone can be added to this group in any period of time, rather than dynamic users and groups that exist only during service's runtime.

yuwata commented 6 years ago

@csepanda Still I'm trying to understand your request... Do you require a dynamically created group which can be set to SupplementaryGroup=?

csepanda commented 6 years ago

@yuwata actually not supplementary group, but effective group id of process. Cause this feature is necessary to interprocess communication. Shared resource creator must have specified effective gid, others services must have this group as primary/supplementary group or effective gid, doesn't matter.

poettering commented 6 years ago

I see the usecase, but I am not sure what the best semantics for this would be.

@csepanda note that you can actually run multiple independent services as the same dynamic user. Wouldn't that already get you quite far? i.e. let's say you have five services that shall be able to mess around with each other files. Simply set DynamicUser=1 for all of them, but combine this with User= set to some common, clear user name. If you do, then the user will be allocated when the first of these services starts, and be released when the last one of them exits.

csepanda commented 6 years ago

@poettering yep, this problem can be bypassed in this way. However there is still question about isolation, permission policy and security. If one service was compromised via any exploit, resources and data of other services, which were sharing same dynamic user, might be damaged or stolen by intruder.

poettering commented 6 years ago

Hmm, here's another idea: we could just separate out dynamic users and groups. I.e. we could allow that this is possible:

[Service]
DynamicUser=1
User=myapp1
Group=myproject
UMask=007

in one unit, and then:

[Service]
DynamicUser=1
User=myapp2
Group=myproject
UMask=007

in another unit. That would then have the effect that we'd allocate the users "myapp1" and "myapp2" is the respective unit starts, but the common group "myproject" would be allocated as the moment either starts and be kept around as long as either is running.

That should fit your usecase perfectly, right?

boucman commented 6 years ago

that would be pretty usefull indeed... would that be "safe" from a file-ownership point of view ? Could the tree-wide renaming trick used for dynamic user still work ? We would have to update all trees of all services that use the group when the group is created...

Also, could a service us DynamicUser=1 with SupplementaryGroup=myproject ?

csepanda commented 6 years ago

@poettering yes, this approach would readable for configuration and would solve task.

poettering commented 6 years ago

So, as it turns out we already support what's proposed here, to some point at least. Here's an example using systemd-run. On the first terminal, try this:

# systemd-run -p DynamicUser=1 -p User=quux1 -p Group=quux -t /bin/bash
Running as unit: run-u1694.service
Press ^] three times within 1s to disconnect TTY.
bash-4.4$ id
uid=63920(quux1) gid=62115(quux) groups=62115(quux) context=system_u:system_r:initrc_t:s0
bash-4.4$ 

And then, on a second terminal, do this:

# systemd-run -p DynamicUser=1 -p User=quux2 -p Group=quux -t /bin/bash
Running as unit: run-u1705.service
Press ^] three times within 1s to disconnect TTY.
bash-4.4$ id
uid=65438(quux2) gid=62115(quux) groups=62115(quux) context=system_u:system_r:initrc_t:s0
bash-4.4$ 

So all looks great. Only pitfall: there's no clean way to make these two instances share a common directory. I mean you could add "-p StateDirectory=quux" to it, and it would pretty much work, but each time you start another one of this, the whole dir would be re-chowned to the UID of the new instance...

And I am not even sure how that could even work properly... I mean, we currently use the top-level ownership of a directory as hint whether we need to re-chown things all the way down. But if multiple UIDs write to the same dir that falls flat entirely...

I am a bit puzzled how we could make this work in any reasonable systematic way...

csepanda commented 6 years ago

Oh, nice, that works.

What about preventing recursive re-chowning, if gid of state directory is same starting service and there is also service with such gid? But there is question, how many bugs can this approach provide.

Wuestengecko commented 6 years ago

Came across this while researching a similar issue... To extend on @poettering's systemd-run example. If you add to the second command line the switch --property=JoinsNamespaceOf=run-u1694.service (use the actual unit name here of course), then both units at least share common /tmp and /var/tmp directories. That maybe doesn't solve all IPC problems, but it would allow for example sockets/pipes that are shared between the units, but isolated from the rest of the system.

JoinsNamespaceOf= is a [Unit] property which references another unit that should be started already. So if you have, say, master.service and slave.service, then add to slave.service:

[Unit]
JoinsNamespaceOf=master.service
After=master.service

This might leave security issues open however, as the slave units are able to see the master's temporary files and vice versa - you need to make sure that access to actual (service-private) tmpfiles is restricted via file permissions.

bennofs commented 2 years ago

Another use case for shared dynamic groups would be connecting a backend processes to a nginx reverse proxy. For that use case, it would be great if there was a way for one service (nginx) to get dynamic groups of another service (backend) as supplementary groups. For example, say you have backends foo and bar, both with their own dynamic groups, then you would like nginx to have the groups of foo and bar as supplementary groups so nginx can access the socket created by those services, but each service cannot access the socket of the other one.