-
Notifications
You must be signed in to change notification settings - Fork 595
Allow UID/GID in ICINGA2_(USER|GROUP) environment variables #10538
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
f89ea15
to
674d7c5
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks a lot for this PR. I have skimmed over it and like its gist, as it is simpler than the previous two approaches.
I have not tested the code, but only read the diff together with the relevant libc and system call man pages. In general, it seems fine. Please address my comments and feel free to tell me if my objections are irrelevant.
Would you please let GitHub close these other two PRs by adding "Closes #10308" and "Closes #10321" to the top message?
a6901fe
to
e0ca82e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$ icinga2 --version | head -n1
icinga2 - The Icinga 2 network monitoring daemon (version: v2.15.0-64-ge0ca82ec3)
All mighty root
user, correct icinga
user, "unprivileged" nobody
, a non-existing UID, and a non-existing user.
$ id
uid=0(root) gid=0(root) groups=0(root)
$ id icinga
uid=5665(icinga) gid=5665(icinga) groups=5665(icinga)
$ id nobody
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
$ id 2323
id: '2323': no such user
$ id nope
id: 'nope': no such user
Config validation works as root
or icinga
- both via names or IDs.
$ icinga2 daemon -C 10:29:21 [113/633]
[2025-08-19 08:28:37 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[ . . . ]
[2025-08-19 08:28:37 +0000] information/cli: Finished validating the configuration file(s).
$ ICINGA2_USER=root ICINGA2_GROUP=root icinga2 daemon -C
[2025-08-19 08:28:41 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[ . . . ]
[2025-08-19 08:28:41 +0000] information/cli: Finished validating the configuration file(s).
$ ICINGA2_USER=0 ICINGA2_GROUP=0 icinga2 daemon -C
[2025-08-19 08:28:46 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[ . . . ]
[2025-08-19 08:28:46 +0000] information/cli: Finished validating the configuration file(s).
$ ICINGA2_USER=icinga ICINGA2_GROUP=icinga icinga2 daemon -C
[2025-08-19 08:29:02 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[ . . . ]
[2025-08-19 08:29:03 +0000] information/cli: Finished validating the configuration file(s).
$ ICINGA2_USER=5665 ICINGA2_GROUP=5665 icinga2 daemon -C
[2025-08-19 08:29:12 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[ . . . ]
[2025-08-19 08:29:12 +0000] information/cli: Finished validating the configuration file(s).
nobody
is a valid user, but lacks permissions.
$ ICINGA2_USER=nobody ICINGA2_GROUP=nogroup icinga2 daemon -C
[2025-08-19 08:32:43 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[ . . . ]
[2025-08-19 08:32:43 +0000] critical/cli: Could not write vars file: Error: Function call 'mkstemp' for file '/var/cache/icinga2/icinga2.vars.tmp.3iK5av' failed with error code 13, 'Permission denied'
$ ICINGA2_USER=65534 ICINGA2_GROUP=65534 icinga2 daemon -C
[2025-08-19 08:33:00 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[ . . . ]
[2025-08-19 08:33:00 +0000] critical/cli: Could not write vars file: Error: Function call 'mkstemp' for file '/var/cache/icinga2/icinga2.vars.tmp.Sm0n6B' failed with error code 13, 'Permission denied'
The following would run as the unknown user 2323 and group 2323. While looking up none:none
would fail - as expected.
$ ICINGA2_USER=2323 ICINGA2_GROUP=2323 icinga2 daemon -C
[2025-08-19 08:35:06 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[2025-08-19 08:35:06 +0000] critical/cli: Could not write vars file: Error: Function call 'mkstemp' for file '/var/cache/icinga2/icinga2.vars.tmp.ty0k7p' failed with error code 13, 'Permission denied'
$ ICINGA2_USER=nope ICINGA2_GROUP=nope icinga2 daemon -C
critical/cli: Invalid group specified: nope
Let's try big numbers :)
$ # 2**31
$ ICINGA2_USER=2147483648 ICINGA2_GROUP=2147483648 icinga2 daemon -C
[2025-08-19 08:42:09 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[ . . . ]
[2025-08-19 08:42:09 +0000] critical/cli: Could not write vars file: Error: Function call 'mkstemp' for file '/var/cache/icinga2/icinga2.vars.tmp.H24Sn0' failed with error code 13, 'Permission denied'
$ # 2**32 - 2
$ ICINGA2_USER=4294967294 ICINGA2_GROUP=4294967294 icinga2 daemon -C
[2025-08-19 08:43:42 +0000] information/cli: Icinga application loader (version: v2.15.0-64-ge0ca82ec3)
[ . . . ]
[2025-08-19 08:43:43 +0000] critical/cli: Could not write vars file: Error: Function call 'mkstemp' for file '/var/cache/icinga2/icinga2.vars.tmp.pnVVnu' failed with error code 13, 'Permission denied'
$ # 2**32 - 1
$ ICINGA2_USER=4294967295 ICINGA2_GROUP=4294967295 icinga2 daemon -C
critical/cli: setgid() failed with error code 22, "Invalid argument"
$ $ ICINGA2_USER=4294967295 icinga2 daemon -C
critical/cli: setuid() failed with error code 22, "Invalid argument"
critical/cli: Please re-run this command as a privileged user or using the "4294967295" account.
$ # 2**32
$ ICINGA2_USER=4294967296 ICINGA2_GROUP=4294967296 icinga2 daemon -C
critical/cli: Invalid group specified: 4294967296
Consider the special errors for setgid(3)
or setuid(3)
.
In general, looks good to me! But it would be nice to document the UID/GID support, e.g., by adding the information to the System Environment list or even adding this somewhere to the Icinga 2 CLI Commands section.
if (getuid() != pw->pw_uid) { | ||
if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) { | ||
if (getuid() != *uid) { | ||
if (!vm.count("reload-internal") && pw && initgroups(user.CStr(), pw->pw_gid) < 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess you have introduced the pw
check to allow bypassing an initgroups(3p)
call for a non-existing user entry, where only an UID was supplied.
This addresses this part of the reported issue:
The
initgroups
call is what actually fails in the instance - for any of the arbitrary UIDs, no passwd entry will ever exist [...]
Am I right? If so, please add a short comment stating so, as otherwise this is much implicit logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I right?
Also because we're dereferencing the pointer right after, but yes. We don't have a struct for the user entry that could give us additional groups, so it makes no sense to call that here. But I will add a comment clarifying this.
lib/base/utility.cpp
Outdated
<< "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; | ||
std::optional<uid_t> uid; | ||
try { | ||
uid = Convert::ToLong(user); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this also be changed to boost::lexical_cast<uid_t>
? Same for the GID part below.
e0ca82e
to
5b52699
Compare
@oxzi Thanks for testing with the different UID/GID values. I've added the comment and also switched to using However, I'm not sure where I should document this "feature" and what I should write in |
5b52699
to
e0898af
Compare
e0898af
to
6ea0275
Compare
Fixes #10307.
This is the third attempt at a PR that fixes this issue.
The approach in #10308 wasn't enough, because it still required an existing group and didn't allow to drop privileges from root to a user that's not in the database. Also it didn't account for other places that required valid users/groups, mostly the
Utility::SetFileOwnership()
function which is used in thenode setup
command which is especially relevant for containers.@oxzi's solution in #10321 added a new command line argument that allowed to explicitly bypass dropping the permissions for the given command. This had the advantage over #10308 that it reliably worked for both user and group, but it also didn't allow dropping privileges (which to be fair, was the point) and also didn't account for the Utility function.
My solution allows to specify UID and GID in the
ICINGA2_USER
andICINGA2_GROUP
environment variables that override the defaults (corresponding to the same environment variables) set at build time. All the user has to do is additionally set these environment variables to the desired UID/GID combination and everything else should just work as expected. If the process is run as root, privileges should drop to the specified ids, if the process starts with these ids, they're validated against the environment variables and nothing else is done/attempted.This issue got renewed interest with #10505, where users like me preferred to set custom UIDs/GIDs in their
docker-compose.yml
files for development to easier mount configuration directories. Even with this PR this is not easily possible without theARG ICINGA_USER_ID
, because the/data
directory gets set up with ownership for theicinga
user and its default UID/GID. But it's at least a step closer and maybe it would work with a mounted and manually set up /data directory.