···
} // removeAttrs config [ "confinement" "serviceConfig" ];
67
-
imports = lib.imap1 mkTestStep [
68
-
{ description = "chroot-only confinement";
69
-
config.confinement.mode = "chroot-only";
66
+
parametrisedTests = lib.concatMap ({ user, privateTmp }: let
67
+
withTmp = if privateTmp then "with PrivateTmp" else "without PrivateTmp";
69
+
serviceConfig = if user == "static-user" then {
70
+
User = "chroot-testuser";
71
+
Group = "chroot-testgroup";
72
+
} else if user == "dynamic-user" then {
77
+
{ description = "${user}, chroot-only confinement ${withTmp}";
79
+
confinement.mode = "chroot-only";
80
+
# Only set if privateTmp is true to ensure that the default is false.
81
+
serviceConfig = serviceConfig // lib.optionalAttrs privateTmp {
85
+
testScript = if user == "root" then ''
86
+
assert os.getuid() == 0
87
+
assert os.getgid() == 0
'bin': Accessibility.WRITABLE,
'nix': Accessibility.WRITABLE,
'run': Accessibility.WRITABLE,
93
+
${lib.optionalString privateTmp "'tmp': Accessibility.STICKY,"}
94
+
${lib.optionalString privateTmp "'var': Accessibility.WRITABLE,"}
95
+
${lib.optionalString privateTmp "'var/tmp': Accessibility.STICKY,"}
98
+
assert os.getuid() != 0
99
+
assert os.getgid() != 0
77
-
assert os.getuid() == 0
78
-
os.chown('/bin', 65534, 0)
101
+
assert_permissions({
102
+
'bin': Accessibility.READABLE,
103
+
'nix': Accessibility.READABLE,
104
+
'run': Accessibility.READABLE,
105
+
${lib.optionalString privateTmp "'tmp': Accessibility.STICKY,"}
106
+
${lib.optionalString privateTmp "'var': Accessibility.READABLE,"}
107
+
${lib.optionalString privateTmp "'var/tmp': Accessibility.STICKY,"}
81
-
{ description = "full confinement with APIVFS";
83
-
Path('/etc').rmdir()
111
+
{ description = "${user}, full APIVFS confinement ${withTmp}";
113
+
# Only set if privateTmp is false to ensure that the default is true.
114
+
serviceConfig = serviceConfig // lib.optionalAttrs (!privateTmp) {
115
+
PrivateTmp = false;
118
+
testScript = if user == "root" then ''
119
+
assert os.getuid() == 0
120
+
assert os.getgid() == 0
'bin': Accessibility.WRITABLE,
'nix': Accessibility.WRITABLE,
88
-
'tmp': Accessibility.WRITABLE,
125
+
${lib.optionalString privateTmp "'tmp': Accessibility.STICKY,"}
'run': Accessibility.WRITABLE,
'proc': Accessibility.SPECIAL,
'sys': Accessibility.SPECIAL,
'dev': Accessibility.WRITABLE,
96
-
bin_gid = Path('/bin').stat().st_gid
97
-
with pytest.raises(OSError) as excinfo:
98
-
os.chown('/bin', 65534, bin_gid)
99
-
assert excinfo.value.errno == errno.EINVAL
101
-
assert os.getuid() == 0
102
-
os.chown('/bin', 0, 0)
105
-
{ description = "check existence of bind-mounted /etc";
106
-
config.serviceConfig.BindReadOnlyPaths = [ "/etc" ];
108
-
assert Path('/etc/passwd').read_text()
111
-
{ description = "check if User/Group really runs as non-root";
112
-
config.serviceConfig.User = "chroot-testuser";
113
-
config.serviceConfig.Group = "chroot-testgroup";
115
-
assert list(Path('/dev').iterdir())
132
+
${lib.optionalString privateTmp "'var': Accessibility.WRITABLE,"}
133
+
${lib.optionalString privateTmp "'var/tmp': Accessibility.STICKY,"}
120
-
with pytest.raises(PermissionError):
121
-
Path('/bin/test').touch()
124
-
{ description = "check if DynamicUser is working in full-apivfs mode";
125
-
config.confinement.mode = "full-apivfs";
126
-
config.serviceConfig.DynamicUser = true;
'bin': Accessibility.READABLE,
'nix': Accessibility.READABLE,
131
-
'tmp': Accessibility.WRITABLE,
142
+
${lib.optionalString privateTmp "'tmp': Accessibility.STICKY,"}
'run': Accessibility.STICKY,
'proc': Accessibility.SPECIAL,
···
'dev/shm': Accessibility.STICKY,
'dev/mqueue': Accessibility.STICKY,
141
-
'var': Accessibility.READABLE,
142
-
'var/tmp': Accessibility.WRITABLE,
152
+
${lib.optionalString privateTmp "'var': Accessibility.READABLE,"}
153
+
${lib.optionalString privateTmp "'var/tmp': Accessibility.STICKY,"}
145
-
assert os.getuid() != 0
146
-
assert os.getgid() != 0
148
-
with pytest.raises(OSError) as excinfo:
149
-
Path('/bin/test').touch()
150
-
assert excinfo.value.errno == errno.EROFS
152
-
with pytest.raises(OSError) as excinfo:
153
-
Path('/etc/test').touch()
154
-
assert excinfo.value.errno == errno.EROFS
157
-
{ description = "check if DynamicUser and PrivateTmp=false are working in full-apivfs mode";
158
-
config.confinement.mode = "full-apivfs";
159
-
config.serviceConfig.DynamicUser = true;
160
-
config.serviceConfig.PrivateTmp = false;
162
-
assert_permissions({
163
-
'bin': Accessibility.READABLE,
164
-
'nix': Accessibility.READABLE,
165
-
'run': Accessibility.STICKY,
167
-
'proc': Accessibility.SPECIAL,
168
-
'sys': Accessibility.SPECIAL,
170
-
'dev': Accessibility.SPECIAL,
171
-
'dev/shm': Accessibility.STICKY,
172
-
'dev/mqueue': Accessibility.STICKY,
175
-
assert os.getuid() != 0
176
-
assert os.getgid() != 0
178
-
with pytest.raises(OSError) as excinfo:
179
-
Path('/bin/test').touch()
180
-
assert excinfo.value.errno == errno.EROFS
182
-
with pytest.raises(OSError) as excinfo:
183
-
Path('/etc/test').touch()
184
-
assert excinfo.value.errno == errno.EROFS
187
-
{ description = "check if DynamicUser is working in chroot-only mode";
188
-
config.confinement.mode = "chroot-only";
189
-
config.serviceConfig.DynamicUser = true;
191
-
assert_permissions({
192
-
'bin': Accessibility.READABLE,
193
-
'nix': Accessibility.READABLE,
194
-
'run': Accessibility.READABLE,
157
+
]) (lib.cartesianProductOfSets {
158
+
user = [ "root" "dynamic-user" "static-user" ];
159
+
privateTmp = [ true false ];
197
-
assert os.getuid() != 0
198
-
assert os.getgid() != 0
200
-
with pytest.raises(OSError) as excinfo:
201
-
Path('/bin/test').touch()
202
-
assert excinfo.value.errno == errno.EROFS
205
-
{ description = "check if DynamicUser and PrivateTmp=true are working in chroot-only mode";
206
-
config.confinement.mode = "chroot-only";
207
-
config.serviceConfig.DynamicUser = true;
208
-
config.serviceConfig.PrivateTmp = true;
163
+
imports = lib.imap1 mkTestStep (parametrisedTests ++ [
164
+
{ description = "existence of bind-mounted /etc";
165
+
config.serviceConfig.BindReadOnlyPaths = [ "/etc" ];
210
-
assert_permissions({
211
-
'bin': Accessibility.READABLE,
212
-
'nix': Accessibility.READABLE,
213
-
'run': Accessibility.READABLE,
214
-
'tmp': Accessibility.WRITABLE,
216
-
'var': Accessibility.READABLE,
217
-
'var/tmp': Accessibility.WRITABLE,
220
-
assert os.getuid() != 0
221
-
assert os.getgid() != 0
223
-
with pytest.raises(OSError) as excinfo:
224
-
Path('/bin/test').touch()
225
-
assert excinfo.value.errno == errno.EROFS
167
+
assert Path('/etc/passwd').read_text()
···
description = "check if symlinks are properly bind-mounted";
config.confinement.packages = lib.singleton symlink;
236
-
Path('/etc').rmdir()
assert Path('${symlink}').read_text() == 'got me'
···
assert excinfo.value.errno == errno.ELOOP
config.users.groups.chroot-testgroup = {};
config.users.users.chroot-testuser = {