While there is a lot of configuration and docs around groups, they are almost not used in NUT
During an investigation for #1209 effort, I found that only drivers seem to have a configuration file option for a group to use. Mentions of setgid() are limited to common.c::become_user() (change to default group of the user account we were asked to become), and of *.c{,pp} files only drivers/main.c has {f,}chown() to tweak socket file permissions if asked to (per that RUN_AS_GROUP setting).
Is there any reason to not have this better configurable for other daemons (e.g. to use a specific secondary group of one)?..
CC @aquette @clepple @gdt @desertwitch
Without thinking much, it seems like it would be cleaner if when configuring nut, you pass --with-user and --with-group and there is an expectation that programs end up as that user, even if they start as root, open what they need to, and drop privs. I feel that surely other suites of daemons are like this already.
In pkgsrc, we declare a username/groupname, passing it to configure as --with-user=nut --with-group=nut, and it's created at install time. Then we expect programs to look up the names to get the uid/gid. So a bit of regularization is probably good.
Yeah, for --with-user that is what happens(*) - for the UID. The question here is about GID that we can also configure for a build but barely use.
(*) These --with-user and --with-group options for configure script set built-in defaults of the NUT programs. They (mostly user) can be overridden at run-time via config files or CLI options.
Yeah, for
--with-userthat is what happens(*) - for the UID. The question here is about GID that we can alsoconfigurefor a build but barely use.
Treat this as a bug, and set gid everywhere you set uid, unless someone can explain why that's confused. The point is to allow groups of related programs to share writeability, if people want.
Again, as noted above: we setuid() in become_user(), and setgid() there as well... although I first said this was "to assume the default group of that user" - technically it tries to assume the group passed in the struct passwd *pw argument. But these come from new_uid = get_user_pwent(user); kind of calls, getting the default group there. None of these tries to apply a different "RUN_AS_GROUP" here for example, but the discrepancy does exist).
- https://github.com/networkupstools/nut/blob/v2.8.4/common/common.c#L795-L845
- https://github.com/networkupstools/nut/blob/master/server/upsd.c#L2279
- https://github.com/networkupstools/nut/blob/master/drivers/main.c#L2442
- https://github.com/networkupstools/nut/blob/master/clients/upsmon.c#L4090
- https://github.com/networkupstools/nut/blob/master/clients/upslog.c#L937
The point of this issue is that I am not sure they actually should apply an alternative run-time group there, e.g. for complicated security setups where each daemon runs as a different user, and they share certain not-necessarily-default groups for certain interactions. The one spot where this is in fact handled is for drivers and upsd to be set up like this, with socket files to be owned by the DRIVER_UID:UPSD_GID if desired. This does not impact the GID the driver daemon runs as.
So the actual chain of events now, I think, is that we request some UID and GID in the configure script. These values are propagated into packaging (groupadd/useradd) and some init scripts (mkdir/chown/...). The run-time daemons dropping privileges end up using the default group their UID has (which may be the configured GID if the useradd did specify it and nobody changed later), rather than the GID value configured into the build. I guess this is the thorn that itches me here :)
Side note: the GID value configured into the build in fact might be impossible to set, if e.g. run-time daemon was started as another UID and did not (currently could not) tweak the requested GID correspondingly. For a practical-ish example, we currently only offer one UID and one GID setting during build, but suggest using multiple UIDs (per daemon) for very secure setups. A daemon user that is not a member of that one GID might never be able to setgid() it in today's code (that lacks a group=... setting or CLI option, similar to user=... that we do have).
My reaction is that there is too much complexity and that the less complicated and the less mysterious, the better. It is true that packaging might define USER and GROUP, and when creating the user, use the group as the primary gid. pkgsrc indeed does that. But once we document --with-group in configure, I think we should use it, perhaps with a caution that if you create a nut user with a primary gid that doesn't match --with-group, you are outside the norm and should have a plan about what's happening and why.
The entire user/group scheme assumes (for other than upsmon) that daemons are started as root, or started as the correct (as configured) user/group. That's fine, and I don't we need to accomodate people that do irregular things, like starting up with the intended user but some other gid, except with the existing check/error code. "Don't do that" is in my view a better approach than trying to accomodate strange configs that I personally see as erroneous.
Right now, the code isn't really following the docs. I think we need to pick one:
-
Change become_user to take instead of pw, a user and group string, and become that uid/gid. Or a group entry also, not important how exactly. Use that for all daemons and the thus for sockets/files created by them (well, subject to BSD vs Linux group inheritance, but that's really an orthogonal issue and not different in option 1 vs option 2.)
-
Document that --with-group is only used for some (specified) limited things, and that daemons will run as the primary gid of the password entry for the configured user.
I lean to the first as being (once we are there) as a simpler world that can be described in fewer words, sort of reduced Kolmogorov complexity.
Perhaps, though, it is best to have the user manual have multiple examples of reasonable setups, listing uid/gid/permissions for everything and giving rationale for why the choices were made. On my system, I have just one socket in /var/db/nut/drivername-upsname, and it's nut:nut 660. I'm using nut:nut for --with-user/--with-group and am not trying to separate nut servers and the nut driver. For me, the nut user's primary gid is nut, so option 1 and option 2 have the same actual result.