Skip to content

Commit 0c2fd00

Browse files
authored
Merge pull request #10538 from Icinga/allow-uid-gid-icinga-user-and-group
Allow UID/GID in ICINGA2_(USER|GROUP) environment variables
2 parents 9905e9a + 3ebe95b commit 0c2fd00

File tree

3 files changed

+91
-47
lines changed

3 files changed

+91
-47
lines changed

doc/21-development.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2334,12 +2334,12 @@ Also see `CMakeLists.txt` for details.
23342334
* `ICINGA2_CONFIGDIR`: Main config directory; defaults to `CMAKE_INSTALL_SYSCONFDIR/icinga2` usually `/etc/icinga2`
23352335
* `ICINGA2_CACHEDIR`: Directory for cache files; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/cache/icinga2` usually `/var/cache/icinga2`
23362336
* `ICINGA2_DATADIR`: Data directory for the daemon; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/lib/icinga2` usually `/var/lib/icinga2`
2337-
* `ICINGA2_LOGDIR`: Logfiles of the daemon; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/log/icinga2 usually `/var/log/icinga2`
2337+
* `ICINGA2_LOGDIR`: Logfiles of the daemon; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/log/icinga2` usually `/var/log/icinga2`
23382338
* `ICINGA2_SPOOLDIR`: Spooling directory ; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/spool/icinga2` usually `/var/spool/icinga2`
23392339
* `ICINGA2_INITRUNDIR`: Runtime data for the init system; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/run/icinga2` usually `/run/icinga2`
23402340
* `ICINGA2_GIT_VERSION_INFO`: Whether to use Git to determine the version number; defaults to `ON`
2341-
* `ICINGA2_USER`: The user Icinga 2 should run as; defaults to `icinga`
2342-
* `ICINGA2_GROUP`: The group Icinga 2 should run as; defaults to `icinga`
2341+
* `ICINGA2_USER`: The user or user-id Icinga 2 should run as; defaults to `icinga`
2342+
* `ICINGA2_GROUP`: The group or group-id Icinga 2 should run as; defaults to `icinga`
23432343
* `ICINGA2_COMMAND_GROUP`: The command group Icinga 2 should use; defaults to `icingacmd`
23442344
* `ICINGA2_SYSCONFIGFILE`: Where to put the config file the initscript/systemd pulls it's dirs from;
23452345
* defaults to `CMAKE_INSTALL_PREFIX/etc/sysconfig/icinga2`

icinga-app/icinga.cpp

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -569,42 +569,60 @@ static int Main()
569569
} else if (command && command->GetImpersonationLevel() == ImpersonateIcinga) {
570570
String group = Configuration::RunAsGroup;
571571
String user = Configuration::RunAsUser;
572+
gid_t gid = 0;
572573

573574
errno = 0;
574-
struct group *gr = getgrnam(group.CStr());
575-
576-
if (!gr) {
577-
if (errno == 0) {
578-
Log(LogCritical, "cli")
579-
<< "Invalid group specified: " << group;
580-
return EXIT_FAILURE;
581-
} else {
582-
Log(LogCritical, "cli")
583-
<< "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
575+
try {
576+
gid = boost::lexical_cast<gid_t>(group);
577+
} catch (const boost::bad_lexical_cast&) {
578+
struct group* gr = getgrnam(group.CStr());
579+
if (!gr) {
580+
if (errno == 0) {
581+
Log(LogCritical, "cli")
582+
<< "Invalid group specified: " << group;
583+
} else {
584+
Log(LogCritical, "cli")
585+
<< "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
586+
}
584587
return EXIT_FAILURE;
585588
}
589+
590+
gid = gr->gr_gid;
586591
}
587592

588-
if (getgid() != gr->gr_gid) {
593+
if (getgid() != gid) {
589594
if (!vm.count("reload-internal") && setgroups(0, nullptr) < 0) {
590595
Log(LogCritical, "cli")
591596
<< "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
592597
Log(LogCritical, "cli")
593-
<< "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
598+
<< "Please rerun this command as a privileged user or using the \"" << user << "\" account.";
594599
return EXIT_FAILURE;
595600
}
596601

597-
if (setgid(gr->gr_gid) < 0) {
602+
if (setgid(gid) < 0) {
598603
Log(LogCritical, "cli")
599604
<< "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
605+
Log(LogCritical, "cli")
606+
<< "Please rerun this command as a privileged user or using the \"" << user << "\" account.";
600607
return EXIT_FAILURE;
601608
}
602609
}
603610

611+
std::optional<uid_t> uid;
612+
struct passwd *pw = nullptr;
613+
604614
errno = 0;
605-
struct passwd *pw = getpwnam(user.CStr());
615+
try {
616+
uid = boost::lexical_cast<uid_t>(user);
617+
pw = getpwuid(*uid);
618+
} catch (const boost::bad_lexical_cast&) {
619+
pw = getpwnam(user.CStr());
620+
if (pw) {
621+
uid = pw->pw_uid;
622+
}
623+
}
606624

607-
if (!pw) {
625+
if (!uid) {
608626
if (errno == 0) {
609627
Log(LogCritical, "cli")
610628
<< "Invalid user specified: " << user;
@@ -617,20 +635,22 @@ static int Main()
617635
}
618636

619637
// also activate the additional groups the configured user is member of
620-
if (getuid() != pw->pw_uid) {
621-
if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) {
638+
if (getuid() != *uid) {
639+
// initgroups() is only called when either getpwuid() or getpwnam() returned a valid user entry.
640+
// Otherwise it makes no sense to set any additional groups.
641+
if (!vm.count("reload-internal") && pw && initgroups(user.CStr(), pw->pw_gid) < 0) {
622642
Log(LogCritical, "cli")
623643
<< "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
624644
Log(LogCritical, "cli")
625-
<< "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
645+
<< "Please rerun this command as a privileged user or using the \"" << user << "\" account.";
626646
return EXIT_FAILURE;
627647
}
628648

629-
if (setuid(pw->pw_uid) < 0) {
649+
if (setuid(*uid) < 0) {
630650
Log(LogCritical, "cli")
631651
<< "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
632652
Log(LogCritical, "cli")
633-
<< "Please re-run this command as a privileged user or using the \"" << user << "\" account.";
653+
<< "Please rerun this command as a privileged user or using the \"" << user << "\" account.";
634654
return EXIT_FAILURE;
635655
}
636656
}

lib/base/utility.cpp

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -800,43 +800,67 @@ void Utility::RenameFile(const String& source, const String& target)
800800
#endif /* _WIN32 */
801801
}
802802

803-
/*
804-
* Set file permissions
803+
/**
804+
* Set the ownership of the specified file to the given user and group.
805+
*
806+
* In case of an error, false is returned and the error is logged.
807+
*
808+
* @note This operation will fail if the program is not run as root or the given user is
809+
* not already the owner and member of the given group.
810+
*
811+
* @param file The path to the file as a string
812+
* @param user Either the username or their UID as a string
813+
* @param group Either the group's name or its GID as a string
814+
*
815+
* @return 'true' if the operation was successful, 'false' if an error occurred.
805816
*/
806817
bool Utility::SetFileOwnership(const String& file, const String& user, const String& group)
807818
{
808819
#ifndef _WIN32
809-
errno = 0;
810-
struct passwd *pw = getpwnam(user.CStr());
811-
812-
if (!pw) {
813-
if (errno == 0) {
814-
Log(LogCritical, "cli")
815-
<< "Invalid user specified: " << user;
816-
return false;
817-
} else {
818-
Log(LogCritical, "cli")
819-
<< "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
820+
uid_t uid = 0;
821+
try {
822+
uid = boost::lexical_cast<uid_t>(user);
823+
} catch (const boost::bad_lexical_cast&) {
824+
errno = 0;
825+
struct passwd* pw = getpwnam(user.CStr());
826+
827+
if (!pw) {
828+
if (errno == 0) {
829+
Log(LogCritical, "cli")
830+
<< "Invalid user specified: " << user;
831+
} else {
832+
Log(LogCritical, "cli") << "getpwnam() failed with error code " << errno << ", \""
833+
<< Utility::FormatErrorNumber(errno) << "\"";
834+
}
820835
return false;
821836
}
837+
838+
uid = pw->pw_uid;
822839
}
823840

824-
errno = 0;
825-
struct group *gr = getgrnam(group.CStr());
826841

827-
if (!gr) {
828-
if (errno == 0) {
829-
Log(LogCritical, "cli")
830-
<< "Invalid group specified: " << group;
831-
return false;
832-
} else {
833-
Log(LogCritical, "cli")
834-
<< "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
842+
gid_t gid = 0;
843+
try {
844+
gid = boost::lexical_cast<gid_t>(group);
845+
} catch (const boost::bad_lexical_cast&) {
846+
errno = 0;
847+
struct group* gr = getgrnam(group.CStr());
848+
849+
if (!gr) {
850+
if (errno == 0) {
851+
Log(LogCritical, "cli")
852+
<< "Invalid group specified: " << group;
853+
} else {
854+
Log(LogCritical, "cli") << "getgrnam() failed with error code " << errno << ", \""
855+
<< Utility::FormatErrorNumber(errno) << "\"";
856+
}
835857
return false;
836858
}
859+
860+
gid = gr->gr_gid;
837861
}
838862

839-
if (chown(file.CStr(), pw->pw_uid, gr->gr_gid) < 0) {
863+
if (chown(file.CStr(), uid, gid) < 0) {
840864
Log(LogCritical, "cli")
841865
<< "chown() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\"";
842866
return false;

0 commit comments

Comments
 (0)