···
serviceName ? "test${toString num}",
···
} // removeAttrs config [ "confinement" "serviceConfig" ];
41
-
__testSteps = lib.mkOrder num (''
42
-
machine.succeed("echo ${toString num} > /teststep")
42
+
__testSteps = lib.mkOrder num ''
43
+
with subtest('${lib.escape ["'" "\\"] description}'):
44
+
machine.succeed("echo ${toString num} > /teststep")
45
+
${lib.replaceStrings ["\n"] ["\n "] testScript}
imports = lib.imap1 mkTestStep [
48
-
{ config.confinement.mode = "chroot-only";
51
+
{ description = "chroot-only confinement";
52
+
config.confinement.mode = "chroot-only";
50
-
with subtest("chroot-only confinement"):
51
-
# chroot-exec starts a socket-activated service,
52
-
# but, upon starting, a systemd system service
53
-
# calls setup_namespace() which calls base_filesystem_create()
54
-
# which creates some usual top level directories.
55
-
# In chroot-only mode, without additional BindPaths= or the like,
56
-
# they must be empty and thus removable by rmdir.
57
-
paths = machine.succeed('chroot-exec rmdir /dev /etc /proc /root /sys /usr /var "&&" ls -Am /').strip()
58
-
assert_eq(paths, "bin, nix, run")
59
-
uid = machine.succeed('chroot-exec id -u').strip()
61
-
machine.succeed("chroot-exec chown 65534 /bin")
54
+
# chroot-exec starts a socket-activated service,
55
+
# but, upon starting, a systemd system service
56
+
# calls setup_namespace() which calls base_filesystem_create()
57
+
# which creates some usual top level directories.
58
+
# In chroot-only mode, without additional BindPaths= or the like,
59
+
# they must be empty and thus removable by rmdir.
60
+
paths = machine.succeed('chroot-exec rmdir /dev /etc /proc /root /sys /usr /var "&&" ls -Am /').strip()
61
+
assert_eq(paths, "bin, nix, run")
62
+
uid = machine.succeed('chroot-exec id -u').strip()
64
+
machine.succeed("chroot-exec chown 65534 /bin")
65
-
with subtest("full confinement with APIVFS"):
66
-
machine.succeed('chroot-exec rmdir /etc')
67
-
machine.fail("chroot-exec chown 65534 /bin")
68
-
assert_eq(machine.succeed('chroot-exec id -u').strip(), "0")
69
-
machine.succeed("chroot-exec chown 0 /bin")
67
+
{ description = "full confinement with APIVFS";
69
+
machine.succeed('chroot-exec rmdir /etc')
70
+
machine.fail("chroot-exec chown 65534 /bin")
71
+
assert_eq(machine.succeed('chroot-exec id -u').strip(), "0")
72
+
machine.succeed("chroot-exec chown 0 /bin")
72
-
{ config.serviceConfig.BindReadOnlyPaths = [ "/etc" ];
75
+
{ description = "check existence of bind-mounted /etc";
76
+
config.serviceConfig.BindReadOnlyPaths = [ "/etc" ];
74
-
with subtest("check existence of bind-mounted /etc"):
75
-
passwd = machine.succeed('chroot-exec cat /etc/passwd').strip()
76
-
assert len(passwd) > 0, "/etc/passwd must not be empty"
78
+
passwd = machine.succeed('chroot-exec cat /etc/passwd').strip()
79
+
assert len(passwd) > 0, "/etc/passwd must not be empty"
79
-
{ config.serviceConfig.User = "chroot-testuser";
82
+
{ description = "check if User/Group really runs as non-root";
83
+
config.serviceConfig.User = "chroot-testuser";
config.serviceConfig.Group = "chroot-testgroup";
82
-
with subtest("check if User/Group really runs as non-root"):
83
-
machine.succeed("chroot-exec ls -l /dev")
84
-
uid = machine.succeed('chroot-exec id -u').strip()
85
-
assert uid != "0", "UID of chroot-testuser shouldn't be 0"
86
-
machine.fail("chroot-exec touch /bin/test")
86
+
machine.succeed("chroot-exec ls -l /dev")
87
+
uid = machine.succeed('chroot-exec id -u').strip()
88
+
assert uid != "0", "UID of chroot-testuser shouldn't be 0"
89
+
machine.fail("chroot-exec touch /bin/test")
89
-
{ config.confinement.mode = "full-apivfs";
92
+
{ description = "check if DynamicUser is working in full-apivfs mode";
93
+
config.confinement.mode = "full-apivfs";
config.serviceConfig.DynamicUser = true;
92
-
with subtest("check if DynamicUser is working in full-apivfs mode"):
93
-
machine.succeed("chroot-exec ls -l /dev")
94
-
paths = machine.succeed('chroot-exec find / -path /dev/"\\*" -prune -o -path /nix/"\\*" -prune -o -path /proc/"\\*" -prune -o -path /sys/"\\*" -prune -o -print || test $? = 1')
96
-
'\n'.join(sorted(paths.split('\n'))),
108
-
/run/host/.os-release-stage
109
-
/run/host/.os-release-stage/os-release
110
-
/run/host/os-release
112
-
/run/systemd/incoming
118
-
find: '/root': Permission denied
119
-
find: '/run/systemd/incoming': Permission denied"""
121
-
uid = machine.succeed('chroot-exec id -u').strip()
122
-
assert uid != "0", "UID of a DynamicUser shouldn't be 0"
123
-
machine.fail("chroot-exec touch /bin/test")
124
-
# DynamicUser=true implies ProtectSystem=strict
125
-
machine.fail("chroot-exec touch /etc/test")
96
+
machine.succeed("chroot-exec ls -l /dev")
97
+
paths = machine.succeed('chroot-exec find / -path /dev/"\\*" -prune -o -path /nix/"\\*" -prune -o -path /proc/"\\*" -prune -o -path /sys/"\\*" -prune -o -print || test $? = 1')
99
+
'\n'.join(sorted(paths.split('\n'))),
111
+
/run/host/.os-release-stage
112
+
/run/host/.os-release-stage/os-release
113
+
/run/host/os-release
115
+
/run/systemd/incoming
121
+
find: '/root': Permission denied
122
+
find: '/run/systemd/incoming': Permission denied
125
+
uid = machine.succeed('chroot-exec id -u').strip()
126
+
assert uid != "0", "UID of a DynamicUser shouldn't be 0"
127
+
machine.fail("chroot-exec touch /bin/test")
128
+
# DynamicUser=true implies ProtectSystem=strict
129
+
machine.fail("chroot-exec touch /etc/test")
128
-
{ config.confinement.mode = "full-apivfs";
132
+
{ description = "check if DynamicUser and PrivateTmp=false are working in full-apivfs mode";
133
+
config.confinement.mode = "full-apivfs";
config.serviceConfig.DynamicUser = true;
config.serviceConfig.PrivateTmp = false;
132
-
with subtest("check if DynamicUser and PrivateTmp=false are working in full-apivfs mode"):
133
-
machine.succeed("chroot-exec ls -l /dev")
134
-
paths = machine.succeed('chroot-exec find / -path /dev/"\\*" -prune -o -path /nix/"\\*" -prune -o -path /proc/"\\*" -prune -o -path /sys/"\\*" -prune -o -print || test $? = 1')
136
-
'\n'.join(sorted(paths.split('\n'))),
148
-
/run/host/.os-release-stage
149
-
/run/host/.os-release-stage/os-release
150
-
/run/host/os-release
152
-
/run/systemd/incoming
156
-
find: '/root': Permission denied
157
-
find: '/run/systemd/incoming': Permission denied"""
159
-
uid = machine.succeed('chroot-exec id -u').strip()
160
-
assert uid != "0", "UID of a DynamicUser shouldn't be 0"
161
-
machine.fail("chroot-exec touch /bin/test")
162
-
# DynamicUser=true implies ProtectSystem=strict
163
-
machine.fail("chroot-exec touch /etc/test")
137
+
machine.succeed("chroot-exec ls -l /dev")
138
+
paths = machine.succeed('chroot-exec find / -path /dev/"\\*" -prune -o -path /nix/"\\*" -prune -o -path /proc/"\\*" -prune -o -path /sys/"\\*" -prune -o -print || test $? = 1')
140
+
'\n'.join(sorted(paths.split('\n'))),
152
+
/run/host/.os-release-stage
153
+
/run/host/.os-release-stage/os-release
154
+
/run/host/os-release
156
+
/run/systemd/incoming
160
+
find: '/root': Permission denied
161
+
find: '/run/systemd/incoming': Permission denied
164
+
uid = machine.succeed('chroot-exec id -u').strip()
165
+
assert uid != "0", "UID of a DynamicUser shouldn't be 0"
166
+
machine.fail("chroot-exec touch /bin/test")
167
+
# DynamicUser=true implies ProtectSystem=strict
168
+
machine.fail("chroot-exec touch /etc/test")
166
-
{ config.confinement.mode = "chroot-only";
171
+
{ description = "check if DynamicUser is working in chroot-only mode";
172
+
config.confinement.mode = "chroot-only";
config.serviceConfig.DynamicUser = true;
169
-
with subtest("check if DynamicUser is working in chroot-only mode"):
170
-
paths = machine.succeed('chroot-exec find / -path /nix/"\\*" -prune -o -print || test $? = 1')
172
-
'\n'.join(sorted(paths.split('\n'))),
184
-
/run/systemd/incoming
188
-
find: '/root': Permission denied
189
-
find: '/run/systemd/incoming': Permission denied"""
191
-
uid = machine.succeed('chroot-exec id -u').strip()
192
-
assert uid != "0", "UID of a DynamicUser shouldn't be 0"
193
-
machine.fail("chroot-exec touch /bin/test")
175
+
paths = machine.succeed('chroot-exec find / -path /nix/"\\*" -prune -o -print || test $? = 1')
177
+
'\n'.join(sorted(paths.split('\n'))),
189
+
/run/systemd/incoming
193
+
find: '/root': Permission denied
194
+
find: '/run/systemd/incoming': Permission denied
197
+
uid = machine.succeed('chroot-exec id -u').strip()
198
+
assert uid != "0", "UID of a DynamicUser shouldn't be 0"
199
+
machine.fail("chroot-exec touch /bin/test")
196
-
{ config.confinement.mode = "chroot-only";
202
+
{ description = "check if DynamicUser and PrivateTmp=true are working in chroot-only mode";
203
+
config.confinement.mode = "chroot-only";
config.serviceConfig.DynamicUser = true;
config.serviceConfig.PrivateTmp = true;
200
-
with subtest("check if DynamicUser and PrivateTmp=true are working in chroot-only mode"):
201
-
paths = machine.succeed('chroot-exec find / -path /nix/"\\*" -prune -o -print || test $? = 1')
203
-
'\n'.join(sorted(paths.split('\n'))),
215
-
/run/systemd/incoming
221
-
find: '/root': Permission denied
222
-
find: '/run/systemd/incoming': Permission denied"""
224
-
uid = machine.succeed('chroot-exec id -u').strip()
225
-
assert uid != "0", "UID of a DynamicUser shouldn't be 0"
226
-
machine.fail("chroot-exec touch /bin/test")
207
+
paths = machine.succeed('chroot-exec find / -path /nix/"\\*" -prune -o -print || test $? = 1')
209
+
'\n'.join(sorted(paths.split('\n'))),
221
+
/run/systemd/incoming
227
+
find: '/root': Permission denied
228
+
find: '/run/systemd/incoming': Permission denied
231
+
uid = machine.succeed('chroot-exec id -u').strip()
232
+
assert uid != "0", "UID of a DynamicUser shouldn't be 0"
233
+
machine.fail("chroot-exec touch /bin/test")
···
target = pkgs.writeText "symlink-target" "got me\n";
} "ln -s \"$target\" \"$out\"";
241
+
description = "check if symlinks are properly bind-mounted";
config.confinement.packages = lib.singleton symlink;
236
-
with subtest("check if symlinks are properly bind-mounted"):
237
-
machine.succeed("chroot-exec rmdir /etc")
238
-
text = machine.succeed('chroot-exec cat ${symlink}').strip()
239
-
assert_eq(text, "got me")
244
+
machine.succeed("chroot-exec rmdir /etc")
245
+
text = machine.succeed('chroot-exec cat ${symlink}').strip()
246
+
assert_eq(text, "got me")
242
-
{ config.serviceConfig.User = "chroot-testuser";
249
+
{ description = "check if StateDirectory works";
250
+
config.serviceConfig.User = "chroot-testuser";
config.serviceConfig.Group = "chroot-testgroup";
config.serviceConfig.StateDirectory = "testme";
246
-
with subtest("check if StateDirectory works"):
247
-
machine.succeed("chroot-exec touch /tmp/canary")
248
-
machine.succeed('chroot-exec "echo works > /var/lib/testme/foo"')
249
-
machine.succeed('test "$(< /var/lib/testme/foo)" = works')
250
-
machine.succeed("test ! -e /tmp/canary")
254
+
machine.succeed("chroot-exec touch /tmp/canary")
255
+
machine.succeed('chroot-exec "echo works > /var/lib/testme/foo"')
256
+
machine.succeed('test "$(< /var/lib/testme/foo)" = works')
257
+
machine.succeed("test ! -e /tmp/canary")
254
-
with subtest("check if /bin/sh works"):
256
-
"chroot-exec test -e /bin/sh",
257
-
'test "$(chroot-exec \'/bin/sh -c "echo bar"\')" = bar',
260
+
{ description = "check if /bin/sh works";
263
+
"chroot-exec test -e /bin/sh",
264
+
'test "$(chroot-exec \'/bin/sh -c "echo bar"\')" = bar',
261
-
{ config.confinement.binSh = null;
268
+
{ description = "check if suppressing /bin/sh works";
269
+
config.confinement.binSh = null;
263
-
with subtest("check if suppressing /bin/sh works"):
264
-
machine.succeed("chroot-exec test ! -e /bin/sh")
265
-
machine.succeed('test "$(chroot-exec \'/bin/sh -c "echo foo"\')" != foo')
272
+
'chroot-exec test ! -e /bin/sh',
273
+
'test "$(chroot-exec \'/bin/sh -c "echo foo"\')" != foo',
268
-
{ config.confinement.binSh = "${pkgs.hello}/bin/hello";
277
+
{ description = "check if we can set /bin/sh to something different";
278
+
config.confinement.binSh = "${pkgs.hello}/bin/hello";
270
-
with subtest("check if we can set /bin/sh to something different"):
271
-
machine.succeed("chroot-exec test -e /bin/sh")
272
-
machine.succeed('test "$(chroot-exec /bin/sh -g foo)" = foo')
280
+
machine.succeed("chroot-exec test -e /bin/sh")
281
+
machine.succeed('test "$(chroot-exec /bin/sh -g foo)" = foo')
275
-
{ config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n";
284
+
{ description = "check if only Exec* dependencies are included";
285
+
config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n";
277
-
with subtest("check if only Exec* dependencies are included"):
278
-
machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" != eek')
287
+
machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" != eek')
281
-
{ config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n";
290
+
{ description = "check if all unit dependencies are included";
291
+
config.environment.FOOBAR = pkgs.writeText "foobar" "eek\n";
config.confinement.fullUnit = true;
284
-
with subtest("check if all unit dependencies are included"):
285
-
machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" = eek')
294
+
machine.succeed('test "$(chroot-exec \'cat "$FOOBAR"\')" = eek')
288
-
{ serviceName = "shipped-unitfile";
297
+
{ description = "check if shipped unit file still works";
298
+
serviceName = "shipped-unitfile";
config.confinement.mode = "chroot-only";
291
-
with subtest("check if shipped unit file still works"):
293
-
'chroot-exec \'kill -9 $$ 2>&1 || :\' | '
294
-
'grep -q "Too many levels of symbolic links"'
302
+
'chroot-exec \'kill -9 $$ 2>&1 || :\' | '
303
+
'grep -q "Too many levels of symbolic links"'
···
testScript = { nodes, ... }: ''
334
+
from textwrap import dedent
def assert_eq(got, expected):
327
-
if got != expected:
328
-
diff = difflib.unified_diff(got.splitlines(keepends=True), expected.splitlines(keepends=True))
329
-
print("".join(diff))
330
-
assert got == expected, f"{got} != {expected}"
338
+
if got != expected:
339
+
diff = difflib.unified_diff(got.splitlines(keepends=True), expected.splitlines(keepends=True))
340
+
print("".join(diff))
341
+
assert got == expected, f"{got} != {expected}"
machine.wait_for_unit("multi-user.target")
'' + nodes.machine.__testSteps;