1{ pkgs, lib, ... }:
2let
3 helloProfileContents = ''
4 abi <abi/4.0>,
5 include <tunables/global>
6 profile hello ${lib.getExe pkgs.hello} {
7 include <abstractions/base>
8 }
9 '';
10in
11{
12 name = "apparmor";
13 meta.maintainers = with lib.maintainers; [
14 julm
15 grimmauld
16 ];
17
18 nodes.machine =
19 {
20 lib,
21 ...
22 }:
23 {
24 security.apparmor = {
25 enable = lib.mkDefault true;
26
27 policies.hello = {
28 # test profile enforce and content definition
29 state = "enforce";
30 profile = helloProfileContents;
31 };
32
33 policies.sl = {
34 # test profile complain and path definition
35 state = "complain";
36 path = ./sl_profile;
37 };
38
39 policies.hexdump = {
40 # test profile complain and path definition
41 state = "enforce";
42 profile = ''
43 abi <abi/4.0>,
44 include <tunables/global>
45 profile hexdump /nix/store/*/bin/hexdump {
46 include <abstractions/base>
47 deny /tmp/** r,
48 }
49 '';
50 };
51
52 includes."abstractions/base" = ''
53 /nix/store/*/bin/** mr,
54 /nix/store/*/lib/** mr,
55 /nix/store/** r,
56 '';
57 };
58 };
59
60 testScript =
61 let
62 inherit (lib) getExe getExe';
63 in
64 ''
65 machine.wait_for_unit("multi-user.target")
66
67 with subtest("AppArmor profiles are loaded"):
68 machine.succeed("systemctl status apparmor.service")
69
70 # AppArmor securityfs
71 with subtest("AppArmor securityfs is mounted"):
72 machine.succeed("mountpoint -q /sys/kernel/security")
73 machine.succeed("cat /sys/kernel/security/apparmor/profiles")
74
75 # Test apparmorRulesFromClosure by:
76 # 1. Prepending a string of the relevant packages' name and version on each line.
77 # 2. Sorting according to those strings.
78 # 3. Removing those prepended strings.
79 # 4. Using `diff` against the expected output.
80 with subtest("apparmorRulesFromClosure"):
81 machine.succeed(
82 "${getExe' pkgs.diffutils "diff"} -u ${
83 pkgs.writeText "expected.rules" (import ./makeExpectedPolicies.nix { inherit pkgs; })
84 } ${
85 pkgs.runCommand "actual.rules" { preferLocalBuild = true; } ''
86 ${getExe pkgs.gnused} -e 's:^[^ ]* ${builtins.storeDir}/[^,/-]*-\([^/,]*\):\1 \0:' ${
87 pkgs.apparmorRulesFromClosure {
88 name = "ping";
89 additionalRules = [ "x $path/foo/**" ];
90 } [ pkgs.libcap ]
91 } |
92 ${getExe' pkgs.coreutils "sort"} -n -k1 |
93 ${getExe pkgs.gnused} -e 's:^[^ ]* ::' >$out
94 ''
95 }"
96 )
97
98 # Test apparmor profile states by using `diff` against `aa-status`
99 with subtest("apparmorProfileStates"):
100 machine.succeed("${getExe' pkgs.diffutils "diff"} -u \
101 <(${getExe' pkgs.apparmor-bin-utils "aa-status"} --json | ${getExe pkgs.jq} --sort-keys . ) \
102 <(${getExe pkgs.jq} --sort-keys . ${
103 pkgs.writers.writeJSON "expectedStates.json" {
104 version = "2";
105 processes = { };
106 profiles = {
107 hexdump = "enforce";
108 hello = "enforce";
109 sl = "complain";
110 };
111 }
112 })")
113
114 # Test apparmor profile files in /etc/apparmor.d/<name> to be either a correct symlink (sl) or have the right file contents (hello)
115 with subtest("apparmorProfileTargets"):
116 machine.succeed("${getExe' pkgs.diffutils "diff"} -u <(${getExe pkgs.file} /etc/static/apparmor.d/sl) ${pkgs.writeText "expected.link" ''
117 /etc/static/apparmor.d/sl: symbolic link to ${./sl_profile}
118 ''}")
119 machine.succeed("${getExe' pkgs.diffutils "diff"} -u /etc/static/apparmor.d/hello ${pkgs.writeText "expected.content" helloProfileContents}")
120
121
122 with subtest("apparmorProfileEnforce"):
123 machine.succeed("${getExe pkgs.hello} 1> /tmp/test-file")
124 machine.fail("${lib.getExe' pkgs.util-linux "hexdump"} /tmp/test-file") # no access to /tmp/test-file granted by apparmor
125 '';
126}