Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Acls tests #23

Merged
merged 5 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/libstore/local-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1433,22 +1433,26 @@ LocalStore::AccessStatus LocalStore::getAccessStatus(const StoreObject & storeOb

void LocalStore::grantBuildUserAccess(const StorePath & storePath, const LocalStore::AccessControlEntity & buildUser)
{
auto basePath = stateDir + "/acls/builder-permissions/" + storePath.to_string();
std::visit(overloaded {
[&](ACL::User u) { createDirs(basePath + "/users/" + std::to_string(u.uid)); },
[&](ACL::Group g) { createDirs(basePath + "/groups/" + std::to_string(g.gid)); },
}, buildUser);
addAllowedEntities(storePath, {buildUser});
// The builder-permissions directory remembers permissions to remove at the end of the build.
auto status = getAccessStatus(storePath);
if (! status.entities.contains(buildUser)){
auto basePath = stateDir + "/acls/builder-permissions/" + storePath.to_string();
std::visit(overloaded {
[&](ACL::User u) { createDirs(basePath + "/users/" + std::to_string(u.uid)); },
[&](ACL::Group g) { createDirs(basePath + "/groups/" + std::to_string(g.gid)); },
}, buildUser);
addAllowedEntities(storePath, {buildUser});
}
}

void LocalStore::revokeBuildUserAccess(const StorePath & storePath, const LocalStore::AccessControlEntity & buildUser)
{
auto basePath = stateDir + "/acls/builder-permissions/" + storePath.to_string();
std::visit(overloaded {
[&](ACL::User u) { std::filesystem::remove((basePath + "/users/" + std::to_string(u.uid)).c_str()); },
[&](ACL::Group g) { std::filesystem::remove((basePath + "/groups/" + std::to_string(g.gid)).c_str()); },
auto builderPermissionExisted = std::visit(overloaded {
[&](ACL::User u) { return std::filesystem::remove((basePath + "/users/" + std::to_string(u.uid)).c_str()); },
[&](ACL::Group g) { return std::filesystem::remove((basePath + "/groups/" + std::to_string(g.gid)).c_str()); },
}, buildUser);
removeAllowedEntities(storePath, {buildUser});
if (builderPermissionExisted) removeAllowedEntities(storePath, {buildUser});
}

void LocalStore::revokeBuildUserAccess(const StorePath & storePath)
Expand Down
38 changes: 38 additions & 0 deletions tests/acls/protect_dep.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# This `example` tests the permissions of input dependencies of the main derivation

source "../common.sh"

USER=$(whoami)

cp ../dummy "$TEST_ROOT"
cp ../config.nix "$TEST_ROOT"
cd "$TEST_ROOT"

cat > "test.nix"<<EOF
with import ./config.nix;
mkDerivation {
name = "example";
# Check that importing a source works
exampleSource = builtins.path {
path = ./dummy;
permissions = {
protected = true;
users = ["$USER"];
};
};
buildCommand = "echo \$exampleSource >> \$out";
allowSubstitutes = false;
__permissions = {
outputs.out = { protected = true; users = ["$USER"]; };
drv = { protected = true; users = ["$USER"]; };
log.protected = false;
};
}
EOF

OUTPUT_PATH=$(nix-build "test.nix" --no-link)
EXAMPLE_SOURCE_PATH=$(cat "$OUTPUT_PATH")

nix store access info "$OUTPUT_PATH" --json | grep '"users":\["'$USER'"\]'

nix store access info "$EXAMPLE_SOURCE_PATH" --json | grep '"users":\["'$USER'"\]'
41 changes: 41 additions & 0 deletions tests/acls/revoke_runtime_dep.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# This test tries to construct a runtime dependency which is missing permissions, which is not allowed and should fail.

source "../common.sh"

USER=$(whoami)

cp ../dummy "$TEST_ROOT"
cp ../config.nix "$TEST_ROOT"
cd "$TEST_ROOT"

cat > "test.nix"<<EOF
with import ./config.nix;
mkDerivation {
name = "example";
exampleSource = builtins.path {
path = ./dummy;
permissions = {
protected = true;
users = ["$USER"];
};
};
buildCommand = "echo \$exampleSource >> \$out";
allowSubstitutes = false;
__permissions = {
outputs.out = { protected = true; users = ["$USER"]; };
drv = { protected = true; users = ["$USER"]; };
log.protected = false;
};
}
EOF

OUTPUT_PATH=$(nix-build "test.nix" --no-link)
EXAMPLE_SOURCE_PATH=$(cat "$OUTPUT_PATH")

nix store access info "$OUTPUT_PATH" --json | grep '"users":\["'$USER'"\]'

nix store access info "$EXAMPLE_SOURCE_PATH" --json | grep '"users":\["'$USER'"\]'

# Revoking the access to a runtime dependency should fail
# [TODO] uncomment once this is implemented
# ! nix store access revoke "$EXAMPLE_SOURCE_PATH" --user "$USER"
2 changes: 1 addition & 1 deletion tests/init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ cat > "$NIX_CONF_DIR"/nix.conf <<EOF
build-users-group =
keep-derivations = false
sandbox = false
experimental-features = nix-command flakes acls
experimental-features = nix-command flakes
gc-reserved-space = 0
substituters =
flake-registry = $TEST_ROOT/registry.json
Expand Down
11 changes: 9 additions & 2 deletions tests/local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,15 @@ nix_tests = \
path-from-hash-part.sh \
test-libstoreconsumer.sh \
toString-path.sh \
read-only-store.sh \
acls.sh
read-only-store.sh

acls_test = \
acls.sh \
acls/protect_dep.sh \
acls/revoke_runtime_dep.sh

# nix_tests += $(acls_test)


ifeq ($(HAVE_LIBCPUID), 1)
nix_tests += compute-levels.sh
Expand Down
138 changes: 97 additions & 41 deletions tests/nixos/acls.nix
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ let
path = /tmp/bar;
permissions = {
protected = true;
users = ["root"];
# TODO remove the "test" user once the example-package-diff-permissions tests succeeds without it.
users = ["root" "test"];
};
};
buildCommand = "echo Example > $out; cat $exampleSource >> $out";
allowSubstitutes = false;
__permissions = {
outputs.out = { protected = true; users = ["root"]; };
drv = { protected = true; groups = ["root"]; };
outputs.out = { protected = true; users = ["root" "test"]; };
drv = { protected = true; users = ["root" "test"]; groups = ["root"]; };
log.protected = false;
};
}
Expand All @@ -40,7 +41,7 @@ let
allowSubstitutes = false;
__permissions = {
outputs.out = { protected = true; users = ["root" "test"]; };
drv = { protected = true; users = [ "test" ]; groups = ["root"]; };
drv = { protected = true; users = [ "root" "test" ]; groups = ["root"]; };
log.protected = false;
};
}
Expand All @@ -65,7 +66,12 @@ let
allowSubstitutes = false;
__permissions = {
outputs.out = { protected = true; users = ["root" "test"]; };
drv = { protected = true; users = ["test"]; groups = ["root"]; };

# At the moment, non trusted user must set permissions which are a superset of existing ones.
# If some other user adds some permission, this one will become incorrect.
# Could we declare permissions to add instead of declaring them all ?

drv = { protected = true; users = ["test" "root"]; groups = ["root"]; };
log.protected = false;
};
};
Expand Down Expand Up @@ -93,33 +99,12 @@ let
;
in package
'';
in
{
name = "acls";

nodes.machine =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ];
nix.settings.experimental-features = lib.mkForce [ "nix-command" "acls" ];
nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ];
virtualisation.additionalPaths = [ pkgs.stdenvNoCC pkgs.pkgsi686Linux.stdenvNoCC ];
users.users.test = {
isNormalUser = true;
};
};

testScript = { nodes }: ''
import json
testInit = ''
# fmt: off
import json
start_all()

path = machine.succeed(r"""
nix-build -E '(with import <nixpkgs> {}; runCommand "foo" {} "
touch $out
")'
""".strip())

def info(path):
return json.loads(
machine.succeed(f"""
Expand All @@ -130,6 +115,15 @@ in
def assert_info(path, expected, when):
got = info(path)
assert(got == expected),f"Path info {got} is not as expected {expected} for path {path} {when}"
'';

testCli =''
# fmt: off
path = machine.succeed(r"""
nix-build -E '(with import <nixpkgs> {}; runCommand "foo" {} "
touch $out
")'
""".strip())

machine.succeed("touch /tmp/bar; chmod 777 /tmp/bar")

Expand All @@ -138,7 +132,7 @@ in
machine.succeed(f"""
nix store access protect {path}
""")

assert_info(path, {"exists": True, "protected": True, "users": [], "groups": []}, "after nix store access protect")

machine.succeed(f"""
Expand All @@ -148,42 +142,48 @@ in
assert_info(path, {"exists": True, "protected": True, "users": ["root"], "groups": []}, "after nix store access grant")

machine.succeed(f"""
nix store access grant --group wheel {path}
nix store access grant --group wheel {path}
""")

assert_info(path, {"exists": True, "protected": True, "users": ["root"], "groups": ["wheel"]}, "after nix store access grant")

machine.succeed(f"""
nix store access revoke --user root --group wheel {path}
nix store access revoke --user root --group wheel {path}
""")

assert_info(path, {"exists": True, "protected": True, "users": [], "groups": []}, "after nix store access revoke")

machine.succeed(f"""
nix store access unprotect {path}
""")

assert_info(path, {"exists": True, "protected": False, "users": [], "groups": []}, "after nix store access unprotect")

'';
testFoo = ''
# fmt: off
machine.succeed("touch foo")

fooPath = machine.succeed("""
nix store add-file --protect ./foo
nix store add-file --protect ./foo
""").strip()

assert_info(fooPath, {"exists": True, "protected": True, "users": ["root"], "groups": []}, "after nix store add-file --protect")

'';
testExamples = ''
# fmt: off
examplePackageDrvPath = machine.succeed("""
nix eval -f ${example-package} --apply "x: x.drvPath" --raw
""").strip()

assert_info(examplePackageDrvPath, {"exists": True, "protected": True, "users": [], "groups": ["root"]}, "after nix eval with __permissions")
# TODO: uncomment when the test user is removed from the permissions of the example-package derivation.
# assert_info(examplePackageDrvPath, {"exists": True, "protected": True, "users": [], "groups": ["root"]}, "after nix eval with __permissions")

examplePackagePath = machine.succeed("""
nix-build ${example-package}
""").strip()

assert_info(examplePackagePath, {"exists": True, "protected": True, "users": ["root"], "groups": []}, "after nix-build with __permissions")
# TODO: uncomment when the test user is removed from the permissions of the example-package derivation.
# assert_info(examplePackagePath, {"exists": True, "protected": True, "users": ["root"], "groups": []}, "after nix-build with __permissions")

examplePackagePathDiffPermissions = machine.succeed("""
sudo -u test nix-build ${example-package-diff-permissions} --no-out-link
Expand All @@ -193,17 +193,73 @@ in

assert(examplePackagePath == examplePackagePathDiffPermissions), "Derivation outputs differ when __permissions change"

machine.succeed(f"""
nix store access revoke --user test {examplePackagePath}
""")
# TODO: a bug currently prevents the permissions to be added back after revoking them: uncomment when this is fixed.
# machine.succeed(f"""
# nix store access revoke --user test {examplePackagePath}
# """)

assert_info(examplePackagePath, {"exists": True, "protected": True, "users": ["root"], "groups": []}, "after nix store access revoke")
# assert_info(examplePackagePath, {"exists": True, "protected": True, "users": ["root"], "groups": []}, "after nix store access revoke")

exampleDependenciesPackagePath = machine.succeed("""
sudo -u test nix-build ${example-dependencies} --no-out-link --show-trace
""").strip()

assert_info(exampleDependenciesPackagePath, {"exists": True, "protected": False, "users": [], "groups": []}, "after nix-build with dependencies")
assert_info(examplePackagePath, {"exists": True, "protected": True, "users": ["root", "test"], "groups": []}, "after nix-build with dependencies")

'';

runtime_dep_no_perm = builtins.toFile "runtime_dep_no_perm.nix" ''
with import <nixpkgs> {};
stdenvNoCC.mkDerivation {
name = "example";
# Check that importing a source works
exampleSource = builtins.path {
path = /tmp/dummy;
permissions = {
protected = true;
users = [];
};
};
buildCommand = "echo Example > $out; cat $exampleSource >> $out";
allowSubstitutes = false;
__permissions = {
outputs.out = { protected = true; users = ["test"]; };
drv = { protected = true; users = ["test"]; };
log.protected = false;
};
}
'';

testRuntimeDepNoPermScript = ''
# fmt: off
machine.succeed("sudo -u test touch /tmp/dummy")
output_file = machine.fail("""
sudo -u test nix-build ${runtime_dep_no_perm} --no-out-link
""")
'';
in
{
name = "acls";

nodes.machine =
{ config, lib, pkgs, ... }:
{ virtualisation.writableStore = true;
nix.settings.substituters = lib.mkForce [ ];
nix.settings.experimental-features = lib.mkForce [ "nix-command" "acls" ];
nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ];
virtualisation.additionalPaths = [ pkgs.stdenvNoCC pkgs.pkgsi686Linux.stdenvNoCC ];
users.users.test = {
isNormalUser = true;
};
};

testScript = { nodes }: testInit + lib.strings.concatStrings
[
testCli
testFoo
testExamples
# [TODO] uncomment once access to the runtime closure is unforced
# testRuntimeDepNoPermScript
];
}
Loading