2014-08-15 00:33:20 +01:00
|
|
|
|
use strict;
|
2021-09-03 16:21:36 +01:00
|
|
|
|
use warnings;
|
2014-08-15 00:33:20 +01:00
|
|
|
|
use File::Path qw(make_path);
|
|
|
|
|
use File::Slurp;
|
2021-09-03 16:21:36 +01:00
|
|
|
|
use Getopt::Long;
|
2014-08-15 00:33:20 +01:00
|
|
|
|
use JSON;
|
|
|
|
|
|
2017-03-29 17:10:20 +01:00
|
|
|
|
# Keep track of deleted uids and gids.
|
|
|
|
|
my $uidMapFile = "/var/lib/nixos/uid-map";
|
|
|
|
|
my $uidMap = -e $uidMapFile ? decode_json(read_file($uidMapFile)) : {};
|
|
|
|
|
|
|
|
|
|
my $gidMapFile = "/var/lib/nixos/gid-map";
|
|
|
|
|
my $gidMap = -e $gidMapFile ? decode_json(read_file($gidMapFile)) : {};
|
|
|
|
|
|
2021-09-03 16:21:36 +01:00
|
|
|
|
my $is_dry = ($ENV{'NIXOS_ACTION'} // "") eq "dry-activate";
|
|
|
|
|
GetOptions("dry-activate" => \$is_dry);
|
|
|
|
|
make_path("/var/lib/nixos", { mode => 0755 }) unless $is_dry;
|
2017-03-29 17:10:20 +01:00
|
|
|
|
|
|
|
|
|
sub updateFile {
|
|
|
|
|
my ($path, $contents, $perms) = @_;
|
2021-09-03 16:21:36 +01:00
|
|
|
|
return if $is_dry;
|
2020-09-23 09:47:39 +01:00
|
|
|
|
write_file($path, { atomic => 1, binmode => ':utf8', perms => $perms // 0644 }, $contents) or die;
|
2017-03-29 17:10:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-03 16:21:36 +01:00
|
|
|
|
sub nscdInvalidate {
|
|
|
|
|
system("nscd", "--invalidate", $_[0]) unless $is_dry;
|
|
|
|
|
}
|
2017-03-29 17:10:20 +01:00
|
|
|
|
|
2014-10-31 16:39:19 +00:00
|
|
|
|
sub hashPassword {
|
|
|
|
|
my ($password) = @_;
|
|
|
|
|
my $salt = "";
|
|
|
|
|
my @chars = ('.', '/', 0..9, 'A'..'Z', 'a'..'z');
|
|
|
|
|
$salt .= $chars[rand 64] for (1..8);
|
|
|
|
|
return crypt($password, '$6$' . $salt . '$');
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-03 16:21:36 +01:00
|
|
|
|
sub dry_print {
|
|
|
|
|
if ($is_dry) {
|
|
|
|
|
print STDERR ("$_[1] $_[2]\n")
|
|
|
|
|
} else {
|
|
|
|
|
print STDERR ("$_[0] $_[2]\n")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-10-31 16:39:19 +00:00
|
|
|
|
|
2014-08-15 00:33:20 +01:00
|
|
|
|
# Functions for allocating free GIDs/UIDs. FIXME: respect ID ranges in
|
|
|
|
|
# /etc/login.defs.
|
|
|
|
|
sub allocId {
|
2017-03-29 17:10:20 +01:00
|
|
|
|
my ($used, $prevUsed, $idMin, $idMax, $up, $getid) = @_;
|
2014-08-15 00:33:20 +01:00
|
|
|
|
my $id = $up ? $idMin : $idMax;
|
|
|
|
|
while ($id >= $idMin && $id <= $idMax) {
|
2017-03-29 17:10:20 +01:00
|
|
|
|
if (!$used->{$id} && !$prevUsed->{$id} && !defined &$getid($id)) {
|
2014-08-15 00:33:20 +01:00
|
|
|
|
$used->{$id} = 1;
|
|
|
|
|
return $id;
|
|
|
|
|
}
|
|
|
|
|
$used->{$id} = 1;
|
|
|
|
|
if ($up) { $id++; } else { $id--; }
|
|
|
|
|
}
|
|
|
|
|
die "$0: out of free UIDs or GIDs\n";
|
|
|
|
|
}
|
|
|
|
|
|
2017-03-29 17:10:20 +01:00
|
|
|
|
my (%gidsUsed, %uidsUsed, %gidsPrevUsed, %uidsPrevUsed);
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
|
|
|
|
sub allocGid {
|
2017-03-29 17:10:20 +01:00
|
|
|
|
my ($name) = @_;
|
|
|
|
|
my $prevGid = $gidMap->{$name};
|
|
|
|
|
if (defined $prevGid && !defined $gidsUsed{$prevGid}) {
|
2021-09-03 16:21:36 +01:00
|
|
|
|
dry_print("reviving", "would revive", "group '$name' with GID $prevGid");
|
2017-03-29 17:10:20 +01:00
|
|
|
|
$gidsUsed{$prevGid} = 1;
|
|
|
|
|
return $prevGid;
|
|
|
|
|
}
|
2019-07-31 22:19:49 +01:00
|
|
|
|
return allocId(\%gidsUsed, \%gidsPrevUsed, 400, 999, 0, sub { my ($gid) = @_; getgrgid($gid) });
|
2014-08-15 00:33:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sub allocUid {
|
2017-03-29 17:10:20 +01:00
|
|
|
|
my ($name, $isSystemUser) = @_;
|
2019-07-31 22:19:49 +01:00
|
|
|
|
my ($min, $max, $up) = $isSystemUser ? (400, 999, 0) : (1000, 29999, 1);
|
2017-03-29 17:10:20 +01:00
|
|
|
|
my $prevUid = $uidMap->{$name};
|
|
|
|
|
if (defined $prevUid && $prevUid >= $min && $prevUid <= $max && !defined $uidsUsed{$prevUid}) {
|
2021-09-03 16:21:36 +01:00
|
|
|
|
dry_print("reviving", "would revive", "user '$name' with UID $prevUid");
|
2017-03-29 17:10:20 +01:00
|
|
|
|
$uidsUsed{$prevUid} = 1;
|
|
|
|
|
return $prevUid;
|
|
|
|
|
}
|
|
|
|
|
return allocId(\%uidsUsed, \%uidsPrevUsed, $min, $max, $up, sub { my ($uid) = @_; getpwuid($uid) });
|
2014-08-15 00:33:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-09-03 16:21:36 +01:00
|
|
|
|
# Read the declared users/groups
|
2014-08-15 00:33:20 +01:00
|
|
|
|
my $spec = decode_json(read_file($ARGV[0]));
|
|
|
|
|
|
2017-03-29 17:10:20 +01:00
|
|
|
|
# Don't allocate UIDs/GIDs that are manually assigned.
|
2014-08-15 00:33:20 +01:00
|
|
|
|
foreach my $g (@{$spec->{groups}}) {
|
|
|
|
|
$gidsUsed{$g->{gid}} = 1 if defined $g->{gid};
|
|
|
|
|
}
|
|
|
|
|
|
2016-08-30 16:13:41 +01:00
|
|
|
|
foreach my $u (@{$spec->{users}}) {
|
|
|
|
|
$uidsUsed{$u->{uid}} = 1 if defined $u->{uid};
|
2014-08-15 00:33:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
2017-03-29 17:10:20 +01:00
|
|
|
|
# Likewise for previously used but deleted UIDs/GIDs.
|
|
|
|
|
$uidsPrevUsed{$_} = 1 foreach values %{$uidMap};
|
|
|
|
|
$gidsPrevUsed{$_} = 1 foreach values %{$gidMap};
|
|
|
|
|
|
|
|
|
|
|
2014-08-15 00:33:20 +01:00
|
|
|
|
# Read the current /etc/group.
|
|
|
|
|
sub parseGroup {
|
|
|
|
|
chomp;
|
|
|
|
|
my @f = split(':', $_, -4);
|
|
|
|
|
my $gid = $f[2] eq "" ? undef : int($f[2]);
|
|
|
|
|
$gidsUsed{$gid} = 1 if defined $gid;
|
|
|
|
|
return ($f[0], { name => $f[0], password => $f[1], gid => $gid, members => $f[3] });
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-23 09:44:59 +01:00
|
|
|
|
my %groupsCur = -f "/etc/group" ? map { parseGroup } read_file("/etc/group", { binmode => ":utf8" }) : ();
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
|
|
|
|
# Read the current /etc/passwd.
|
|
|
|
|
sub parseUser {
|
|
|
|
|
chomp;
|
|
|
|
|
my @f = split(':', $_, -7);
|
|
|
|
|
my $uid = $f[2] eq "" ? undef : int($f[2]);
|
|
|
|
|
$uidsUsed{$uid} = 1 if defined $uid;
|
|
|
|
|
return ($f[0], { name => $f[0], fakePassword => $f[1], uid => $uid,
|
|
|
|
|
gid => $f[3], description => $f[4], home => $f[5], shell => $f[6] });
|
|
|
|
|
}
|
2020-09-23 09:44:59 +01:00
|
|
|
|
my %usersCur = -f "/etc/passwd" ? map { parseUser } read_file("/etc/passwd", { binmode => ":utf8" }) : ();
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
|
|
|
|
# Read the groups that were created declaratively (i.e. not by groups)
|
|
|
|
|
# in the past. These must be removed if they are no longer in the
|
|
|
|
|
# current spec.
|
|
|
|
|
my $declGroupsFile = "/var/lib/nixos/declarative-groups";
|
|
|
|
|
my %declGroups;
|
2020-09-23 09:44:59 +01:00
|
|
|
|
$declGroups{$_} = 1 foreach split / /, -e $declGroupsFile ? read_file($declGroupsFile, { binmode => ":utf8" }) : "";
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
|
|
|
|
# Idem for the users.
|
|
|
|
|
my $declUsersFile = "/var/lib/nixos/declarative-users";
|
|
|
|
|
my %declUsers;
|
2020-09-23 09:44:59 +01:00
|
|
|
|
$declUsers{$_} = 1 foreach split / /, -e $declUsersFile ? read_file($declUsersFile, { binmode => ":utf8" }) : "";
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Generate a new /etc/group containing the declared groups.
|
|
|
|
|
my %groupsOut;
|
|
|
|
|
foreach my $g (@{$spec->{groups}}) {
|
|
|
|
|
my $name = $g->{name};
|
|
|
|
|
my $existing = $groupsCur{$name};
|
|
|
|
|
|
|
|
|
|
my %members = map { ($_, 1) } @{$g->{members}};
|
|
|
|
|
|
|
|
|
|
if (defined $existing) {
|
|
|
|
|
$g->{gid} = $existing->{gid} if !defined $g->{gid};
|
|
|
|
|
if ($g->{gid} != $existing->{gid}) {
|
2021-09-03 16:21:36 +01:00
|
|
|
|
dry_print("warning: not applying", "warning: would not apply", "GID change of group ‘$name’ ($existing->{gid} -> $g->{gid})");
|
2014-08-15 00:33:20 +01:00
|
|
|
|
$g->{gid} = $existing->{gid};
|
|
|
|
|
}
|
|
|
|
|
$g->{password} = $existing->{password}; # do we want this?
|
|
|
|
|
if ($spec->{mutableUsers}) {
|
|
|
|
|
# Merge in non-declarative group members.
|
|
|
|
|
foreach my $uname (split /,/, $existing->{members} // "") {
|
|
|
|
|
$members{$uname} = 1 if !defined $declUsers{$uname};
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2017-03-29 17:10:20 +01:00
|
|
|
|
$g->{gid} = allocGid($name) if !defined $g->{gid};
|
2014-08-15 00:33:20 +01:00
|
|
|
|
$g->{password} = "x";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$g->{members} = join ",", sort(keys(%members));
|
|
|
|
|
$groupsOut{$name} = $g;
|
2017-03-29 17:10:20 +01:00
|
|
|
|
|
|
|
|
|
$gidMap->{$name} = $g->{gid};
|
2014-08-15 00:33:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Update the persistent list of declarative groups.
|
2017-03-29 17:10:20 +01:00
|
|
|
|
updateFile($declGroupsFile, join(" ", sort(keys %groupsOut)));
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
|
|
|
|
# Merge in the existing /etc/group.
|
|
|
|
|
foreach my $name (keys %groupsCur) {
|
|
|
|
|
my $g = $groupsCur{$name};
|
|
|
|
|
next if defined $groupsOut{$name};
|
|
|
|
|
if (!$spec->{mutableUsers} || defined $declGroups{$name}) {
|
2021-09-03 16:21:36 +01:00
|
|
|
|
dry_print("removing group", "would remove group", "‘$name’");
|
2014-08-15 00:33:20 +01:00
|
|
|
|
} else {
|
|
|
|
|
$groupsOut{$name} = $g;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Rewrite /etc/group. FIXME: acquire lock.
|
|
|
|
|
my @lines = map { join(":", $_->{name}, $_->{password}, $_->{gid}, $_->{members}) . "\n" }
|
|
|
|
|
(sort { $a->{gid} <=> $b->{gid} } values(%groupsOut));
|
2022-10-17 01:37:23 +01:00
|
|
|
|
updateFile($gidMapFile, to_json($gidMap, {canonical => 1}));
|
2017-03-29 17:10:20 +01:00
|
|
|
|
updateFile("/etc/group", \@lines);
|
2021-09-03 16:21:36 +01:00
|
|
|
|
nscdInvalidate("group");
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
|
|
|
|
# Generate a new /etc/passwd containing the declared users.
|
|
|
|
|
my %usersOut;
|
|
|
|
|
foreach my $u (@{$spec->{users}}) {
|
|
|
|
|
my $name = $u->{name};
|
|
|
|
|
|
|
|
|
|
# Resolve the gid of the user.
|
|
|
|
|
if ($u->{group} =~ /^[0-9]$/) {
|
|
|
|
|
$u->{gid} = $u->{group};
|
|
|
|
|
} elsif (defined $groupsOut{$u->{group}}) {
|
|
|
|
|
$u->{gid} = $groupsOut{$u->{group}}->{gid} // die;
|
|
|
|
|
} else {
|
|
|
|
|
warn "warning: user ‘$name’ has unknown group ‘$u->{group}’\n";
|
|
|
|
|
$u->{gid} = 65534;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $existing = $usersCur{$name};
|
|
|
|
|
if (defined $existing) {
|
|
|
|
|
$u->{uid} = $existing->{uid} if !defined $u->{uid};
|
|
|
|
|
if ($u->{uid} != $existing->{uid}) {
|
2021-09-03 16:21:36 +01:00
|
|
|
|
dry_print("warning: not applying", "warning: would not apply", "UID change of user ‘$name’ ($existing->{uid} -> $u->{uid})");
|
2014-08-15 00:33:20 +01:00
|
|
|
|
$u->{uid} = $existing->{uid};
|
|
|
|
|
}
|
|
|
|
|
} else {
|
2017-03-29 17:10:20 +01:00
|
|
|
|
$u->{uid} = allocUid($name, $u->{isSystemUser}) if !defined $u->{uid};
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
2014-11-03 10:59:38 +00:00
|
|
|
|
if (defined $u->{initialPassword}) {
|
|
|
|
|
$u->{hashedPassword} = hashPassword($u->{initialPassword});
|
|
|
|
|
} elsif (defined $u->{initialHashedPassword}) {
|
|
|
|
|
$u->{hashedPassword} = $u->{initialHashedPassword};
|
|
|
|
|
}
|
2015-02-24 10:57:03 +00:00
|
|
|
|
}
|
2014-11-03 10:59:38 +00:00
|
|
|
|
|
nixos/users-groups: createHome: Ensure HOME permissions, fix description
configuration.nix(1) states
users.extraUsers.<name>.createHome
[...] If [...] the home directory already exists but is not
owned by the user, directory owner and group will be changed to
match the user.
i.e. ownership would change only if the user mismatched; the code
however ignores the owner, it is sufficient to enable `createHome`:
if ($u->{createHome}) {
make_path($u->{home}, { mode => 0700 }) if ! -e $u->{home};
chown $u->{uid}, $u->{gid}, $u->{home};
}
Furthermore, permissions are ignored on already existing directories and
therefore may allow others to read private data eventually.
Given that createHome already acts as switch to not only create but
effectively own the home directory, manage permissions in the same
manner to ensure the intended default and cover all primary attributes.
Avoid yet another configuration option to have administrators make a
clear and simple choice between securely managing home directories
and optionally defering management to own code (taking care of custom
location, ownership, mode, extended attributes, etc.).
While here, simplify and thereby fix misleading documentation.
2020-11-22 22:42:02 +00:00
|
|
|
|
# Ensure home directory incl. ownership and permissions.
|
2022-05-21 13:18:10 +01:00
|
|
|
|
if ($u->{createHome} and !$is_dry) {
|
|
|
|
|
make_path($u->{home}, { mode => oct($u->{homeMode}) }) if ! -e $u->{home};
|
2015-02-24 10:57:03 +00:00
|
|
|
|
chown $u->{uid}, $u->{gid}, $u->{home};
|
2022-04-10 20:06:19 +01:00
|
|
|
|
chmod oct($u->{homeMode}), $u->{home};
|
2014-08-15 00:33:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (defined $u->{passwordFile}) {
|
|
|
|
|
if (-e $u->{passwordFile}) {
|
|
|
|
|
$u->{hashedPassword} = read_file($u->{passwordFile});
|
|
|
|
|
chomp $u->{hashedPassword};
|
|
|
|
|
} else {
|
|
|
|
|
warn "warning: password file ‘$u->{passwordFile}’ does not exist\n";
|
|
|
|
|
}
|
2014-10-31 16:39:19 +00:00
|
|
|
|
} elsif (defined $u->{password}) {
|
|
|
|
|
$u->{hashedPassword} = hashPassword($u->{password});
|
2014-08-15 00:33:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
2018-06-04 05:01:53 +01:00
|
|
|
|
if (!defined $u->{shell}) {
|
|
|
|
|
if (defined $existing) {
|
|
|
|
|
$u->{shell} = $existing->{shell};
|
|
|
|
|
} else {
|
|
|
|
|
warn "warning: no declarative or previous shell for ‘$name’, setting shell to nologin\n";
|
|
|
|
|
$u->{shell} = "/run/current-system/sw/bin/nologin";
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-08-15 00:33:20 +01:00
|
|
|
|
$u->{fakePassword} = $existing->{fakePassword} // "x";
|
|
|
|
|
$usersOut{$name} = $u;
|
2017-03-29 17:10:20 +01:00
|
|
|
|
|
|
|
|
|
$uidMap->{$name} = $u->{uid};
|
2014-08-15 00:33:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Update the persistent list of declarative users.
|
2017-03-29 17:10:20 +01:00
|
|
|
|
updateFile($declUsersFile, join(" ", sort(keys %usersOut)));
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
|
|
|
|
# Merge in the existing /etc/passwd.
|
|
|
|
|
foreach my $name (keys %usersCur) {
|
|
|
|
|
my $u = $usersCur{$name};
|
|
|
|
|
next if defined $usersOut{$name};
|
|
|
|
|
if (!$spec->{mutableUsers} || defined $declUsers{$name}) {
|
2021-09-03 16:21:36 +01:00
|
|
|
|
dry_print("removing user", "would remove user", "‘$name’");
|
2014-08-15 00:33:20 +01:00
|
|
|
|
} else {
|
|
|
|
|
$usersOut{$name} = $u;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
# Rewrite /etc/passwd. FIXME: acquire lock.
|
|
|
|
|
@lines = map { join(":", $_->{name}, $_->{fakePassword}, $_->{uid}, $_->{gid}, $_->{description}, $_->{home}, $_->{shell}) . "\n" }
|
|
|
|
|
(sort { $a->{uid} <=> $b->{uid} } (values %usersOut));
|
2022-10-17 01:37:23 +01:00
|
|
|
|
updateFile($uidMapFile, to_json($uidMap, {canonical => 1}));
|
2017-03-29 17:10:20 +01:00
|
|
|
|
updateFile("/etc/passwd", \@lines);
|
2021-09-03 16:21:36 +01:00
|
|
|
|
nscdInvalidate("passwd");
|
2014-08-15 00:33:20 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Rewrite /etc/shadow to add new accounts or remove dead ones.
|
|
|
|
|
my @shadowNew;
|
|
|
|
|
my %shadowSeen;
|
|
|
|
|
|
2020-09-23 09:44:59 +01:00
|
|
|
|
foreach my $line (-f "/etc/shadow" ? read_file("/etc/shadow", { binmode => ":utf8" }) : ()) {
|
2014-08-15 00:33:20 +01:00
|
|
|
|
chomp $line;
|
2014-10-31 16:39:19 +00:00
|
|
|
|
my ($name, $hashedPassword, @rest) = split(':', $line, -9);
|
2014-08-15 00:33:20 +01:00
|
|
|
|
my $u = $usersOut{$name};;
|
|
|
|
|
next if !defined $u;
|
2014-11-03 11:13:16 +00:00
|
|
|
|
$hashedPassword = "!" if !$spec->{mutableUsers};
|
2014-10-31 16:39:19 +00:00
|
|
|
|
$hashedPassword = $u->{hashedPassword} if defined $u->{hashedPassword} && !$spec->{mutableUsers}; # FIXME
|
2019-08-26 04:42:31 +01:00
|
|
|
|
chomp $hashedPassword;
|
2014-10-31 16:39:19 +00:00
|
|
|
|
push @shadowNew, join(":", $name, $hashedPassword, @rest) . "\n";
|
2014-08-15 00:33:20 +01:00
|
|
|
|
$shadowSeen{$name} = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach my $u (values %usersOut) {
|
|
|
|
|
next if defined $shadowSeen{$u->{name}};
|
2014-10-31 16:39:19 +00:00
|
|
|
|
my $hashedPassword = "!";
|
|
|
|
|
$hashedPassword = $u->{hashedPassword} if defined $u->{hashedPassword};
|
2014-08-15 00:33:20 +01:00
|
|
|
|
# FIXME: set correct value for sp_lstchg.
|
2014-10-31 16:39:19 +00:00
|
|
|
|
push @shadowNew, join(":", $u->{name}, $hashedPassword, "1::::::") . "\n";
|
2014-08-15 00:33:20 +01:00
|
|
|
|
}
|
|
|
|
|
|
2021-03-17 17:17:51 +00:00
|
|
|
|
updateFile("/etc/shadow", \@shadowNew, 0640);
|
2020-09-24 21:28:52 +01:00
|
|
|
|
{
|
|
|
|
|
my $uid = getpwnam "root";
|
|
|
|
|
my $gid = getgrnam "shadow";
|
|
|
|
|
my $path = "/etc/shadow";
|
2021-09-03 16:21:36 +01:00
|
|
|
|
(chown($uid, $gid, $path) || die "Failed to change ownership of $path: $!") unless $is_dry;
|
2020-09-24 21:28:52 +01:00
|
|
|
|
}
|
2020-04-28 22:30:34 +01:00
|
|
|
|
|
|
|
|
|
# Rewrite /etc/subuid & /etc/subgid to include default container mappings
|
|
|
|
|
|
|
|
|
|
my $subUidMapFile = "/var/lib/nixos/auto-subuid-map";
|
|
|
|
|
my $subUidMap = -e $subUidMapFile ? decode_json(read_file($subUidMapFile)) : {};
|
|
|
|
|
|
|
|
|
|
my (%subUidsUsed, %subUidsPrevUsed);
|
|
|
|
|
|
|
|
|
|
$subUidsPrevUsed{$_} = 1 foreach values %{$subUidMap};
|
|
|
|
|
|
|
|
|
|
sub allocSubUid {
|
|
|
|
|
my ($name, @rest) = @_;
|
|
|
|
|
|
|
|
|
|
# TODO: No upper bounds?
|
|
|
|
|
my ($min, $max, $up) = (100000, 100000 * 100, 1);
|
|
|
|
|
my $prevId = $subUidMap->{$name};
|
|
|
|
|
if (defined $prevId && !defined $subUidsUsed{$prevId}) {
|
|
|
|
|
$subUidsUsed{$prevId} = 1;
|
|
|
|
|
return $prevId;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my $id = allocId(\%subUidsUsed, \%subUidsPrevUsed, $min, $max, $up, sub { my ($uid) = @_; getpwuid($uid) });
|
|
|
|
|
my $offset = $id - 100000;
|
|
|
|
|
my $count = $offset * 65536;
|
|
|
|
|
my $subordinate = 100000 + $count;
|
|
|
|
|
return $subordinate;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
my @subGids;
|
|
|
|
|
my @subUids;
|
|
|
|
|
foreach my $u (values %usersOut) {
|
|
|
|
|
my $name = $u->{name};
|
|
|
|
|
|
|
|
|
|
foreach my $range (@{$u->{subUidRanges}}) {
|
|
|
|
|
my $value = join(":", ($name, $range->{startUid}, $range->{count}));
|
|
|
|
|
push @subUids, $value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
foreach my $range (@{$u->{subGidRanges}}) {
|
|
|
|
|
my $value = join(":", ($name, $range->{startGid}, $range->{count}));
|
|
|
|
|
push @subGids, $value;
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-05 10:20:47 +00:00
|
|
|
|
if($u->{autoSubUidGidRange}) {
|
2020-04-28 22:30:34 +01:00
|
|
|
|
my $subordinate = allocSubUid($name);
|
|
|
|
|
$subUidMap->{$name} = $subordinate;
|
|
|
|
|
my $value = join(":", ($name, $subordinate, 65536));
|
|
|
|
|
push @subUids, $value;
|
|
|
|
|
push @subGids, $value;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateFile("/etc/subuid", join("\n", @subUids) . "\n");
|
|
|
|
|
updateFile("/etc/subgid", join("\n", @subGids) . "\n");
|
|
|
|
|
updateFile($subUidMapFile, encode_json($subUidMap) . "\n");
|