nixos/tests/confinement: Parametrise subtests
This is to make sure that we test all of the DynamicUser/User/Group and PrivateTmp options in a uniform way. The reason why we need to do this is because we recently introduced support for the DynamicUser option and since there are some corner cases where we might end up with more elevated privileges (eg. writable directories in some cases), we want to make sure that the environment is as restrictive as with a static User/Group assignment. I also removed various checks that try to os.chown(), since with our new recursive checker those are redundant. Signed-off-by: aszlig <aszlig@nix.build>
This commit is contained in:
parent
51d3f3475c
commit
27f36b5e57
@ -63,168 +63,110 @@ import ../make-test-python.nix {
|
||||
} // removeAttrs config [ "confinement" "serviceConfig" ];
|
||||
};
|
||||
|
||||
in {
|
||||
imports = lib.imap1 mkTestStep [
|
||||
{ description = "chroot-only confinement";
|
||||
config.confinement.mode = "chroot-only";
|
||||
testScript = ''
|
||||
parametrisedTests = lib.concatMap ({ user, privateTmp }: let
|
||||
withTmp = if privateTmp then "with PrivateTmp" else "without PrivateTmp";
|
||||
|
||||
serviceConfig = if user == "static-user" then {
|
||||
User = "chroot-testuser";
|
||||
Group = "chroot-testgroup";
|
||||
} else if user == "dynamic-user" then {
|
||||
DynamicUser = true;
|
||||
} else {};
|
||||
|
||||
in [
|
||||
{ description = "${user}, chroot-only confinement ${withTmp}";
|
||||
config = {
|
||||
confinement.mode = "chroot-only";
|
||||
# Only set if privateTmp is true to ensure that the default is false.
|
||||
serviceConfig = serviceConfig // lib.optionalAttrs privateTmp {
|
||||
PrivateTmp = true;
|
||||
};
|
||||
};
|
||||
testScript = if user == "root" then ''
|
||||
assert os.getuid() == 0
|
||||
assert os.getgid() == 0
|
||||
|
||||
assert_permissions({
|
||||
'bin': Accessibility.WRITABLE,
|
||||
'nix': Accessibility.WRITABLE,
|
||||
'run': Accessibility.WRITABLE,
|
||||
${lib.optionalString privateTmp "'tmp': Accessibility.STICKY,"}
|
||||
${lib.optionalString privateTmp "'var': Accessibility.WRITABLE,"}
|
||||
${lib.optionalString privateTmp "'var/tmp': Accessibility.STICKY,"}
|
||||
})
|
||||
'' else ''
|
||||
assert os.getuid() != 0
|
||||
assert os.getgid() != 0
|
||||
|
||||
assert os.getuid() == 0
|
||||
os.chown('/bin', 65534, 0)
|
||||
assert_permissions({
|
||||
'bin': Accessibility.READABLE,
|
||||
'nix': Accessibility.READABLE,
|
||||
'run': Accessibility.READABLE,
|
||||
${lib.optionalString privateTmp "'tmp': Accessibility.STICKY,"}
|
||||
${lib.optionalString privateTmp "'var': Accessibility.READABLE,"}
|
||||
${lib.optionalString privateTmp "'var/tmp': Accessibility.STICKY,"}
|
||||
})
|
||||
'';
|
||||
}
|
||||
{ description = "full confinement with APIVFS";
|
||||
testScript = ''
|
||||
Path('/etc').rmdir()
|
||||
{ description = "${user}, full APIVFS confinement ${withTmp}";
|
||||
config = {
|
||||
# Only set if privateTmp is false to ensure that the default is true.
|
||||
serviceConfig = serviceConfig // lib.optionalAttrs (!privateTmp) {
|
||||
PrivateTmp = false;
|
||||
};
|
||||
};
|
||||
testScript = if user == "root" then ''
|
||||
assert os.getuid() == 0
|
||||
assert os.getgid() == 0
|
||||
|
||||
assert_permissions({
|
||||
'bin': Accessibility.WRITABLE,
|
||||
'nix': Accessibility.WRITABLE,
|
||||
'tmp': Accessibility.WRITABLE,
|
||||
${lib.optionalString privateTmp "'tmp': Accessibility.STICKY,"}
|
||||
'run': Accessibility.WRITABLE,
|
||||
|
||||
'proc': Accessibility.SPECIAL,
|
||||
'sys': Accessibility.SPECIAL,
|
||||
'dev': Accessibility.WRITABLE,
|
||||
|
||||
${lib.optionalString privateTmp "'var': Accessibility.WRITABLE,"}
|
||||
${lib.optionalString privateTmp "'var/tmp': Accessibility.STICKY,"}
|
||||
})
|
||||
'' else ''
|
||||
assert os.getuid() != 0
|
||||
assert os.getgid() != 0
|
||||
|
||||
bin_gid = Path('/bin').stat().st_gid
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
os.chown('/bin', 65534, bin_gid)
|
||||
assert excinfo.value.errno == errno.EINVAL
|
||||
assert_permissions({
|
||||
'bin': Accessibility.READABLE,
|
||||
'nix': Accessibility.READABLE,
|
||||
${lib.optionalString privateTmp "'tmp': Accessibility.STICKY,"}
|
||||
'run': Accessibility.STICKY,
|
||||
|
||||
assert os.getuid() == 0
|
||||
os.chown('/bin', 0, 0)
|
||||
'proc': Accessibility.SPECIAL,
|
||||
'sys': Accessibility.SPECIAL,
|
||||
|
||||
'dev': Accessibility.SPECIAL,
|
||||
'dev/shm': Accessibility.STICKY,
|
||||
'dev/mqueue': Accessibility.STICKY,
|
||||
|
||||
${lib.optionalString privateTmp "'var': Accessibility.READABLE,"}
|
||||
${lib.optionalString privateTmp "'var/tmp': Accessibility.STICKY,"}
|
||||
})
|
||||
'';
|
||||
}
|
||||
{ description = "check existence of bind-mounted /etc";
|
||||
]) (lib.cartesianProductOfSets {
|
||||
user = [ "root" "dynamic-user" "static-user" ];
|
||||
privateTmp = [ true false ];
|
||||
});
|
||||
|
||||
in {
|
||||
imports = lib.imap1 mkTestStep (parametrisedTests ++ [
|
||||
{ description = "existence of bind-mounted /etc";
|
||||
config.serviceConfig.BindReadOnlyPaths = [ "/etc" ];
|
||||
testScript = ''
|
||||
assert Path('/etc/passwd').read_text()
|
||||
'';
|
||||
}
|
||||
{ description = "check if User/Group really runs as non-root";
|
||||
config.serviceConfig.User = "chroot-testuser";
|
||||
config.serviceConfig.Group = "chroot-testgroup";
|
||||
testScript = ''
|
||||
assert list(Path('/dev').iterdir())
|
||||
|
||||
assert os.getuid() != 0
|
||||
assert os.getgid() != 0
|
||||
|
||||
with pytest.raises(PermissionError):
|
||||
Path('/bin/test').touch()
|
||||
'';
|
||||
}
|
||||
{ description = "check if DynamicUser is working in full-apivfs mode";
|
||||
config.confinement.mode = "full-apivfs";
|
||||
config.serviceConfig.DynamicUser = true;
|
||||
testScript = ''
|
||||
assert_permissions({
|
||||
'bin': Accessibility.READABLE,
|
||||
'nix': Accessibility.READABLE,
|
||||
'tmp': Accessibility.WRITABLE,
|
||||
'run': Accessibility.STICKY,
|
||||
|
||||
'proc': Accessibility.SPECIAL,
|
||||
'sys': Accessibility.SPECIAL,
|
||||
|
||||
'dev': Accessibility.SPECIAL,
|
||||
'dev/shm': Accessibility.STICKY,
|
||||
'dev/mqueue': Accessibility.STICKY,
|
||||
|
||||
'var': Accessibility.READABLE,
|
||||
'var/tmp': Accessibility.WRITABLE,
|
||||
})
|
||||
|
||||
assert os.getuid() != 0
|
||||
assert os.getgid() != 0
|
||||
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
Path('/bin/test').touch()
|
||||
assert excinfo.value.errno == errno.EROFS
|
||||
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
Path('/etc/test').touch()
|
||||
assert excinfo.value.errno == errno.EROFS
|
||||
'';
|
||||
}
|
||||
{ description = "check if DynamicUser and PrivateTmp=false are working in full-apivfs mode";
|
||||
config.confinement.mode = "full-apivfs";
|
||||
config.serviceConfig.DynamicUser = true;
|
||||
config.serviceConfig.PrivateTmp = false;
|
||||
testScript = ''
|
||||
assert_permissions({
|
||||
'bin': Accessibility.READABLE,
|
||||
'nix': Accessibility.READABLE,
|
||||
'run': Accessibility.STICKY,
|
||||
|
||||
'proc': Accessibility.SPECIAL,
|
||||
'sys': Accessibility.SPECIAL,
|
||||
|
||||
'dev': Accessibility.SPECIAL,
|
||||
'dev/shm': Accessibility.STICKY,
|
||||
'dev/mqueue': Accessibility.STICKY,
|
||||
})
|
||||
|
||||
assert os.getuid() != 0
|
||||
assert os.getgid() != 0
|
||||
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
Path('/bin/test').touch()
|
||||
assert excinfo.value.errno == errno.EROFS
|
||||
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
Path('/etc/test').touch()
|
||||
assert excinfo.value.errno == errno.EROFS
|
||||
'';
|
||||
}
|
||||
{ description = "check if DynamicUser is working in chroot-only mode";
|
||||
config.confinement.mode = "chroot-only";
|
||||
config.serviceConfig.DynamicUser = true;
|
||||
testScript = ''
|
||||
assert_permissions({
|
||||
'bin': Accessibility.READABLE,
|
||||
'nix': Accessibility.READABLE,
|
||||
'run': Accessibility.READABLE,
|
||||
})
|
||||
|
||||
assert os.getuid() != 0
|
||||
assert os.getgid() != 0
|
||||
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
Path('/bin/test').touch()
|
||||
assert excinfo.value.errno == errno.EROFS
|
||||
'';
|
||||
}
|
||||
{ description = "check if DynamicUser and PrivateTmp=true are working in chroot-only mode";
|
||||
config.confinement.mode = "chroot-only";
|
||||
config.serviceConfig.DynamicUser = true;
|
||||
config.serviceConfig.PrivateTmp = true;
|
||||
testScript = ''
|
||||
assert_permissions({
|
||||
'bin': Accessibility.READABLE,
|
||||
'nix': Accessibility.READABLE,
|
||||
'run': Accessibility.READABLE,
|
||||
'tmp': Accessibility.WRITABLE,
|
||||
|
||||
'var': Accessibility.READABLE,
|
||||
'var/tmp': Accessibility.WRITABLE,
|
||||
})
|
||||
|
||||
assert os.getuid() != 0
|
||||
assert os.getgid() != 0
|
||||
|
||||
with pytest.raises(OSError) as excinfo:
|
||||
Path('/bin/test').touch()
|
||||
assert excinfo.value.errno == errno.EROFS
|
||||
'';
|
||||
}
|
||||
(let
|
||||
symlink = pkgs.runCommand "symlink" {
|
||||
target = pkgs.writeText "symlink-target" "got me";
|
||||
@ -233,7 +175,6 @@ import ../make-test-python.nix {
|
||||
description = "check if symlinks are properly bind-mounted";
|
||||
config.confinement.packages = lib.singleton symlink;
|
||||
testScript = ''
|
||||
Path('/etc').rmdir()
|
||||
assert Path('${symlink}').read_text() == 'got me'
|
||||
'';
|
||||
})
|
||||
@ -318,7 +259,7 @@ import ../make-test-python.nix {
|
||||
assert excinfo.value.errno == errno.ELOOP
|
||||
'';
|
||||
}
|
||||
];
|
||||
]);
|
||||
|
||||
config.users.groups.chroot-testgroup = {};
|
||||
config.users.users.chroot-testuser = {
|
||||
|
Loading…
Reference in New Issue
Block a user