nix/daemon: consider supplementary groups during authentication
Two things happens here: - we consider supplementary groups that are known for authentication, fixes #968. - we check supplementary groups if they are our build users group and throw an error if they are (new behavior). Nonetheless, we did not remove the ability for `matchUser` to recurse and check for groups that the user may have but the connection might not have communicated as part of their groups, i.e. if a process reduces its list of groups via a call to setgroups, it will still be authorized. This will come in another commit. The authorization NixOS test has been extended with a store ping test running in systemd with DynamicUser=true *AND* a supplementary group in allowed-users. Co-authored-by: Tom Hubrecht <github@mail.hubrecht.ovh> Change-Id: I25b2b8304d66a04651cea523b5585a5d15ceebe8 Signed-off-by: Raito Bezarius <raito@lix.systems>
This commit is contained in:
co-authored by
Tom Hubrecht
parent
9e55dd6b8f
commit
c493fb668e
@@ -0,0 +1,11 @@
|
||||
---
|
||||
synopsis: "Supplementary groups are now supported for daemon authentication"
|
||||
cls: [5021]
|
||||
issues: [fj#968]
|
||||
category: "Improvements"
|
||||
credits: [raito, thubrecht, alois31, horrors]
|
||||
---
|
||||
|
||||
Linux now support receiving supplementary groups during UNIX domain authentication to a Lix daemon.
|
||||
|
||||
This change is particularly beneficial for systemd units with `DynamicUser=true` that need to connect to a Lix daemon, using a `SupplementaryGroups=` allocated by systemd in the context of the process. This is desirable if you wish to harden Lix clients.
|
||||
@@ -7,9 +7,13 @@ default: ['*']
|
||||
A list user names, separated by whitespace.
|
||||
These users are allowed to connect to the Nix daemon.
|
||||
|
||||
You can specify groups by prefixing names with `@`.
|
||||
For instance, `@wheel` means all users in the `wheel` group.
|
||||
Also, you can allow all users by specifying `*`.
|
||||
You can specify groups by prefixing group names with `@`, like `@wheel` for all
|
||||
users in the `wheel` group. To allow all users, use `*`.
|
||||
|
||||
Both primary and supplementary groups (when the platform supports it) are
|
||||
considered when determining membership. Additionally, groups a user belongs to
|
||||
in the user database (e.g. LDAP if configured) are also taken into account for
|
||||
access control.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
|
||||
@@ -7,8 +7,13 @@ default: [root]
|
||||
A list of user names, separated by whitespace.
|
||||
These users will have additional rights when connecting to the Nix daemon, such as the ability to specify additional [substituters](#conf-substituters), or to import unsigned [NARs](@docroot@/glossary.md#gloss-nar).
|
||||
|
||||
You can also specify groups by prefixing names with `@`.
|
||||
For instance, `@wheel` means all users in the `wheel` group.
|
||||
You can specify groups by prefixing group names with `@`, like `@wheel` for all
|
||||
users in the `wheel` group.
|
||||
|
||||
Both primary and supplementary groups (when the platform supports it) are
|
||||
considered when determining membership. Additionally, groups a user belongs to
|
||||
in the user database (e.g. LDAP if configured) are also taken into account for
|
||||
access control.
|
||||
|
||||
> **Warning**
|
||||
>
|
||||
|
||||
+31
-17
@@ -130,22 +130,21 @@ static bool matchUser(const std::string & user, const struct group & gr)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does the given user (specified by user name and primary group name)
|
||||
* Does the given user (specified by user name, primary group name and supplementary group names)
|
||||
* match the given user/group whitelist?
|
||||
*
|
||||
* If the list allows all users: Yes.
|
||||
*
|
||||
* If the username is in the set: Yes.
|
||||
*
|
||||
* If the groupname is in the set: Yes.
|
||||
*
|
||||
* If the user is in another group which is in the set: yes.
|
||||
* If the groups intersects in the set: Yes.
|
||||
*
|
||||
* Otherwise: No.
|
||||
*/
|
||||
static bool matchUser(const std::string & user, const std::string & group, const Strings & users)
|
||||
static bool
|
||||
matchUser(const std::string & user, const std::unordered_set<std::string> & groups, const Strings & users)
|
||||
{
|
||||
if (find(users.begin(), users.end(), "*") != users.end())
|
||||
return true;
|
||||
@@ -156,7 +155,7 @@ static bool matchUser(const std::string & user, const std::string & group, const
|
||||
for (auto & i : users)
|
||||
if (i.substr(0, 1) == "@") {
|
||||
auto rest = i.substr(1);
|
||||
if (group == rest) {
|
||||
if (groups.contains(rest)) {
|
||||
return true;
|
||||
}
|
||||
struct group * gr = sys::getgrnam(rest);
|
||||
@@ -292,26 +291,41 @@ static std::pair<TrustedFlag, std::string> authPeer(const PeerInfo & peer)
|
||||
struct passwd * pw = getpwuid(peer.uid);
|
||||
std::string user = pw ? pw->pw_name : std::to_string(peer.uid);
|
||||
|
||||
struct group * gr = getgrgid(peer.gid);
|
||||
std::string group = gr ? gr->gr_name : std::to_string(peer.gid);
|
||||
auto assertHealthyGroup = [&](const std::string & group) {
|
||||
if (group == settings.buildUsersGroup) {
|
||||
throw Error(
|
||||
"the user '%1%' is not allowed to connect to the Nix daemon as its group is '%2%', "
|
||||
"which is the group of users running the sandboxed builds.",
|
||||
user,
|
||||
group
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
if (group == settings.buildUsersGroup) {
|
||||
throw Error(
|
||||
"the user '%1%' is not allowed to connect to the Nix daemon as its group is '%2%', "
|
||||
"which is the group of users running the sandboxed builds.",
|
||||
user,
|
||||
group
|
||||
);
|
||||
std::unordered_set<std::string> groups;
|
||||
|
||||
auto insertGroup = [&](gid_t gid) {
|
||||
auto gr = getgrgid(gid);
|
||||
std::string group = gr ? gr->gr_name : std::to_string(gid);
|
||||
assertHealthyGroup(group);
|
||||
groups.insert(group);
|
||||
};
|
||||
|
||||
// This ensures that the `groups` set is always non-empty with the primary group name.
|
||||
insertGroup(peer.gid);
|
||||
|
||||
for (const gid_t suppGid : peer.supplementaryGids) {
|
||||
insertGroup(suppGid);
|
||||
}
|
||||
|
||||
const Strings & trustedUsers = authorizationSettings.trustedUsers;
|
||||
const Strings & allowedUsers = authorizationSettings.allowedUsers;
|
||||
|
||||
if (matchUser(user, group, trustedUsers)) {
|
||||
if (matchUser(user, groups, trustedUsers)) {
|
||||
trusted = Trusted;
|
||||
}
|
||||
|
||||
if (!trusted && !matchUser(user, group, allowedUsers)) {
|
||||
if (!trusted && !matchUser(user, groups, allowedUsers)) {
|
||||
throw Error("user '%1%' is not allowed to connect to the Nix daemon", user);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
{
|
||||
name = "authorization";
|
||||
|
||||
nodes.machine = {
|
||||
nodes.machine = { config, ... }: {
|
||||
virtualisation.writableStore = true;
|
||||
# TODO add a test without allowed-users setting. allowed-users is uncommon among NixOS users.
|
||||
nix.settings.allowed-users = ["alice" "bob"];
|
||||
nix.settings.allowed-users = ["alice" "bob" "@special-group"];
|
||||
nix.settings.trusted-users = ["alice"];
|
||||
|
||||
users.users.alice.isNormalUser = true;
|
||||
users.users.bob.isNormalUser = true;
|
||||
users.users.mallory.isNormalUser = true;
|
||||
users.groups.special-group = {};
|
||||
|
||||
systemd.services.nix-connect-test = {
|
||||
description = "Test Nix daemon connection as a DynamicUser with allowed suppgp";
|
||||
serviceConfig = {
|
||||
DynamicUser = true;
|
||||
SupplementaryGroups = "special-group";
|
||||
ExecStart = "${config.nix.package}/bin/nix store ping";
|
||||
Restart = "on-failure";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
nix.settings.experimental-features = "nix-command";
|
||||
};
|
||||
@@ -20,6 +33,7 @@
|
||||
in
|
||||
''
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_unit("nix-connect-test.service")
|
||||
machine.succeed("""
|
||||
exec 1>&2
|
||||
echo kSELDhobKaF8/VdxIxdP7EQe+Q > one
|
||||
|
||||
Reference in New Issue
Block a user