]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
userdb: Add userdb.transient credentials
authorDaanDeMeyer <daan.j.demeyer@gmail.com>
Thu, 3 Jul 2025 19:22:41 +0000 (21:22 +0200)
committerLennart Poettering <lennart@poettering.net>
Thu, 10 Jul 2025 19:36:09 +0000 (21:36 +0200)
To implement --bind-user in systemd-vmspawn, we need a transient
version of these credentials. These are useful when the home directory
of the user is mounted into the container/vm and every trace of the user
will be (mostly) gone again when the container/vm is shut down.

man/systemd.system-credentials.xml
src/userdb/userdbctl.c
units/systemd-userdb-load-credentials.service

index 73c36c19153d647f24d6b6db0be11cbb071c5659..d436cff5d489ab949e85429bf447076cbaff5d6e 100644 (file)
       <varlistentry>
         <term><varname>userdb.user.*</varname></term>
         <term><varname>userdb.group.*</varname></term>
+        <term><varname>userdb.transient.user.*</varname></term>
+        <term><varname>userdb.transient.group.*</varname></term>
 
         <listitem>
           <para>Configure JSON user and group records. Read by
           <literal>userdb.user.foobar</literal> the suffix is <literal>foobar</literal>) must match the user
           or group name field from the user or group record.</para>
 
-          <para>Note that the records are created in <filename>/etc/userdb/</filename>
+          <para>Note that the records created for <varname>userdb.user.*</varname> and
+          <varname>userdb.group.*</varname> credentials are created in <filename>/etc/userdb/</filename> and
+          the records created for <varname>userdb.transient.user.*</varname> and
+          <varname>userdb.transient.group.*</varname> are created in <filename>/run/userdb/</filename>
           (<filename>/etc/passwd</filename> and <filename>/etc/group</filename> are not modified).</para>
 
           <xi:include href="version-info.xml" xpointer="v258"/>
index 5c7b8aae458d19854a98b02e1281ff18abd6cdd8..0c3667141743cc3edcc200bcb1364362466682f6 100644 (file)
@@ -1181,18 +1181,32 @@ static int ssh_authorized_keys(int argc, char *argv[], void *userdata) {
         return r;
 }
 
-static int load_credential_one(int credential_dir_fd, const char *name, int userdb_dir_fd) {
+static int load_credential_one(
+                int credential_dir_fd,
+                const char *name,
+                int userdb_dir_persist_fd,
+                int userdb_dir_transient_fd) {
+
         int r;
 
         assert(credential_dir_fd >= 0);
         assert(name);
-        assert(userdb_dir_fd >= 0);
+        assert(userdb_dir_persist_fd >= 0);
+        assert(userdb_dir_transient_fd >= 0);
+
+        const char *suffix = startswith(name, "userdb.");
+        if (!suffix)
+                return 0;
 
-        const char *user = startswith(name, "userdb.user.");
-        const char *group = startswith(name, "userdb.group.");
+        const char *transient = startswith(suffix, "transient."),
+                *user = startswith(transient ?: suffix, "user."),
+                *group = startswith(transient ?: suffix, "group.");
         if (!user && !group)
                 return 0;
 
+        const char *userdb_dir = transient ? "/run/userdb" : "/etc/userdb";
+        int userdb_dir_fd = transient ? userdb_dir_transient_fd : userdb_dir_persist_fd;
+
         _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL;
         unsigned line = 0, column = 0;
         r = sd_json_parse_file_at(NULL, credential_dir_fd, name, SD_JSON_PARSE_SENSITIVE, &v, &line, &column);
@@ -1350,12 +1364,12 @@ static int load_credential_one(int credential_dir_fd, const char *name, int user
 
         r = write_string_file_at(userdb_dir_fd, fn, formatted, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC);
         if (r < 0)
-                return log_error_errno(r, "Failed to write JSON record to /etc/userdb/%s: %m", fn);
+                return log_error_errno(r, "Failed to write JSON record to %s/%s: %m", userdb_dir, fn);
 
         if (symlinkat(fn, userdb_dir_fd, link) < 0)
                 return log_error_errno(errno, "Failed to create symlink from %s to %s: %m", link, fn);
 
-        log_info("Installed /etc/userdb/%s from credential.", fn);
+        log_info("Installed %s/%s from credential.", userdb_dir, fn);
 
         if ((ur && !sd_json_variant_is_blank_object(ur_privileged->json)) ||
             (gr && !sd_json_variant_is_blank_object(gr_privileged->json))) {
@@ -1371,7 +1385,7 @@ static int load_credential_one(int credential_dir_fd, const char *name, int user
 
                 r = write_string_file_at(userdb_dir_fd, fn, formatted, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MODE_0600);
                 if (r < 0)
-                        return log_error_errno(r, "Failed to write JSON record to /etc/userdb/%s: %m", fn);
+                        return log_error_errno(r, "Failed to write JSON record to %s/%s: %m", userdb_dir, fn);
 
                 link = mfree(link);
 
@@ -1386,7 +1400,7 @@ static int load_credential_one(int credential_dir_fd, const char *name, int user
                 if (symlinkat(fn, userdb_dir_fd, link) < 0)
                         return log_error_errno(errno, "Failed to create symlink from %s to %s: %m", link, fn);
 
-                log_info("Installed /etc/userdb/%s from credential.", fn);
+                log_info("Installed %s/%s from credential.", userdb_dir, fn);
         }
 
         if (ur)
@@ -1399,7 +1413,7 @@ static int load_credential_one(int credential_dir_fd, const char *name, int user
                         if (fd < 0)
                                 return log_error_errno(errno, "Failed to create %s: %m", membership);
 
-                        log_info("Installed /etc/userdb/%s from credential.", membership);
+                        log_info("Installed %s/%s from credential.", userdb_dir, membership);
                 }
         else
                 STRV_FOREACH(u, gr->members) {
@@ -1411,7 +1425,7 @@ static int load_credential_one(int credential_dir_fd, const char *name, int user
                         if (fd < 0)
                                 return log_error_errno(errno, "Failed to create %s: %m", membership);
 
-                        log_info("Installed /etc/userdb/%s from credential.", membership);
+                        log_info("Installed %s/%s from credential.", userdb_dir, membership);
                 }
 
         if (ur && user_record_disposition(ur) == USER_REGULAR) {
@@ -1461,13 +1475,21 @@ static int load_credentials(int argc, char *argv[], void *userdata) {
         if (r < 0)
                 return log_error_errno(r, "Failed to enumerate credentials: %m");
 
-        _cleanup_close_ int userdb_dir_fd = xopenat_full(
-                AT_FDCWD, "/etc/userdb",
-                /* open_flags= */ O_DIRECTORY|O_CREAT|O_CLOEXEC,
-                /* xopen_flags= */ XO_LABEL,
-                /* mode= */ 0755);
-        if (userdb_dir_fd < 0)
-                return log_error_errno(userdb_dir_fd, "Failed to open %s: %m", "/etc/userdb/");
+        _cleanup_close_ int userdb_persist_dir_fd = xopenat_full(
+                        AT_FDCWD, "/etc/userdb",
+                        /* open_flags= */ O_DIRECTORY|O_CREAT|O_CLOEXEC,
+                        /* xopen_flags= */ XO_LABEL,
+                        /* mode= */ 0755);
+        if (userdb_persist_dir_fd < 0)
+                return log_error_errno(userdb_persist_dir_fd, "Failed to open /etc/userdb/: %m");
+
+        _cleanup_close_ int userdb_transient_dir_fd = xopenat_full(
+                        AT_FDCWD, "/run/userdb",
+                        /* open_flags= */ O_DIRECTORY|O_CREAT|O_CLOEXEC,
+                        /* xopen_flags= */ XO_LABEL,
+                        /* mode= */ 0755);
+        if (userdb_transient_dir_fd < 0)
+                return log_error_errno(userdb_transient_dir_fd, "Failed to open /run/userdb/: %m");
 
         FOREACH_ARRAY(i, des->entries, des->n_entries) {
                 struct dirent *de = *i;
@@ -1475,7 +1497,11 @@ static int load_credentials(int argc, char *argv[], void *userdata) {
                 if (de->d_type != DT_REG)
                         continue;
 
-                RET_GATHER(r, load_credential_one(credential_dir_fd, de->d_name, userdb_dir_fd));
+                RET_GATHER(r, load_credential_one(
+                                credential_dir_fd,
+                                de->d_name,
+                                userdb_persist_dir_fd,
+                                userdb_transient_dir_fd));
         }
 
         return r;
index 5bcd6d18966b6475b75cbccbef4fdd5f02144caf..e926a03dd6a7acf4fd6c3acb779a6a39c54cdc3a 100644 (file)
@@ -26,6 +26,8 @@ RemainAfterExit=yes
 ExecStart=userdbctl load-credentials
 ImportCredential=userdb.user.*
 ImportCredential=userdb.group.*
+ImportCredential=userdb.transient.user.*
+ImportCredential=userdb.transient.group.*
 
 [Install]
 WantedBy=sysinit.target