]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
nspawn: ensure single-process container running as --user can access credentials
authorLuca Boccassi <bluca@debian.org>
Thu, 14 Mar 2024 23:44:20 +0000 (23:44 +0000)
committerLuca Boccassi <luca.boccassi@gmail.com>
Mon, 22 Apr 2024 13:47:44 +0000 (15:47 +0200)
When starting a container with --user, the new uid will be resolved and switched to
only in the inner child, at the end of the setup, by spawning getent. But the
credentials are set up in the outer child, long before the user is resolvable,
and the directories/files are made only readable by root and read-only, which
means they cannot be changed later and made visible to the user.

When this particular combination is specified, it is obvious the caller wants
the single-process container to be able to use credentials, so make them world
readable only in that specific case.

Fixes https://github.com/systemd/systemd/issues/31794

man/systemd-nspawn.xml
src/nspawn/nspawn.c
test/units/testsuite-13.nspawn.sh

index ec0492c26df9b9d90056d6208abadadbcd474dda..2645a6b217cfdbd2088aac72178ba4091ca96aa0 100644 (file)
 
         <listitem><para>After transitioning into the container, change to the specified user defined in the
         container's user database. Like all other systemd-nspawn features, this is not a security feature and
-        provides protection against accidental destructive operations only.</para></listitem>
+        provides protection against accidental destructive operations only.</para>
+
+        <para>Note that if credentials are used in combination with a non-root <option>--user=</option>
+        (e.g.: <option>--set-credential=</option>, <option>--load-credential=</option> or
+        <option>--import-credential=</option>), then <option>--no-new-privileges=yes</option> must be used, and
+        <option>--boot</option> or <option>--as-pid2</option> must not be used, as the credentials would
+        otherwise be unreadable by the container due to missing privileges after switching to the specified
+        user.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index f4da91797e3639e958047e17d5e51b57eca8843b..60894c9c864a8f38069bbb073126f405734736bd 100644 (file)
@@ -2400,17 +2400,31 @@ int make_run_host(const char *root) {
 }
 
 static int setup_credentials(const char *root) {
+        bool world_readable = false;
         const char *q;
         int r;
 
         if (arg_credentials.n_credentials == 0)
                 return 0;
 
+        /* If starting a single-process container as a non-root user, the uid will only be resolved after we
+         * are inside the inner child, when credential directories and files are already read-only, so they
+         * are unusable as the single process won't have access to them. We also don't have access to the
+         * uid that will actually be used from here, as we are setting credentials up from the outher child.
+         * In order to make them usable as requested by the configuration, make them world readable in that
+         * case, as by definition there are no other processes in that case besides the one being started,
+         * which is being configured to be able to access credentials, and any of its children which will
+         * inherit its privileges anyway. To ensure this, also enforce (and document) that
+         * --no-new-privileges is necessary for this combination to work. */
+        if (arg_no_new_privileges && !isempty(arg_user) && !STR_IN_SET(arg_user, "root", "0") &&
+            arg_start_mode == START_PID1)
+                world_readable = true;
+
         r = make_run_host(root);
         if (r < 0)
                 return r;
 
-        r = userns_mkdir(root, "/run/host/credentials", 0700, 0, 0);
+        r = userns_mkdir(root, "/run/host/credentials", world_readable ? 0777 : 0700, 0, 0);
         if (r < 0)
                 return log_error_errno(r, "Failed to create /run/host/credentials: %m");
 
@@ -2427,7 +2441,7 @@ static int setup_credentials(const char *root) {
                 if (!j)
                         return log_oom();
 
-                fd = open(j, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC|O_NOFOLLOW, 0600);
+                fd = open(j, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC|O_NOFOLLOW, world_readable ? 0666 : 0600);
                 if (fd < 0)
                         return log_error_errno(errno, "Failed to create credential file %s: %m", j);
 
@@ -2435,7 +2449,7 @@ static int setup_credentials(const char *root) {
                 if (r < 0)
                         return log_error_errno(r, "Failed to write credential to file %s: %m", j);
 
-                if (fchmod(fd, 0400) < 0)
+                if (fchmod(fd, world_readable ? 0444 : 0400) < 0)
                         return log_error_errno(errno, "Failed to adjust access mode of %s: %m", j);
 
                 if (arg_userns_mode != USER_NAMESPACE_NO) {
@@ -2444,7 +2458,7 @@ static int setup_credentials(const char *root) {
                 }
         }
 
-        if (chmod(q, 0500) < 0)
+        if (chmod(q, world_readable ? 0555 : 0500) < 0)
                 return log_error_errno(errno, "Failed to adjust access mode of %s: %m", q);
 
         r = userns_lchown(q, 0, 0);
index 69f21efd6058a54ce3e9dce691ae13dea3bd003c..5b67cf8c7e12b8e27a9d83b9c3cc0b53b9a9458b 100755 (executable)
@@ -271,6 +271,13 @@ EOF
                    --load-credential=cred.path:/tmp/cred.path \
                    --set-credential="cred.set:hello world" \
                    bash -xec '[[ "$(</run/host/credentials/cred.path)" == "foo bar" ]]; [[ "$(</run/host/credentials/cred.set)" == "hello world" ]]'
+    # Combine with --user to ensure creds are still readable
+    systemd-nspawn --directory="$root" \
+                   --user=testuser \
+                   --no-new-privileges=yes \
+                   --load-credential=cred.path:/tmp/cred.path \
+                   --set-credential="cred.set:hello world" \
+                   bash -xec '[[ "$(</run/host/credentials/cred.path)" == "foo bar" ]]; [[ "$(</run/host/credentials/cred.set)" == "hello world" ]]'
     rm -f /tmp/cred.path
 
     # Assorted tests