mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-10 08:16:15 +02:00
* Get rid of `build-users'. We'll just take all the members of
`build-users-group'. This makes configuration easier: you can just add users in /etc/group.
This commit is contained in:
parent
751f6d2157
commit
6e5ec1029a
3 changed files with 88 additions and 66 deletions
|
@ -78,44 +78,44 @@
|
||||||
#build-max-jobs = 1
|
#build-max-jobs = 1
|
||||||
|
|
||||||
|
|
||||||
### Option `build-users'
|
### Option `build-users-group'
|
||||||
#
|
#
|
||||||
# This option contains a list of user names under which Nix can
|
# This options specifies the Unix group containing the Nix build user
|
||||||
# execute builds. In multi-user Nix installations, builds should not
|
# accounts. In multi-user Nix installations, builds should not
|
||||||
# be performed by the Nix account since that would allow users to
|
# be performed by the Nix account since that would allow users to
|
||||||
# arbitrarily modify the Nix store and database by supplying specially
|
# arbitrarily modify the Nix store and database by supplying specially
|
||||||
# crafted builders; and they cannot be performed by the calling user
|
# crafted builders; and they cannot be performed by the calling user
|
||||||
# since that would allow him/her to influence the build result.
|
# since that would allow him/her to influence the build result.
|
||||||
#
|
#
|
||||||
# Thus this list should contain a number of `special' user accounts
|
# Therefore, if this option is non-empty and specifies a valid group,
|
||||||
# created specifically for Nix, e.g., `nix-builder-1',
|
# builds will be performed under the user accounts that are a member
|
||||||
# `nix-builder-2', and so on. The more users the better, since at
|
# of the group specified here (as listed in /etc/group). Those user
|
||||||
# most a number of builds equal to the number of build users can be
|
# accounts should not be used for any other purpose!
|
||||||
# running simultaneously.
|
|
||||||
#
|
#
|
||||||
# If this list is empty, builds will be performed under the Nix
|
# Nix will never run two builds under the same user account at the
|
||||||
# account (that is, the uid under which the Nix daemon runs, or that
|
# same time. This is to prevent an obvious security hole: a malicious
|
||||||
# owns the setuid nix-worker program).
|
# user writing a Nix expression that modifies the build result of a
|
||||||
|
# legitimate Nix expression being built by another user. Therefore it
|
||||||
|
# is good to have as many Nix build user accounts as you can spare.
|
||||||
|
# (Remember: uids are cheap.)
|
||||||
|
#
|
||||||
|
# The build users should have permission to create files in the Nix
|
||||||
|
# store, but not delete them. Therefore, /nix/store should be owned
|
||||||
|
# by the Nix account, its group should be the group specified here,
|
||||||
|
# and its mode should be 1775.
|
||||||
|
#
|
||||||
|
# If the build users group is empty, builds will be performed under
|
||||||
|
# the uid of the Nix process (that is, the uid of the caller if
|
||||||
|
# $NIX_REMOTE is empty, the uid under which the Nix daemon runs if
|
||||||
|
# $NIX_REMOTE is `daemon', or the uid that owns the setuid nix-worker
|
||||||
|
# program if $NIX_REMOTE is `slave'). Obviously, this should not be
|
||||||
|
# used in multi-user settings with untrusted users.
|
||||||
|
#
|
||||||
|
# The default is empty.
|
||||||
#
|
#
|
||||||
# Example:
|
# Example:
|
||||||
# build-users = nix-builder-1 nix-builder-2 nix-builder-3
|
# build-users-group = nix-builders
|
||||||
#build-users =
|
build-users-group = nix-builders
|
||||||
|
|
||||||
|
|
||||||
### Option `build-users-group'
|
|
||||||
#
|
|
||||||
# If `build-users' is used, then this option specifies the group ID
|
|
||||||
# (gid) under which each build is to be performed. This group should
|
|
||||||
# have permission to create files in the Nix store, but not delete
|
|
||||||
# them. I.e., /nix/store should be owned by the Nix account, its
|
|
||||||
# group should be the group specified here, and its mode should be
|
|
||||||
# 1775.
|
|
||||||
#
|
|
||||||
# The default is `nix'.
|
|
||||||
#
|
|
||||||
# Example:
|
|
||||||
# build-users-group = nix
|
|
||||||
#build-users-group =
|
|
||||||
|
|
||||||
|
|
||||||
### Option `system'
|
### Option `system'
|
||||||
|
|
|
@ -341,6 +341,7 @@ private:
|
||||||
AutoCloseFD fdUserLock;
|
AutoCloseFD fdUserLock;
|
||||||
|
|
||||||
uid_t uid;
|
uid_t uid;
|
||||||
|
gid_t gid;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UserLock();
|
UserLock();
|
||||||
|
@ -350,6 +351,7 @@ public:
|
||||||
void release();
|
void release();
|
||||||
|
|
||||||
uid_t getUID();
|
uid_t getUID();
|
||||||
|
uid_t getGID();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -358,7 +360,7 @@ PathSet UserLock::lockedPaths;
|
||||||
|
|
||||||
UserLock::UserLock()
|
UserLock::UserLock()
|
||||||
{
|
{
|
||||||
uid = 0;
|
uid = gid = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -372,20 +374,36 @@ void UserLock::acquire()
|
||||||
{
|
{
|
||||||
assert(uid == 0);
|
assert(uid == 0);
|
||||||
|
|
||||||
Strings buildUsers = querySetting("build-users", Strings());
|
string buildUsersGroup = querySetting("build-users-group", "");
|
||||||
|
assert(buildUsersGroup != "");
|
||||||
|
|
||||||
if (buildUsers.empty())
|
/* Get the members of the build-users-group. */
|
||||||
throw Error(
|
struct group * gr = getgrnam(buildUsersGroup.c_str());
|
||||||
"cannot build as `root'; you must define "
|
if (!gr)
|
||||||
"one or more users for building in `build-users' "
|
throw Error(format("the group `%1%' specified in `build-users-group' does not exist")
|
||||||
"in the Nix configuration file");
|
% buildUsersGroup);
|
||||||
|
gid = gr->gr_gid;
|
||||||
|
|
||||||
for (Strings::iterator i = buildUsers.begin(); i != buildUsers.end(); ++i) {
|
/* Copy the result of getgrnam. */
|
||||||
|
Strings users;
|
||||||
|
for (char * * p = gr->gr_mem; *p; ++p) {
|
||||||
|
debug(format("found build user `%1%'") % *p);
|
||||||
|
users.push_back(*p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (users.empty())
|
||||||
|
throw Error(format("the build users group `%1%' has no members")
|
||||||
|
% buildUsersGroup);
|
||||||
|
|
||||||
|
/* Find a user account that isn't currently in use for another
|
||||||
|
build. */
|
||||||
|
for (Strings::iterator i = users.begin(); i != users.end(); ++i) {
|
||||||
debug(format("trying user `%1%'") % *i);
|
debug(format("trying user `%1%'") % *i);
|
||||||
|
|
||||||
struct passwd * pw = getpwnam(i->c_str());
|
struct passwd * pw = getpwnam(i->c_str());
|
||||||
if (!pw)
|
if (!pw)
|
||||||
throw Error(format("the user `%1%' listed in `build-users' does not exist") % *i);
|
throw Error(format("the user `%1%' in the group `%2%' does not exist")
|
||||||
|
% *i % buildUsersGroup);
|
||||||
|
|
||||||
fnUserLock = (format("%1%/userpool/%2%") % nixStateDir % pw->pw_uid).str();
|
fnUserLock = (format("%1%/userpool/%2%") % nixStateDir % pw->pw_uid).str();
|
||||||
|
|
||||||
|
@ -405,9 +423,9 @@ void UserLock::acquire()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Error("all build users are currently in use; "
|
throw Error(format("all build users are currently in use; "
|
||||||
"consider expanding the `build-users' field "
|
"consider creating additional users and adding them to the `%1%' group")
|
||||||
"in the Nix configuration file");
|
% buildUsersGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -428,6 +446,12 @@ uid_t UserLock::getUID()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
uid_t UserLock::getGID()
|
||||||
|
{
|
||||||
|
return gid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void killUser(uid_t uid)
|
static void killUser(uid_t uid)
|
||||||
{
|
{
|
||||||
debug(format("killing all processes running under uid `%1%'") % uid);
|
debug(format("killing all processes running under uid `%1%'") % uid);
|
||||||
|
@ -1275,12 +1299,12 @@ void DerivationGoal::startBuilder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* If `build-users' is not empty, then we have to build as one of
|
/* If `build-users-group' is not empty, then we have to build as
|
||||||
the users listed in `build-users'. */
|
one of the members of that group. */
|
||||||
gid_t gidBuildGroup = -1;
|
if (querySetting("build-users-group", "") != "") {
|
||||||
if (querySetting("build-users", Strings()).size() > 0) {
|
|
||||||
buildUser.acquire();
|
buildUser.acquire();
|
||||||
assert(buildUser.getUID() != 0);
|
assert(buildUser.getUID() != 0);
|
||||||
|
assert(buildUser.getGID() != 0);
|
||||||
|
|
||||||
/* Make sure that no other processes are executing under this
|
/* Make sure that no other processes are executing under this
|
||||||
uid. */
|
uid. */
|
||||||
|
@ -1290,16 +1314,8 @@ void DerivationGoal::startBuilder()
|
||||||
if (chown(tmpDir.c_str(), buildUser.getUID(), (gid_t) -1) == -1)
|
if (chown(tmpDir.c_str(), buildUser.getUID(), (gid_t) -1) == -1)
|
||||||
throw SysError(format("cannot change ownership of `%1%'") % tmpDir);
|
throw SysError(format("cannot change ownership of `%1%'") % tmpDir);
|
||||||
|
|
||||||
/* What group to execute the builder in? */
|
|
||||||
string buildGroup = querySetting("build-users-group", "nix");
|
|
||||||
struct group * gr = getgrnam(buildGroup.c_str());
|
|
||||||
if (!gr) throw Error(
|
|
||||||
format("the group `%1%' specified in `build-users-group' does not exist")
|
|
||||||
% buildGroup);
|
|
||||||
gidBuildGroup = gr->gr_gid;
|
|
||||||
|
|
||||||
/* Check that the Nix store has the appropriate permissions,
|
/* Check that the Nix store has the appropriate permissions,
|
||||||
i.e., owned by root and mode 1777 (sticky bit on so that
|
i.e., owned by root and mode 1775 (sticky bit on so that
|
||||||
the builder can create its output but not mess with the
|
the builder can create its output but not mess with the
|
||||||
outputs of other processes). */
|
outputs of other processes). */
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
@ -1307,11 +1323,11 @@ void DerivationGoal::startBuilder()
|
||||||
throw SysError(format("cannot stat `%1%'") % nixStore);
|
throw SysError(format("cannot stat `%1%'") % nixStore);
|
||||||
if (!(st.st_mode & S_ISVTX) ||
|
if (!(st.st_mode & S_ISVTX) ||
|
||||||
((st.st_mode & S_IRWXG) != S_IRWXG) ||
|
((st.st_mode & S_IRWXG) != S_IRWXG) ||
|
||||||
(st.st_gid != gidBuildGroup))
|
(st.st_gid != buildUser.getGID()))
|
||||||
throw Error(format(
|
throw Error(format(
|
||||||
"builder does not have write permission to `%1%'; "
|
"builder does not have write permission to `%1%'; "
|
||||||
"try `chgrp %1% %2%; chmod 1775 %2%'")
|
"try `chgrp %1% %2%; chmod 1775 %2%'")
|
||||||
% buildGroup % nixStore);
|
% buildUser.getGID() % nixStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1365,13 +1381,15 @@ void DerivationGoal::startBuilder()
|
||||||
if (setgroups(0, 0) == -1)
|
if (setgroups(0, 0) == -1)
|
||||||
throw SysError("cannot clear the set of supplementary groups");
|
throw SysError("cannot clear the set of supplementary groups");
|
||||||
|
|
||||||
setgid(gidBuildGroup);
|
if (setgid(buildUser.getGID()) == -1 ||
|
||||||
assert(getgid() == gidBuildGroup);
|
getgid() != buildUser.getGID() ||
|
||||||
assert(getegid() == gidBuildGroup);
|
getegid() != buildUser.getGID())
|
||||||
|
throw SysError("setgid failed");
|
||||||
|
|
||||||
setuid(buildUser.getUID());
|
if (setuid(buildUser.getUID()) == -1 ||
|
||||||
assert(getuid() == buildUser.getUID());
|
getuid() != buildUser.getUID() ||
|
||||||
assert(geteuid() == buildUser.getUID());
|
geteuid() != buildUser.getUID())
|
||||||
|
throw SysError("setuid failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Execute the program. This should not return. */
|
/* Execute the program. This should not return. */
|
||||||
|
|
|
@ -40,14 +40,18 @@ static void runBuilder(string userName,
|
||||||
don't want to create that directory here. */
|
don't want to create that directory here. */
|
||||||
secureChown(pw->pw_uid, gidBuilders, ".");
|
secureChown(pw->pw_uid, gidBuilders, ".");
|
||||||
|
|
||||||
|
|
||||||
/* Set the real, effective and saved gid. Must be done before
|
/* Set the real, effective and saved gid. Must be done before
|
||||||
setuid(), otherwise it won't set the real and saved gids. */
|
setuid(), otherwise it won't set the real and saved gids. */
|
||||||
|
if (setgroups(0, 0) == -1)
|
||||||
|
throw SysError("cannot clear the set of supplementary groups");
|
||||||
//setgid(gidBuilders);
|
//setgid(gidBuilders);
|
||||||
|
|
||||||
/* Set the real, effective and saved uid. */
|
/* Set the real, effective and saved uid. */
|
||||||
setuid(pw->pw_uid);
|
if (setuid(pw->pw_uid) == -1 ||
|
||||||
if (getuid() != pw->pw_uid || geteuid() != pw->pw_uid)
|
getuid() != pw->pw_uid ||
|
||||||
throw Error("cannot setuid");
|
geteuid() != pw->pw_uid)
|
||||||
|
throw SysError("setuid failed");
|
||||||
|
|
||||||
/* Execute the program. */
|
/* Execute the program. */
|
||||||
std::vector<const char *> args;
|
std::vector<const char *> args;
|
||||||
|
|
Loading…
Reference in a new issue