at 22.05-pre 6.2 kB view raw
1import ./make-test-python.nix { 2 name = "systemd-confinement"; 3 4 machine = { pkgs, lib, ... }: let 5 testServer = pkgs.writeScript "testserver.sh" '' 6 #!${pkgs.runtimeShell} 7 export PATH=${lib.escapeShellArg "${pkgs.coreutils}/bin"} 8 ${lib.escapeShellArg pkgs.runtimeShell} 2>&1 9 echo "exit-status:$?" 10 ''; 11 12 testClient = pkgs.writeScriptBin "chroot-exec" '' 13 #!${pkgs.runtimeShell} -e 14 output="$(echo "$@" | nc -NU "/run/test$(< /teststep).sock")" 15 ret="$(echo "$output" | sed -nre '$s/^exit-status:([0-9]+)$/\1/p')" 16 echo "$output" | head -n -1 17 exit "''${ret:-1}" 18 ''; 19 20 mkTestStep = num: { config ? {}, testScript }: { 21 systemd.sockets."test${toString num}" = { 22 description = "Socket for Test Service ${toString num}"; 23 wantedBy = [ "sockets.target" ]; 24 socketConfig.ListenStream = "/run/test${toString num}.sock"; 25 socketConfig.Accept = true; 26 }; 27 28 systemd.services."test${toString num}@" = { 29 description = "Confined Test Service ${toString num}"; 30 confinement = (config.confinement or {}) // { enable = true; }; 31 serviceConfig = (config.serviceConfig or {}) // { 32 ExecStart = testServer; 33 StandardInput = "socket"; 34 }; 35 } // removeAttrs config [ "confinement" "serviceConfig" ]; 36 37 __testSteps = lib.mkOrder num ('' 38 machine.succeed("echo ${toString num} > /teststep") 39 '' + testScript); 40 }; 41 42 in { 43 imports = lib.imap1 mkTestStep [ 44 { config.confinement.mode = "chroot-only"; 45 testScript = '' 46 with subtest("chroot-only confinement"): 47 paths = machine.succeed('chroot-exec ls -1 / | paste -sd,').strip() 48 assert_eq(paths, "bin,nix,run") 49 uid = machine.succeed('chroot-exec id -u').strip() 50 assert_eq(uid, "0") 51 machine.succeed("chroot-exec chown 65534 /bin") 52 ''; 53 } 54 { testScript = '' 55 with subtest("full confinement with APIVFS"): 56 machine.fail("chroot-exec ls -l /etc") 57 machine.fail("chroot-exec chown 65534 /bin") 58 assert_eq(machine.succeed('chroot-exec id -u').strip(), "0") 59 machine.succeed("chroot-exec chown 0 /bin") 60 ''; 61 } 62 { config.serviceConfig.BindReadOnlyPaths = [ "/etc" ]; 63 testScript = '' 64 with subtest("check existence of bind-mounted /etc"): 65 passwd = machine.succeed('chroot-exec cat /etc/passwd').strip() 66 assert len(passwd) > 0, "/etc/passwd must not be empty" 67 ''; 68 } 69 { config.serviceConfig.User = "chroot-testuser"; 70 config.serviceConfig.Group = "chroot-testgroup"; 71 testScript = '' 72 with subtest("check if User/Group really runs as non-root"): 73 machine.succeed("chroot-exec ls -l /dev") 74 uid = machine.succeed('chroot-exec id -u').strip() 75 assert uid != "0", "UID of chroot-testuser shouldn't be 0" 76 machine.fail("chroot-exec touch /bin/test") 77 ''; 78 } 79 (let 80 symlink = pkgs.runCommand "symlink" { 81 target = pkgs.writeText "symlink-target" "got me\n"; 82 } "ln -s \"$target\" \"$out\""; 83 in { 84 config.confinement.packages = lib.singleton symlink; 85 testScript = '' 86 with subtest("check if symlinks are properly bind-mounted"): 87 machine.fail("chroot-exec test -e /etc") 88 text = machine.succeed('chroot-exec cat ${symlink}').strip() 89 assert_eq(text, "got me") 90 ''; 91 }) 92 { config.serviceConfig.User = "chroot-testuser"; 93 config.serviceConfig.Group = "chroot-testgroup"; 94 config.serviceConfig.StateDirectory = "testme"; 95 testScript = '' 96 with subtest("check if StateDirectory works"): 97 machine.succeed("chroot-exec touch /tmp/canary") 98 machine.succeed('chroot-exec "echo works > /var/lib/testme/foo"') 99 machine.succeed('test "$(< /var/lib/testme/foo)" = works') 100 machine.succeed("test ! -e /tmp/canary") 101 ''; 102 } 103 { testScript = '' 104 with subtest("check if /bin/sh works"): 105 machine.succeed( 106 "chroot-exec test -e /bin/sh", 107 'test "$(chroot-exec \'/bin/sh -c "echo bar"\')" = bar', 108 ) 109 ''; 110 } 111 { config.confinement.binSh = null; 112 testScript = '' 113 with subtest("check if suppressing /bin/sh works"): 114 machine.succeed("chroot-exec test ! -e /bin/sh") 115 machine.succeed('test "$(chroot-exec \'/bin/sh -c "echo foo"\')" != foo') 116 ''; 117 } 118 { config.confinement.binSh = "${pkgs.hello}/bin/hello"; 119 testScript = '' 120 with subtest("check if we can set /bin/sh to something different"): 121 machine.succeed("chroot-exec test -e /bin/sh") 122 machine.succeed('test "$(chroot-exec /bin/sh -g foo)" = foo') 123 ''; 124 } 125 { config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n"; 126 testScript = '' 127 with subtest("check if only Exec* dependencies are included"): 128 machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" != eek') 129 ''; 130 } 131 { config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n"; 132 config.confinement.fullUnit = true; 133 testScript = '' 134 with subtest("check if all unit dependencies are included"): 135 machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" = eek') 136 ''; 137 } 138 ]; 139 140 options.__testSteps = lib.mkOption { 141 type = lib.types.lines; 142 description = "All of the test steps combined as a single script."; 143 }; 144 145 config.environment.systemPackages = lib.singleton testClient; 146 147 config.users.groups.chroot-testgroup = {}; 148 config.users.users.chroot-testuser = { 149 isSystemUser = true; 150 description = "Chroot Test User"; 151 group = "chroot-testgroup"; 152 }; 153 }; 154 155 testScript = { nodes, ... }: '' 156 def assert_eq(a, b): 157 assert a == b, f"{a} != {b}" 158 159 machine.wait_for_unit("multi-user.target") 160 '' + nodes.machine.config.__testSteps; 161}