From d130181fd807f4f623441a431c76bbd827af0178 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 22 Jul 2020 17:50:45 +0200 Subject: [PATCH 1/6] nspawn: add missing spdx header --- src/nspawn/nspawn-def.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/nspawn/nspawn-def.h b/src/nspawn/nspawn-def.h index 9b54cda2f7..ac3a1a02c4 100644 --- a/src/nspawn/nspawn-def.h +++ b/src/nspawn/nspawn-def.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ #pragma once #include From 549719699c7761b4ca5d74d77230008824f31325 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 23 Jul 2020 13:45:45 +0100 Subject: [PATCH 2/6] NEWS: fix typo in path --- NEWS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 36c2c930e8..390282c90a 100644 --- a/NEWS +++ b/NEWS @@ -527,7 +527,7 @@ CHANGES WITH 246: has been extended by a set of environment variables that expose select fields from the host's os-release file to the container payload. Similarly, host's os-release files can be mounted into the - container underneath /run/hosts. Together, those mechanisms provide a + container underneath /run/host. Together, those mechanisms provide a standardized way to expose information about the host to the container payload. Both interfaces are implemented in systemd-nspawn. From 98aac2ad5a1cbea6af3e474f45da77f696c99bdd Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 23 Jul 2020 13:46:13 +0100 Subject: [PATCH 3/6] doc: update os-release spec with new path for container host's file --- man/os-release.xml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/man/os-release.xml b/man/os-release.xml index 675daf3ede..a2164436c3 100644 --- a/man/os-release.xml +++ b/man/os-release.xml @@ -342,10 +342,9 @@ Container and sandbox runtime managers may make the host's identification data available to applications by providing the host's - /etc/os-release and - /usr/lib/os-release as respectively - /run/host/etc/os-release and - /run/host/usr/lib/os-release. + /etc/os-release (if available, otherwise + /usr/lib/os-release as a fallback) as + /run/host/os-release. From 62b0ee9eb1b488e96359fa9b3f225c80e92cd082 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 23 Jul 2020 14:44:10 +0100 Subject: [PATCH 4/6] portabled: update host's os-release path --- src/portable/portable.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/portable/portable.c b/src/portable/portable.c index 48294d4c49..3a1367ec2b 100644 --- a/src/portable/portable.c +++ b/src/portable/portable.c @@ -695,17 +695,28 @@ static int install_chroot_dropin( if (!text) return -ENOMEM; - if (endswith(m->name, ".service")) + if (endswith(m->name, ".service")) { + const char *os_release_source; + + if (access("/etc/os-release", F_OK) < 0) { + if (errno != ENOENT) + return log_debug_errno(errno, "Failed to check if /etc/os-release exists: %m"); + + os_release_source = "/usr/lib/os-release"; + } else + os_release_source = "/etc/os-release"; + if (!strextend(&text, "\n" "[Service]\n", IN_SET(type, IMAGE_DIRECTORY, IMAGE_SUBVOLUME) ? "RootDirectory=" : "RootImage=", image_path, "\n" "Environment=PORTABLE=", basename(image_path), "\n" - "BindReadOnlyPaths=-/etc/os-release:/run/host/etc/os-release /usr/lib/os-release:/run/host/usr/lib/os-release\n" + "BindReadOnlyPaths=", os_release_source, ":/run/host/os-release\n" "LogExtraFields=PORTABLE=", basename(image_path), "\n", NULL)) return -ENOMEM; + } r = write_string_file(dropin, text, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC); if (r < 0) From d64e32c245b6710f90f746d3bf970dd17f765d4b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Jul 2020 16:49:13 +0200 Subject: [PATCH 5/6] nspawn: rework how /run/host/ is set up Let's find the right os-release file on the host side, and only mount the one that matters, i.e. /etc/os-release if it exists and /usr/lib/os-release otherwise. Use the fixed path /run/host/os-release for that. Let's also mount /run/host as a bind mount on itself before we set up /run/host, and let's mount it MS_RDONLY after we are done, so that it remains immutable as a whole. --- src/nspawn/nspawn-mount.c | 66 ++++++++++++++++++++------------------ src/nspawn/nspawn-mount.h | 1 + test/units/testsuite-13.sh | 21 +++++++++--- 3 files changed, 53 insertions(+), 35 deletions(-) diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 4687ac4c18..a48c7f62da 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -563,15 +563,16 @@ int mount_all(const char *dest, MOUNT_FATAL|MOUNT_MKDIR }, { "tmpfs", "/run", "tmpfs", "mode=755" TMPFS_LIMITS_RUN, MS_NOSUID|MS_NODEV|MS_STRICTATIME, MOUNT_FATAL|MOUNT_MKDIR }, - { "/usr/lib/os-release", "/run/host/usr/lib/os-release", NULL, NULL, MS_BIND, - MOUNT_FATAL|MOUNT_MKDIR|MOUNT_TOUCH }, /* As per kernel interface requirements, bind mount first (creating mount points) and make read-only later */ - { NULL, "/run/host/usr/lib/os-release", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, - 0 }, - { "/etc/os-release", "/run/host/etc/os-release", NULL, NULL, MS_BIND, - MOUNT_MKDIR|MOUNT_TOUCH }, - { NULL, "/run/host/etc/os-release", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, - 0 }, - + { "/run/host", "/run/host", NULL, NULL, MS_BIND, + MOUNT_FATAL|MOUNT_MKDIR|MOUNT_PREFIX_ROOT }, /* Prepare this so that we can make it read-only when we are done */ + { "/etc/os-release", "/run/host/os-release", NULL, NULL, MS_BIND, + MOUNT_TOUCH }, /* As per kernel interface requirements, bind mount first (creating mount points) and make read-only later */ + { "/usr/lib/os-release", "/run/host/os-release", NULL, NULL, MS_BIND, + MOUNT_FATAL }, /* If /etc/os-release doesn't exist use the version in /usr/lib as fallback */ + { NULL, "/run/host/os-release", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, + MOUNT_FATAL }, + { NULL, "/run/host", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, + MOUNT_FATAL|MOUNT_IN_USERNS }, #if HAVE_SELINUX { "/sys/fs/selinux", "/sys/fs/selinux", NULL, NULL, MS_BIND, MOUNT_MKDIR }, /* Bind mount first (mkdir/chown the mount point in case /sys/ is mounted as minimal skeleton tmpfs) */ @@ -589,9 +590,9 @@ int mount_all(const char *dest, int r; for (k = 0; k < ELEMENTSOF(mount_table); k++) { - _cleanup_free_ char *where = NULL, *options = NULL; - const char *o; + _cleanup_free_ char *where = NULL, *options = NULL, *prefixed = NULL; bool fatal = FLAGS_SET(mount_table[k].mount_settings, MOUNT_FATAL); + const char *o; if (in_userns != FLAGS_SET(mount_table[k].mount_settings, MOUNT_IN_USERNS)) continue; @@ -616,20 +617,9 @@ int mount_all(const char *dest, return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); if (r > 0) continue; - - /* Shortcut for optional bind mounts: if the source can't be found skip ahead to avoid creating - * empty and unused directories. */ - if (!fatal && FLAGS_SET(mount_table[k].mount_settings, MOUNT_MKDIR) && FLAGS_SET(mount_table[k].flags, MS_BIND)) { - r = access(mount_table[k].what, F_OK); - if (r < 0) { - if (errno == ENOENT) - continue; - return log_error_errno(errno, "Failed to stat %s: %m", mount_table[k].what); - } - } } - if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_MKDIR)) { + if ((mount_table[k].mount_settings & (MOUNT_MKDIR|MOUNT_TOUCH)) != 0) { uid_t u = (use_userns && !in_userns) ? uid_shift : UID_INVALID; if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_TOUCH)) @@ -647,13 +637,17 @@ int mount_all(const char *dest, if (r != -EROFS) continue; } - if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_TOUCH)) { - r = touch(where); - if (r < 0 && r != -EEXIST) { - if (fatal) - return log_error_errno(r, "Failed to create mount point %s: %m", where); - log_debug_errno(r, "Failed to create mount point %s: %m", where); - } + } + + if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_TOUCH)) { + r = touch(where); + if (r < 0 && r != -EEXIST) { + if (fatal && r != -EROFS) + return log_error_errno(r, "Failed to create file %s: %m", where); + + log_debug_errno(r, "Failed to create file %s: %m", where); + if (r != -EROFS) + continue; } } @@ -666,8 +660,18 @@ int mount_all(const char *dest, o = options; } + if (FLAGS_SET(mount_table[k].mount_settings, MOUNT_PREFIX_ROOT)) { + /* Optionally prefix the mount source with the root dir. This is useful in bind + * mounts to be created within the container image before we transition into it. Note + * that MOUNT_IN_USERNS is run after we transitioned hence prefixing is not ncessary + * for those. */ + r = chase_symlinks(mount_table[k].what, dest, CHASE_PREFIX_ROOT, &prefixed, NULL); + if (r < 0) + return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].what); + } + r = mount_verbose(fatal ? LOG_ERR : LOG_DEBUG, - mount_table[k].what, + prefixed ?: mount_table[k].what, where, mount_table[k].type, mount_table[k].flags, diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h index 062ed8b57d..3898c74f18 100644 --- a/src/nspawn/nspawn-mount.h +++ b/src/nspawn/nspawn-mount.h @@ -18,6 +18,7 @@ typedef enum MountSettingsMask { MOUNT_NON_ROOT_ONLY = 1 << 7, /* if set, only non-root mounts are mounted */ MOUNT_MKDIR = 1 << 8, /* if set, make directory to mount over first */ MOUNT_TOUCH = 1 << 9, /* if set, touch file to mount over first */ + MOUNT_PREFIX_ROOT = 1 << 10,/* if set, prefix the source path with the container's root directory */ } MountSettingsMask; typedef enum CustomMountType { diff --git a/test/units/testsuite-13.sh b/test/units/testsuite-13.sh index 0ad75ac8b0..b8fbf00ea6 100755 --- a/test/units/testsuite-13.sh +++ b/test/units/testsuite-13.sh @@ -66,12 +66,25 @@ if [ -n "${ID:+set}" ] && [ "${ID}" != "${container_host_id}" ]; then exit 1; fi if [ -n "${VERSION_ID:+set}" ] && [ "${VERSION_ID}" != "${container_host_version_id}" ]; then exit 1; fi if [ -n "${BUILD_ID:+set}" ] && [ "${BUILD_ID}" != "${container_host_build_id}" ]; then exit 1; fi if [ -n "${VARIANT_ID:+set}" ] && [ "${VARIANT_ID}" != "${container_host_variant_id}" ]; then exit 1; fi -cd /tmp; (cd /run/host/usr/lib; md5sum os-release) | md5sum -c -if echo test >> /run/host/usr/lib/os-release; then exit 1; fi -if echo test >> /run/host/etc/os-release; then exit 1; fi +cd /tmp; (cd /run/host; md5sum os-release) | md5sum -c +if echo test >> /run/host/os-release; then exit 1; fi ' - systemd-nspawn --register=no -D /testsuite-13.nc-container --bind=/etc/os-release:/tmp/os-release /bin/sh -x -e -c "$_cmd" + local _os_release_source="/etc/os-release" + if [ ! -r "${_os_release_source}" ]; then + _os_release_source="/usr/lib/os-release" + elif [ -L "${_os_release_source}" ] && rm /etc/os-release; then + # Ensure that /etc always wins if available + cp /usr/lib/os-release /etc + echo MARKER=1 >> /etc/os-release + fi + + systemd-nspawn --register=no -D /testsuite-13.nc-container --bind="${_os_release_source}":/tmp/os-release /bin/sh -x -e -c "$_cmd" + + if grep -q MARKER /etc/os-release; then + rm /etc/os-release + ln -s ../usr/lib/os-release /etc/os-release + fi } function run { From 38821a0e29dd818666e9d25e98d68d4392d996dc Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 23 Jul 2020 16:58:56 +0200 Subject: [PATCH 6/6] update TODO --- TODO | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TODO b/TODO index 7d4d54aa7a..58fdc45e1e 100644 --- a/TODO +++ b/TODO @@ -17,6 +17,10 @@ Janitorial Clean-ups: Features: +* nspawn: move "incoming mount" directory to /run/host, move "inaccessible" + nodes to /run/host, move notify socket (for sd_notify() between payload and + container manager) + * cryptsetup: if keyfile specified in crypttab is AF_UNIX socket, connect to it and read from it (like we do elsewhere with READ_FULL_FILE_CONNECT_SOCKET)