From 3ebe95ba8c97745b2cef0ad0e894087a1addd3ff Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Thu, 14 Aug 2025 10:10:51 +0200 Subject: [PATCH] Allow UID/GID in ICINGA2_(USER|GROUP) environment variables --- doc/21-development.md | 6 ++-- icinga-app/icinga.cpp | 62 +++++++++++++++++++++++++------------- lib/base/utility.cpp | 70 +++++++++++++++++++++++++++++-------------- 3 files changed, 91 insertions(+), 47 deletions(-) diff --git a/doc/21-development.md b/doc/21-development.md index 2f17c8e83..c3f46fb92 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -2334,12 +2334,12 @@ Also see `CMakeLists.txt` for details. * `ICINGA2_CONFIGDIR`: Main config directory; defaults to `CMAKE_INSTALL_SYSCONFDIR/icinga2` usually `/etc/icinga2` * `ICINGA2_CACHEDIR`: Directory for cache files; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/cache/icinga2` usually `/var/cache/icinga2` * `ICINGA2_DATADIR`: Data directory for the daemon; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/lib/icinga2` usually `/var/lib/icinga2` -* `ICINGA2_LOGDIR`: Logfiles of the daemon; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/log/icinga2 usually `/var/log/icinga2` +* `ICINGA2_LOGDIR`: Logfiles of the daemon; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/log/icinga2` usually `/var/log/icinga2` * `ICINGA2_SPOOLDIR`: Spooling directory ; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/spool/icinga2` usually `/var/spool/icinga2` * `ICINGA2_INITRUNDIR`: Runtime data for the init system; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/run/icinga2` usually `/run/icinga2` * `ICINGA2_GIT_VERSION_INFO`: Whether to use Git to determine the version number; defaults to `ON` -* `ICINGA2_USER`: The user Icinga 2 should run as; defaults to `icinga` -* `ICINGA2_GROUP`: The group Icinga 2 should run as; defaults to `icinga` +* `ICINGA2_USER`: The user or user-id Icinga 2 should run as; defaults to `icinga` +* `ICINGA2_GROUP`: The group or group-id Icinga 2 should run as; defaults to `icinga` * `ICINGA2_COMMAND_GROUP`: The command group Icinga 2 should use; defaults to `icingacmd` * `ICINGA2_SYSCONFIGFILE`: Where to put the config file the initscript/systemd pulls it's dirs from; * defaults to `CMAKE_INSTALL_PREFIX/etc/sysconfig/icinga2` diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index 63b51b77d..831cba89e 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -569,42 +569,60 @@ static int Main() } else if (command && command->GetImpersonationLevel() == ImpersonateIcinga) { String group = Configuration::RunAsGroup; String user = Configuration::RunAsUser; + gid_t gid = 0; errno = 0; - struct group *gr = getgrnam(group.CStr()); - - if (!gr) { - if (errno == 0) { - Log(LogCritical, "cli") - << "Invalid group specified: " << group; - return EXIT_FAILURE; - } else { - Log(LogCritical, "cli") - << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + try { + gid = boost::lexical_cast(group); + } catch (const boost::bad_lexical_cast&) { + struct group* gr = getgrnam(group.CStr()); + if (!gr) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid group specified: " << group; + } else { + Log(LogCritical, "cli") + << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + } return EXIT_FAILURE; } + + gid = gr->gr_gid; } - if (getgid() != gr->gr_gid) { + if (getgid() != gid) { if (!vm.count("reload-internal") && setgroups(0, nullptr) < 0) { Log(LogCritical, "cli") << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; Log(LogCritical, "cli") - << "Please re-run this command as a privileged user or using the \"" << user << "\" account."; + << "Please rerun this command as a privileged user or using the \"" << user << "\" account."; return EXIT_FAILURE; } - if (setgid(gr->gr_gid) < 0) { + if (setgid(gid) < 0) { Log(LogCritical, "cli") << "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + Log(LogCritical, "cli") + << "Please rerun this command as a privileged user or using the \"" << user << "\" account."; return EXIT_FAILURE; } } - errno = 0; - struct passwd *pw = getpwnam(user.CStr()); + std::optional uid; + struct passwd *pw = nullptr; - if (!pw) { + errno = 0; + try { + uid = boost::lexical_cast(user); + pw = getpwuid(*uid); + } catch (const boost::bad_lexical_cast&) { + pw = getpwnam(user.CStr()); + if (pw) { + uid = pw->pw_uid; + } + } + + if (!uid) { if (errno == 0) { Log(LogCritical, "cli") << "Invalid user specified: " << user; @@ -617,20 +635,22 @@ static int Main() } // also activate the additional groups the configured user is member of - if (getuid() != pw->pw_uid) { - if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) { + if (getuid() != *uid) { + // initgroups() is only called when either getpwuid() or getpwnam() returned a valid user entry. + // Otherwise it makes no sense to set any additional groups. + if (!vm.count("reload-internal") && pw && initgroups(user.CStr(), pw->pw_gid) < 0) { Log(LogCritical, "cli") << "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; Log(LogCritical, "cli") - << "Please re-run this command as a privileged user or using the \"" << user << "\" account."; + << "Please rerun this command as a privileged user or using the \"" << user << "\" account."; return EXIT_FAILURE; } - if (setuid(pw->pw_uid) < 0) { + if (setuid(*uid) < 0) { Log(LogCritical, "cli") << "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; Log(LogCritical, "cli") - << "Please re-run this command as a privileged user or using the \"" << user << "\" account."; + << "Please rerun this command as a privileged user or using the \"" << user << "\" account."; return EXIT_FAILURE; } } diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 2b4e32def..1a4ac65f7 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -800,43 +800,67 @@ void Utility::RenameFile(const String& source, const String& target) #endif /* _WIN32 */ } -/* - * Set file permissions +/** + * Set the ownership of the specified file to the given user and group. + * + * In case of an error, false is returned and the error is logged. + * + * @note This operation will fail if the program is not run as root or the given user is + * not already the owner and member of the given group. + * + * @param file The path to the file as a string + * @param user Either the username or their UID as a string + * @param group Either the group's name or its GID as a string + * + * @return 'true' if the operation was successful, 'false' if an error occurred. */ bool Utility::SetFileOwnership(const String& file, const String& user, const String& group) { #ifndef _WIN32 - errno = 0; - struct passwd *pw = getpwnam(user.CStr()); + uid_t uid = 0; + try { + uid = boost::lexical_cast(user); + } catch (const boost::bad_lexical_cast&) { + errno = 0; + struct passwd* pw = getpwnam(user.CStr()); - if (!pw) { - if (errno == 0) { - Log(LogCritical, "cli") - << "Invalid user specified: " << user; - return false; - } else { - Log(LogCritical, "cli") - << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + if (!pw) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid user specified: " << user; + } else { + Log(LogCritical, "cli") << "getpwnam() failed with error code " << errno << ", \"" + << Utility::FormatErrorNumber(errno) << "\""; + } return false; } + + uid = pw->pw_uid; } - errno = 0; - struct group *gr = getgrnam(group.CStr()); - if (!gr) { - if (errno == 0) { - Log(LogCritical, "cli") - << "Invalid group specified: " << group; - return false; - } else { - Log(LogCritical, "cli") - << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + gid_t gid = 0; + try { + gid = boost::lexical_cast(group); + } catch (const boost::bad_lexical_cast&) { + errno = 0; + struct group* gr = getgrnam(group.CStr()); + + if (!gr) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid group specified: " << group; + } else { + Log(LogCritical, "cli") << "getgrnam() failed with error code " << errno << ", \"" + << Utility::FormatErrorNumber(errno) << "\""; + } return false; } + + gid = gr->gr_gid; } - if (chown(file.CStr(), pw->pw_uid, gr->gr_gid) < 0) { + if (chown(file.CStr(), uid, gid) < 0) { Log(LogCritical, "cli") << "chown() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; return false;