]> git.ipfire.org Git - thirdparty/systemd.git/blob - test/units/testsuite-13.nspawn-oci.sh
Merge pull request #27670 from poettering/switch-root-umount-all
[thirdparty/systemd.git] / test / units / testsuite-13.nspawn-oci.sh
1 #!/usr/bin/env bash
2 # SPDX-License-Identifier: LGPL-2.1-or-later
3 # shellcheck disable=SC2016
4 set -eux
5 set -o pipefail
6
7 # shellcheck source=test/units/util.sh
8 . "$(dirname "$0")"/util.sh
9
10 export SYSTEMD_LOG_LEVEL=debug
11 export SYSTEMD_LOG_TARGET=journal
12
13 # shellcheck disable=SC2317
14 at_exit() {
15 set +e
16
17 mountpoint -q /var/lib/machines && umount /var/lib/machines
18 [[ -n "${DEV:-}" ]] && rm -f "$DEV"
19 [[ -n "${NETNS:-}" ]] && umount "$NETNS" && rm -f "$NETNS"
20 [[ -n "${TMPDIR:-}" ]] && rm -fr "$TMPDIR"
21 rm -f /run/systemd/nspawn/*.nspawn
22 }
23
24 trap at_exit EXIT
25
26 # Mount tmpfs over /var/lib/machines to not pollute the image
27 mkdir -p /var/lib/machines
28 mount -t tmpfs tmpfs /var/lib/machines
29
30 # Setup a couple of dirs/devices for the OCI containers
31 DEV="$(mktemp -u /dev/oci-dev-XXX)"
32 mknod -m 666 "$DEV" b 42 42
33 NETNS="$(mktemp /var/tmp/netns.XXX)"
34 mount --bind /proc/self/ns/net "$NETNS"
35 TMPDIR="$(mktemp -d)"
36 touch "$TMPDIR/hello"
37 OCI="$(mktemp -d /var/lib/machines/testsuite-13.oci-bundle.XXX)"
38 create_dummy_container "$OCI/rootfs"
39 mkdir -p "$OCI/rootfs/opt/var"
40 mkdir -p "$OCI/rootfs/opt/readonly"
41
42 # Let's start with a simple config
43 cat >"$OCI/config.json" <<EOF
44 {
45 "ociVersion" : "1.0.0",
46 "root" : {
47 "path" : "rootfs"
48 },
49 "mounts" : [
50 {
51 "destination" : "/root",
52 "type" : "tmpfs",
53 "source" : "tmpfs"
54 }
55 ]
56 }
57 EOF
58 systemd-nspawn --oci-bundle="$OCI" bash -xec 'mountpoint /root'
59
60 # And now for something a bit more involved
61 # Notes:
62 # - the hooks are parsed & processed, but never executed
63 # - set sysctl's are parsed but never used?
64 # - same goes for arg_sysctl in nspawn.c
65 cat >"$OCI/config.json" <<EOF
66 {
67 "ociVersion" : "1.0.0",
68 "hostname" : "my-oci-container",
69 "root" : {
70 "path" : "rootfs",
71 "readonly" : false
72 },
73 "mounts" : [
74 {
75 "destination" : "/root",
76 "type" : "tmpfs",
77 "source" : "tmpfs"
78 },
79 {
80 "destination" : "/var",
81 "type" : "none",
82 "source" : "$TMPDIR",
83 "options" : ["rbind", "rw"]
84 }
85 ],
86 "process" : {
87 "terminal" : false,
88 "consoleSize" : {
89 "height" : 25,
90 "width" : 80
91 },
92 "user" : {
93 "uid" : 0,
94 "gid" : 0,
95 "additionalGids" : [5, 6]
96 },
97 "env" : [
98 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
99 "FOO=bar"
100 ],
101 "cwd" : "/root",
102 "args" : [
103 "bash",
104 "-xe",
105 "/entrypoint.sh"
106 ],
107 "noNewPrivileges" : true,
108 "oomScoreAdj" : 20,
109 "capabilities" : {
110 "bounding" : [
111 "CAP_AUDIT_WRITE",
112 "CAP_KILL",
113 "CAP_NET_BIND_SERVICE"
114 ],
115 "permitted" : [
116 "CAP_AUDIT_WRITE",
117 "CAP_KILL",
118 "CAP_NET_BIND_SERVICE"
119 ],
120 "inheritable" : [
121 "CAP_AUDIT_WRITE",
122 "CAP_KILL",
123 "CAP_NET_BIND_SERVICE"
124 ],
125 "effective" : [
126 "CAP_AUDIT_WRITE",
127 "CAP_KILL"
128 ],
129 "ambient" : [
130 "CAP_NET_BIND_SERVICE"
131 ]
132 },
133 "rlimits" : [
134 {
135 "type" : "RLIMIT_NOFILE",
136 "soft" : 1024,
137 "hard" : 1024
138 },
139 {
140 "type" : "RLIMIT_RTPRIO",
141 "soft" : 5,
142 "hard" : 10
143 }
144 ]
145 },
146 "linux" : {
147 "namespaces" : [
148 {
149 "type" : "mount"
150 },
151 {
152 "type" : "network",
153 "path" : "$NETNS"
154 },
155 {
156 "type" : "pid"
157 },
158 {
159 "type" : "uts"
160 }
161 ],
162 "uidMappings" : [
163 {
164 "containerID" : 0,
165 "hostID" : 1000,
166 "size" : 100
167 }
168 ],
169 "gidMappings" : [
170 {
171 "containerID" : 0,
172 "hostID" : 1000,
173 "size" : 100
174 }
175 ],
176 "devices" : [
177 {
178 "type" : "c",
179 "path" : "/dev/zero",
180 "major" : 1,
181 "minor" : 5,
182 "fileMode" : 444
183 },
184 {
185 "type" : "b",
186 "path" : "$DEV",
187 "major" : 4,
188 "minor" : 2,
189 "fileMode" : 666,
190 "uid" : 0,
191 "gid" : 0
192 }
193 ],
194 "resources" : {
195 "devices" : [
196 {
197 "allow" : false,
198 "access" : "m"
199 },
200 {
201 "allow" : true,
202 "type" : "b",
203 "major" : 4,
204 "minor" : 2,
205 "access" : "rwm"
206 }
207 ],
208 "memory" : {
209 "limit" : 134217728,
210 "reservation" : 33554432,
211 "swap" : 268435456
212 },
213 "cpu" : {
214 "shares" : 1024,
215 "quota" : 1000000,
216 "period" : 500000,
217 "cpus" : "0-7"
218 },
219 "blockIO" : {
220 "weight" : 10,
221 "weightDevice" : [
222 {
223 "major" : 4,
224 "minor" : 2,
225 "weight" : 500
226 }
227 ],
228 "throttleReadBpsDevice" : [
229 {
230 "major" : 4,
231 "minor" : 2,
232 "rate" : 500
233 }
234 ],
235 "throttleWriteBpsDevice" : [
236 {
237 "major" : 4,
238 "minor" : 2,
239 "rate" : 500
240 }
241 ],
242 "throttleReadIOPSDevice" : [
243 {
244 "major" : 4,
245 "minor" : 2,
246 "rate" : 500
247 }
248 ],
249 "throttleWriteIOPSDevice" : [
250 {
251 "major" : 4,
252 "minor" : 2,
253 "rate" : 500
254 }
255 ]
256 },
257 "pids" : {
258 "limit" : 1024
259 }
260 },
261 "sysctl" : {
262 "kernel.domainname" : "foo.bar",
263 "vm.swappiness" : "60"
264 },
265 "seccomp" : {
266 "defaultAction" : "SCMP_ACT_ALLOW",
267 "architectures" : [
268 "SCMP_ARCH_ARM",
269 "SCMP_ARCH_X86_64"
270 ],
271 "syscalls" : [
272 {
273 "names" : [
274 "lchown",
275 "chmod"
276 ],
277 "action" : "SCMP_ACT_ERRNO",
278 "args" : [
279 {
280 "index" : 0,
281 "value" : 1,
282 "op" : "SCMP_CMP_NE"
283 },
284 {
285 "index" : 1,
286 "value" : 2,
287 "valueTwo" : 3,
288 "op" : "SCMP_CMP_MASKED_EQ"
289 }
290 ]
291 }
292 ]
293 },
294 "rootfsPropagation" : "shared",
295 "maskedPaths" : [
296 "/proc/kcore",
297 "/root/nonexistent"
298 ],
299 "readonlyPaths" : [
300 "/proc/sys",
301 "/opt/readonly"
302 ]
303 },
304 "hooks" : {
305 "prestart" : [
306 {
307 "path" : "/bin/sh",
308 "args" : [
309 "-xec",
310 "echo \$PRESTART_FOO >/prestart"
311 ],
312 "env" : [
313 "PRESTART_FOO=prestart_bar",
314 "ALSO_FOO=also_bar"
315 ],
316 "timeout" : 666
317 },
318 {
319 "path" : "/bin/touch",
320 "args" : [
321 "/tmp/also-prestart"
322 ]
323 }
324 ],
325 "poststart" : [
326 {
327 "path" : "/bin/sh",
328 "args" : [
329 "touch",
330 "/poststart"
331 ]
332 }
333 ],
334 "poststop" : [
335 {
336 "path" : "/bin/sh",
337 "args" : [
338 "touch",
339 "/poststop"
340 ]
341 }
342 ]
343 },
344 "annotations" : {
345 "hello.world" : "1",
346 "foo" : "bar"
347 }
348 }
349 EOF
350 # Create a simple "entrypoint" script that validates that the container
351 # is created correctly according to the OCI config
352 cat >"$OCI/rootfs/entrypoint.sh" <<EOF
353 #!/usr/bin/bash -e
354
355 # Mounts
356 mountpoint /root
357 mountpoint /var
358 test -e /var/hello
359
360 # Process
361 [[ "\$PWD" == /root ]]
362 [[ "\$FOO" == bar ]]
363
364 # Process - rlimits
365 [[ "\$(ulimit -S -n)" -eq 1024 ]]
366 [[ "\$(ulimit -H -n)" -eq 1024 ]]
367 [[ "\$(ulimit -S -r)" -eq 5 ]]
368 [[ "\$(ulimit -H -r)" -eq 10 ]]
369 [[ "\$(hostname)" == my-oci-container ]]
370
371 # Linux - devices
372 test -c /dev/zero
373 test -b "$DEV"
374 [[ "\$(stat -c '%t:%T' "$DEV")" == 4:2 ]]
375
376 # Linux - maskedPaths
377 test -e /proc/kcore
378 cat /proc/kcore && exit 1
379 test ! -e /root/nonexistent
380
381 # Linux - readonlyPaths
382 touch /opt/readonly/foo && exit 1
383
384 exit 0
385 EOF
386 timeout 30 systemd-nspawn --oci-bundle="$OCI"
387
388 # Test a couple of invalid configs
389 INVALID_SNIPPETS=(
390 # Invalid object
391 '"foo" : { }'
392 '"process" : { "foo" : [ ] }'
393 # Non-absolute mount
394 '"mounts" : [ { "destination" : "foo", "type" : "tmpfs", "source" : "tmpfs" } ]'
395 # Invalid rlimit
396 '"process" : { "rlimits" : [ { "type" : "RLIMIT_FOO", "soft" : 0, "hard" : 0 } ] }'
397 # rlimit without RLIMIT_ prefix
398 '"process" : { "rlimits" : [ { "type" : "CORE", "soft" : 0, "hard" : 0 } ] }'
399 # Invalid env assignment
400 '"process" : { "env" : [ "foo" ] }'
401 '"process" : { "env" : [ "foo=bar", 1 ] }'
402 # Invalid process args
403 '"process" : { "args" : [ ] }'
404 '"process" : { "args" : [ "" ] }'
405 '"process" : { "args" : [ "foo", 1 ] }'
406 # Invalid capabilities
407 '"process" : { "capabilities" : { "bounding" : [ 1 ] } }'
408 '"process" : { "capabilities" : { "bounding" : [ "FOO_BAR" ] } }'
409 # Unsupported option (without JSON_PERMISSIVE)
410 '"linux" : { "resources" : { "cpu" : { "realtimeRuntime" : 1 } } }'
411 # Invalid namespace
412 '"linux" : { "namespaces" : [ { "type" : "foo" } ] }'
413 # Namespace path for a non-network namespace
414 '"linux" : { "namespaces" : [ { "type" : "user", "path" : "/foo/bar" } ] }'
415 # Duplicate namespace
416 '"linux" : { "namespaces" : [ { "type" : "ipc" }, { "type" : "ipc" } ] }'
417 # Invalid device type
418 '"linux" : { "devices" : [ { "type" : "foo", "path" : "/dev/foo" } ] }'
419 # Invalid cgroups path
420 '"linux" : { "cgroupsPath" : "/foo/bar/baz" }'
421 '"linux" : { "cgroupsPath" : "foo/bar/baz" }'
422 # Invalid sysctl assignments
423 '"linux" : { "sysctl" : { "vm.swappiness" : 60 } }'
424 '"linux" : { "sysctl" : { "foo..bar" : "baz" } }'
425 # Invalid seccomp assignments
426 '"linux" : { "seccomp" : { } }'
427 '"linux" : { "seccomp" : { "defaultAction" : 1 } }'
428 '"linux" : { "seccomp" : { "defaultAction" : "foo" } }'
429 '"linux" : { "seccomp" : { "defaultAction" : "SCMP_ACT_ALLOW", "syscalls" : [ { "action" : "SCMP_ACT_ERRNO", "names" : [ ] } ] } }'
430 # Invalid masked paths
431 '"linux" : { "maskedPaths" : [ "/foo", 1 ] }'
432 '"linux" : { "maskedPaths" : [ "/foo", "bar" ] }'
433 # Invalid read-only paths
434 '"linux" : { "readonlyPaths" : [ "/foo", 1 ] }'
435 '"linux" : { "readonlyPaths" : [ "/foo", "bar" ] }'
436 # Invalid hooks
437 '"hooks" : { "prestart" : [ { "path" : "/bin/sh", "timeout" : 0 } ] }'
438 # Invalid annotations
439 '"annotations" : { "" : "bar" }'
440 '"annotations" : { "foo" : 1 }'
441 )
442
443 for snippet in "${INVALID_SNIPPETS[@]}"; do
444 : "Snippet: $snippet"
445 cat >"$OCI/config.json" <<EOF
446 {
447 "ociVersion" : "1.0.0",
448 "root" : {
449 "path" : "rootfs"
450 },
451 $snippet
452 }
453 EOF
454 (! systemd-nspawn --oci-bundle="$OCI" sh -c 'echo hello')
455 done
456
457 # Invalid OCI bundle version
458 cat >"$OCI/config.json" <<EOF
459 {
460 "ociVersion" : "6.6.6",
461 "root" : {
462 "path" : "rootfs"
463 }
464 }
465 EOF
466 (! systemd-nspawn --oci-bundle="$OCI" sh -c 'echo hello')