nixos/tests/apparmor: move to folder, refactor, improve coverage

- nixfmt on apparmor test
- move apparmor test to nixos/tests/apparmor directory
- expected profile contents are now generated in its own file to make the test file less confusing and hard to maintain
- enforce/complain is now being tested via diff of expected against aa-status
- path is now tested against diff+file checking symlink target of /etc/static/apparmor.d/<name>
- profile is now checked by diff of /etc/static/apparmor.d/<name> against original string added in nix config
- test still successfully passes
- added test for confined hello to succeed
- added test for confined hexdump on denied path to fail

+1 -1
nixos/tests/all-tests.nix
···
apfs = runTest ./apfs.nix;
appliance-repart-image = runTest ./appliance-repart-image.nix;
appliance-repart-image-verity-store = runTest ./appliance-repart-image-verity-store.nix;
-
apparmor = handleTest ./apparmor.nix {};
+
apparmor = handleTest ./apparmor {};
archi = handleTest ./archi.nix {};
aria2 = handleTest ./aria2.nix {};
armagetronad = handleTest ./armagetronad.nix {};
-118
nixos/tests/apparmor.nix
···
-
import ./make-test-python.nix (
-
{ pkgs, lib, ... }:
-
{
-
name = "apparmor";
-
meta.maintainers = with lib.maintainers; [
-
julm
-
grimmauld
-
];
-
-
nodes.machine =
-
{
-
lib,
-
pkgs,
-
config,
-
...
-
}:
-
{
-
security.apparmor.enable = lib.mkDefault true;
-
};
-
-
testScript = ''
-
machine.wait_for_unit("multi-user.target")
-
-
with subtest("AppArmor profiles are loaded"):
-
machine.succeed("systemctl status apparmor.service")
-
-
# AppArmor securityfs
-
with subtest("AppArmor securityfs is mounted"):
-
machine.succeed("mountpoint -q /sys/kernel/security")
-
machine.succeed("cat /sys/kernel/security/apparmor/profiles")
-
-
# Test apparmorRulesFromClosure by:
-
# 1. Prepending a string of the relevant packages' name and version on each line.
-
# 2. Sorting according to those strings.
-
# 3. Removing those prepended strings.
-
# 4. Using `diff` against the expected output.
-
with subtest("apparmorRulesFromClosure"):
-
machine.succeed(
-
"${pkgs.diffutils}/bin/diff -u ${pkgs.writeText "expected.rules" ''
-
ixr ${pkgs.bash}/libexec/**,
-
mr ${pkgs.bash}/lib/**.so*,
-
mr ${pkgs.bash}/lib64/**.so*,
-
mr ${pkgs.bash}/share/**,
-
r ${pkgs.bash},
-
r ${pkgs.bash}/etc/**,
-
r ${pkgs.bash}/lib/**,
-
r ${pkgs.bash}/lib64/**,
-
x ${pkgs.bash}/foo/**,
-
ixr ${pkgs.glibc}/libexec/**,
-
mr ${pkgs.glibc}/lib/**.so*,
-
mr ${pkgs.glibc}/lib64/**.so*,
-
mr ${pkgs.glibc}/share/**,
-
r ${pkgs.glibc},
-
r ${pkgs.glibc}/etc/**,
-
r ${pkgs.glibc}/lib/**,
-
r ${pkgs.glibc}/lib64/**,
-
x ${pkgs.glibc}/foo/**,
-
ixr ${pkgs.libcap}/libexec/**,
-
mr ${pkgs.libcap}/lib/**.so*,
-
mr ${pkgs.libcap}/lib64/**.so*,
-
mr ${pkgs.libcap}/share/**,
-
r ${pkgs.libcap},
-
r ${pkgs.libcap}/etc/**,
-
r ${pkgs.libcap}/lib/**,
-
r ${pkgs.libcap}/lib64/**,
-
x ${pkgs.libcap}/foo/**,
-
ixr ${pkgs.libcap.lib}/libexec/**,
-
mr ${pkgs.libcap.lib}/lib/**.so*,
-
mr ${pkgs.libcap.lib}/lib64/**.so*,
-
mr ${pkgs.libcap.lib}/share/**,
-
r ${pkgs.libcap.lib},
-
r ${pkgs.libcap.lib}/etc/**,
-
r ${pkgs.libcap.lib}/lib/**,
-
r ${pkgs.libcap.lib}/lib64/**,
-
x ${pkgs.libcap.lib}/foo/**,
-
ixr ${pkgs.libidn2.out}/libexec/**,
-
mr ${pkgs.libidn2.out}/lib/**.so*,
-
mr ${pkgs.libidn2.out}/lib64/**.so*,
-
mr ${pkgs.libidn2.out}/share/**,
-
r ${pkgs.libidn2.out},
-
r ${pkgs.libidn2.out}/etc/**,
-
r ${pkgs.libidn2.out}/lib/**,
-
r ${pkgs.libidn2.out}/lib64/**,
-
x ${pkgs.libidn2.out}/foo/**,
-
ixr ${pkgs.libunistring}/libexec/**,
-
mr ${pkgs.libunistring}/lib/**.so*,
-
mr ${pkgs.libunistring}/lib64/**.so*,
-
mr ${pkgs.libunistring}/share/**,
-
r ${pkgs.libunistring},
-
r ${pkgs.libunistring}/etc/**,
-
r ${pkgs.libunistring}/lib/**,
-
r ${pkgs.libunistring}/lib64/**,
-
x ${pkgs.libunistring}/foo/**,
-
ixr ${pkgs.glibc.libgcc}/libexec/**,
-
mr ${pkgs.glibc.libgcc}/lib/**.so*,
-
mr ${pkgs.glibc.libgcc}/lib64/**.so*,
-
mr ${pkgs.glibc.libgcc}/share/**,
-
r ${pkgs.glibc.libgcc},
-
r ${pkgs.glibc.libgcc}/etc/**,
-
r ${pkgs.glibc.libgcc}/lib/**,
-
r ${pkgs.glibc.libgcc}/lib64/**,
-
x ${pkgs.glibc.libgcc}/foo/**,
-
''} ${
-
pkgs.runCommand "actual.rules" { preferLocalBuild = true; } ''
-
${pkgs.gnused}/bin/sed -e 's:^[^ ]* ${builtins.storeDir}/[^,/-]*-\([^/,]*\):\1 \0:' ${
-
pkgs.apparmorRulesFromClosure {
-
name = "ping";
-
additionalRules = [ "x $path/foo/**" ];
-
} [ pkgs.libcap ]
-
} |
-
${pkgs.coreutils}/bin/sort -n -k1 |
-
${pkgs.gnused}/bin/sed -e 's:^[^ ]* ::' >$out
-
''
-
}"
-
)
-
'';
-
}
-
)
+130
nixos/tests/apparmor/default.nix
···
+
import ../make-test-python.nix (
+
{ pkgs, lib, ... }:
+
let
+
helloProfileContents = ''
+
abi <abi/4.0>,
+
include <tunables/global>
+
profile hello ${lib.getExe pkgs.hello} {
+
include <abstractions/base>
+
}
+
'';
+
in
+
{
+
name = "apparmor";
+
meta.maintainers = with lib.maintainers; [
+
julm
+
grimmauld
+
];
+
+
nodes.machine =
+
{
+
lib,
+
pkgs,
+
config,
+
...
+
}:
+
{
+
security.apparmor = {
+
enable = lib.mkDefault true;
+
+
policies.hello = {
+
# test profile enforce and content definition
+
state = "enforce";
+
profile = helloProfileContents;
+
};
+
+
policies.sl = {
+
# test profile complain and path definition
+
state = "complain";
+
path = ./sl_profile;
+
};
+
+
policies.hexdump = {
+
# test profile complain and path definition
+
state = "enforce";
+
profile = ''
+
abi <abi/4.0>,
+
include <tunables/global>
+
profile hexdump /nix/store/*/bin/hexdump {
+
include <abstractions/base>
+
deny /tmp/** r,
+
}
+
'';
+
};
+
+
includes."abstractions/base" = ''
+
/nix/store/*/bin/** mr,
+
/nix/store/*/lib/** mr,
+
/nix/store/** r,
+
'';
+
};
+
};
+
+
testScript =
+
let
+
inherit (lib) getExe getExe';
+
in
+
''
+
machine.wait_for_unit("multi-user.target")
+
+
with subtest("AppArmor profiles are loaded"):
+
machine.succeed("systemctl status apparmor.service")
+
+
# AppArmor securityfs
+
with subtest("AppArmor securityfs is mounted"):
+
machine.succeed("mountpoint -q /sys/kernel/security")
+
machine.succeed("cat /sys/kernel/security/apparmor/profiles")
+
+
# Test apparmorRulesFromClosure by:
+
# 1. Prepending a string of the relevant packages' name and version on each line.
+
# 2. Sorting according to those strings.
+
# 3. Removing those prepended strings.
+
# 4. Using `diff` against the expected output.
+
with subtest("apparmorRulesFromClosure"):
+
machine.succeed(
+
"${getExe' pkgs.diffutils "diff"} -u ${
+
pkgs.writeText "expected.rules" (import ./makeExpectedPolicies.nix { inherit pkgs; })
+
} ${
+
pkgs.runCommand "actual.rules" { preferLocalBuild = true; } ''
+
${getExe pkgs.gnused} -e 's:^[^ ]* ${builtins.storeDir}/[^,/-]*-\([^/,]*\):\1 \0:' ${
+
pkgs.apparmorRulesFromClosure {
+
name = "ping";
+
additionalRules = [ "x $path/foo/**" ];
+
} [ pkgs.libcap ]
+
} |
+
${getExe' pkgs.coreutils "sort"} -n -k1 |
+
${getExe pkgs.gnused} -e 's:^[^ ]* ::' >$out
+
''
+
}"
+
)
+
+
# Test apparmor profile states by using `diff` against `aa-status`
+
with subtest("apparmorProfileStates"):
+
machine.succeed("${getExe' pkgs.diffutils "diff"} -u \
+
<(${getExe' pkgs.apparmor-bin-utils "aa-status"} --json | ${getExe pkgs.jq} --sort-keys . ) \
+
<(${getExe pkgs.jq} --sort-keys . ${
+
pkgs.writers.writeJSON "expectedStates.json" {
+
version = "2";
+
processes = { };
+
profiles = {
+
hexdump = "enforce";
+
hello = "enforce";
+
sl = "complain";
+
};
+
}
+
})")
+
+
# Test apparmor profile files in /etc/apparmor.d/<name> to be either a correct symlink (sl) or have the right file contents (hello)
+
with subtest("apparmorProfileTargets"):
+
machine.succeed("${getExe' pkgs.diffutils "diff"} -u <(${getExe pkgs.file} /etc/static/apparmor.d/sl) ${pkgs.writeText "expected.link" ''
+
/etc/static/apparmor.d/sl: symbolic link to ${./sl_profile}
+
''}")
+
machine.succeed("${getExe' pkgs.diffutils "diff"} -u /etc/static/apparmor.d/hello ${pkgs.writeText "expected.content" helloProfileContents}")
+
+
+
with subtest("apparmorProfileEnforce"):
+
machine.succeed("${getExe pkgs.hello} 1> /tmp/test-file")
+
machine.fail("${lib.getExe' pkgs.util-linux "hexdump"} /tmp/test-file") # no access to /tmp/test-file granted by apparmor
+
'';
+
}
+
)
+66
nixos/tests/apparmor/makeExpectedPolicies.nix
···
+
{ pkgs }:
+
''
+
ixr ${pkgs.bash}/libexec/**,
+
mr ${pkgs.bash}/lib/**.so*,
+
mr ${pkgs.bash}/lib64/**.so*,
+
mr ${pkgs.bash}/share/**,
+
r ${pkgs.bash},
+
r ${pkgs.bash}/etc/**,
+
r ${pkgs.bash}/lib/**,
+
r ${pkgs.bash}/lib64/**,
+
x ${pkgs.bash}/foo/**,
+
ixr ${pkgs.glibc}/libexec/**,
+
mr ${pkgs.glibc}/lib/**.so*,
+
mr ${pkgs.glibc}/lib64/**.so*,
+
mr ${pkgs.glibc}/share/**,
+
r ${pkgs.glibc},
+
r ${pkgs.glibc}/etc/**,
+
r ${pkgs.glibc}/lib/**,
+
r ${pkgs.glibc}/lib64/**,
+
x ${pkgs.glibc}/foo/**,
+
ixr ${pkgs.libcap}/libexec/**,
+
mr ${pkgs.libcap}/lib/**.so*,
+
mr ${pkgs.libcap}/lib64/**.so*,
+
mr ${pkgs.libcap}/share/**,
+
r ${pkgs.libcap},
+
r ${pkgs.libcap}/etc/**,
+
r ${pkgs.libcap}/lib/**,
+
r ${pkgs.libcap}/lib64/**,
+
x ${pkgs.libcap}/foo/**,
+
ixr ${pkgs.libcap.lib}/libexec/**,
+
mr ${pkgs.libcap.lib}/lib/**.so*,
+
mr ${pkgs.libcap.lib}/lib64/**.so*,
+
mr ${pkgs.libcap.lib}/share/**,
+
r ${pkgs.libcap.lib},
+
r ${pkgs.libcap.lib}/etc/**,
+
r ${pkgs.libcap.lib}/lib/**,
+
r ${pkgs.libcap.lib}/lib64/**,
+
x ${pkgs.libcap.lib}/foo/**,
+
ixr ${pkgs.libidn2.out}/libexec/**,
+
mr ${pkgs.libidn2.out}/lib/**.so*,
+
mr ${pkgs.libidn2.out}/lib64/**.so*,
+
mr ${pkgs.libidn2.out}/share/**,
+
r ${pkgs.libidn2.out},
+
r ${pkgs.libidn2.out}/etc/**,
+
r ${pkgs.libidn2.out}/lib/**,
+
r ${pkgs.libidn2.out}/lib64/**,
+
x ${pkgs.libidn2.out}/foo/**,
+
ixr ${pkgs.libunistring}/libexec/**,
+
mr ${pkgs.libunistring}/lib/**.so*,
+
mr ${pkgs.libunistring}/lib64/**.so*,
+
mr ${pkgs.libunistring}/share/**,
+
r ${pkgs.libunistring},
+
r ${pkgs.libunistring}/etc/**,
+
r ${pkgs.libunistring}/lib/**,
+
r ${pkgs.libunistring}/lib64/**,
+
x ${pkgs.libunistring}/foo/**,
+
ixr ${pkgs.glibc.libgcc}/libexec/**,
+
mr ${pkgs.glibc.libgcc}/lib/**.so*,
+
mr ${pkgs.glibc.libgcc}/lib64/**.so*,
+
mr ${pkgs.glibc.libgcc}/share/**,
+
r ${pkgs.glibc.libgcc},
+
r ${pkgs.glibc.libgcc}/etc/**,
+
r ${pkgs.glibc.libgcc}/lib/**,
+
r ${pkgs.glibc.libgcc}/lib64/**,
+
x ${pkgs.glibc.libgcc}/foo/**,
+
''
+5
nixos/tests/apparmor/sl_profile
···
+
abi <abi/4.0>,
+
include <tunables/global>
+
profile sl /bin/sl {
+
include <abstractions/base>
+
}