Skip to content

Commit

Permalink
feat: add an easy way to configure users and groups
Browse files Browse the repository at this point in the history
Just like nixpkgs provides `dockerTools.shadowSetup`, nix2container needs a quick way to configure users, groups and permissions within the container.

Here it is.

@moduon MT-1075
  • Loading branch information
yajo committed Apr 10, 2024
1 parent 2154ad0 commit c8f094a
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 4 deletions.
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,34 @@ Function arguments are:
building the layer. This is mainly useful to ignore the
configuration file from the container layer.
### `nix2container.shadowLayer`
Helper to create a layer and quickly customize the users and groups available
inside the container.
Function arguments:
- **`includeRoot`** (defaults to `false`): enable to add a `root` user (uid 0).
- **`users`** (defaults to `[{uname = "somebody"; uid = 1000;}]`): non-root
users configuration. Valid keys are:
- **`uname`**: user name.
- **`uid`**: user id, usually something between 1000 and 3000.
- **`gname`** (defaults to the same as `uname`): main group name.
- **`gid`** (defaults to the same as `gid`): main group id.
- **`home`** (defaults to `/home/${uname}`): user's home dir. It will
belong to `uname` and `gname`, with mode `u=rwx,g=rx,o=`.
- **`shell`** (defaults to `pkgs.runtimeShell`): user's shell.
- **`extraGroups`** (defaults to `[]`): list of attrsets with extra
groups to create in the image and add to the user. Valid keys are `gid`
(optional) and `gname`.
## Isolate dependencies in dedicated layers
Expand Down
92 changes: 89 additions & 3 deletions default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -477,8 +477,94 @@ let
pkgs.lib.throwIf (contents != null && copyToRoot != null)
"You can not specify both contents and copyToRoot."
;
in
{

# Similar to dockerTools.shadowSetup, but simpler to integrate here
shadowLayer = {
# Enable to include a root user in the container
includeRoot ? false,
# List of users attrsets. Valid keys: see addUser.
users ? [
{
uname = "somebody";
uid = 1000;
}
],
}: let
rootUser = {
uname = "root";
uid = 0;
home = "/root";
};
addGroup = {
gname,
gid ? null,
}: let
gidFlag = l.optionalString (gid != null) "-g ${l.toString gid}";
in ''
groupadd -P $out ${gidFlag} ${gname}
'';
addUser = {
extraGroups ? [], # List of attrsets. Valid keys: see addGroup.
gid ? uid,
gname ? uname,
home ? "/home/${uname}",
shell ? pkgs.runtimeShell,
uid,
uname,
}: let
eG =
l.optionalString (l.length extraGroups > 0)
"-G ${l.concatStringsSep "," extraGroups}";
allGroups = [{inherit gname gid;}] ++ extraGroups;
addAllGroups = l.concatMapStrings addGroup allGroups;
in ''
${addAllGroups}
useradd -P $out -u ${l.toString uid} -g ${l.toString gid} -Md ${home} -s ${shell} ${eG} ${uname}
mkdir -p $out${home}
'';
allUsers = l.optionals includeRoot [rootUser] ++ users;
shadowSetup =
pkgs.runCommand "shadow-setup" {
nativeBuildInputs = [pkgs.shadow];
} ''
mkdir -p $out/etc/pam.d
touch $out/etc/login.defs $out/etc/passwd $out/etc/group
cat > $out/etc/pam.d/other <<EOF
account sufficient pam_unix.so
auth sufficient pam_rootok.so
password requisite pam_unix.so nullok yescrypt
session required pam_unix.so
EOF
${l.concatMapStrings addUser allUsers}
'';
userPerms = {
gid ? uid,
gname ? uname,
home ? "/home/${uname}",
uid,
uname,
...
}: {
inherit gid gname uid uname;
mode = "u=rwx,g=rx,o=";
regex = home;
};
in
buildLayer {
copyToRoot = shadowSetup;
perms = l.forEach allUsers userPerms;
};
in {
inherit nix2container-bin skopeo-nix2container;
nix2container = { inherit buildImage buildLayer pullImage pullImageFromManifest; };
nix2container = {
inherit
buildImage
buildLayer
pullImage
pullImageFromManifest
shadowLayer
;
};
}
1 change: 1 addition & 0 deletions examples/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
nix = pkgs.callPackage ./nix.nix { inherit nix2container; };
nix-user = pkgs.callPackage ./nix-user.nix { inherit nix2container; };
ownership = pkgs.callPackage ./ownership.nix { inherit nix2container; };
shadow = pkgs.callPackage ./shadow.nix { inherit nix2container; };
}
18 changes: 18 additions & 0 deletions examples/shadow.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
pkgs,
nix2container,
}:
nix2container.buildImage {
name = "shadow";
tag = "latest";

layers = [
(nix2container.shadowLayer {includeRoot = true;})
];

copyToRoot = [pkgs.coreutils];

config = {
User = "somebody";
};
}
14 changes: 13 additions & 1 deletion tests/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ let
image,
command ? "",
grepFlags ? "",
runFlags ? "",
pattern,
}: pkgs.writeScriptBin "test-script" ''
${image.copyToPodman}/bin/copy-to-podman
${pkgs.podman}/bin/podman run ${image.imageName}:${image.imageTag} ${command} | ${pkgs.gnugrep}/bin/grep ${grepFlags} '${pattern}'
${pkgs.podman}/bin/podman run ${runFlags} ${image.imageName}:${image.imageTag} ${command} | ${pkgs.gnugrep}/bin/grep ${grepFlags} '${pattern}'
ret=$?
if [ $ret -ne 0 ];
then
Expand Down Expand Up @@ -86,6 +87,17 @@ let
grepFlags = "-Pz";
pattern = "(?s)\[PASS].*\[PASS].*\[PASS].*drwxr-xr-x \\d+ user user 4096 Jan 1 1970 store";
};
shadow-somebody = testScript {
image = examples.shadow;
command = "id";
pattern = "uid=1000(somebody) gid=1000(somebody) groups=1000(somebody)";
};
shadow-root = testScript {
image = examples.shadow;
runFlags = "-u root";
command = "id";
pattern = "uid=0(root) gid=0(root) groups=0(root)";
};
# Ensure the Nix database is correctly initialized by querying the
# closure of the Nix binary.
# The store path is in a dedicated layer
Expand Down

0 comments on commit c8f094a

Please sign in to comment.