]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
firstboot: process the root account after sysusers created it 27750/head
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Mon, 3 Oct 2022 10:12:15 +0000 (12:12 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Tue, 23 May 2023 13:09:39 +0000 (15:09 +0200)
We would create root account from sysusers or from firstboot, depending on
which one ran earlier. Since firstboot offers more options, in particular can
set the root password, we needed to order it earlier. This created an ugly
ordering requirement:

systemd-sysusers.service > systemd-firstboot.service > ... >
  systemd-remount-fs.service > systemd-tmpfiles-setup-dev.service >
  systemd-sysusers.service

We want sysusers.service to create basic users, so we can create nodes in dev,
so we can operate on block devices and such, so that we can resize and remount
things. But at the same time, systemd-firstboot.service can only work if it is
run early, before systemd-sysusers.service has created /etc/passwd. We can't
have it both ways: the units that want to have a fully writable root file
system cannot be ordered before units which are required to do file system
preparation.

Instead of trying to order firstboot very early, let's let it do its thing even
if it is started later. Instead of refusing to create to the root account if
/etc/passwd and /etc/shadow exist, actually check if the account is configured.
Now sysusers writes root account with password PASSWORD_UNPROVISIONED
("!unprovisioned"), and then firstboot checks for this, and will configure root
in this case.

This allows sysusers to be executed earlier (or accounts to be set up earlier
in another way).

This effectively reverts b825ab1a99b69956057c79838faaf7b44afee474.

src/basic/user-util.h
src/firstboot/firstboot.c
src/sysusers/sysusers.c
units/systemd-firstboot.service

index 5aca6307e05bff7567efbc3cce20dad0a98ca42e..c82941dd814e2a2fc81986699f78b7555a7d24cc 100644 (file)
@@ -150,3 +150,8 @@ static inline bool hashed_password_is_locked_or_invalid(const char *password) {
 
 /* A password indicating "hey, no password required for login" */
 #define PASSWORD_NONE ""
+
+/* Used by sysusers to indicate that the password should be filled in by firstboot.
+ * Also see https://github.com/systemd/systemd/pull/24680#pullrequestreview-1439464325.
+ */
+#define PASSWORD_UNPROVISIONED "!unprovisioned"
index 19d5568854e11d5102295467d8438ee034e23ecc..6b42e64043311f6e90e68e43b67ff1d169c723ec 100644 (file)
@@ -234,17 +234,72 @@ static int prompt_loop(const char *text, char **l, unsigned percentage, bool (*i
 }
 
 static int should_configure(int dir_fd, const char *filename) {
+        _cleanup_fclose_ FILE *passwd = NULL, *shadow = NULL;
+        int r;
+
         assert(dir_fd >= 0);
         assert(filename);
 
-        if (faccessat(dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW) < 0) {
-                if (errno != ENOENT)
-                        return log_error_errno(errno, "Failed to access %s: %m", filename);
+        if (streq(filename, "passwd") && !arg_force)
+                /* We may need to do additional checks, so open the file. */
+                r = xfopenat(dir_fd, filename, "re", O_NOFOLLOW, &passwd);
+        else
+                r = RET_NERRNO(faccessat(dir_fd, filename, F_OK, AT_SYMLINK_NOFOLLOW));
 
+        if (r == -ENOENT)
                 return true; /* missing */
+        if (r < 0)
+                return log_error_errno(r, "Failed to access %s: %m", filename);
+        if (arg_force)
+                return true; /* exists, but if --force was given we should still configure the file. */
+
+        if (!passwd)
+                return false;
+
+        /* In case of /etc/passwd, do an additional check for the root password field.
+         * We first check that passwd redirects to shadow, and then we check shadow.
+         */
+        struct passwd *i;
+        while ((r = fgetpwent_sane(passwd, &i)) > 0) {
+                if (!streq(i->pw_name, "root"))
+                        continue;
+
+                if (streq_ptr(i->pw_passwd, PASSWORD_SEE_SHADOW))
+                        break;
+                log_debug("passwd: root account with non-shadow password found, treating root as configured");
+                return false;
         }
+        if (r < 0)
+                return log_error_errno(r, "Failed to read %s: %m", filename);
+        if (r == 0) {
+                log_debug("No root account found in %s, assuming root is not configured.", filename);
+                return true;
+        }
+
+        r = xfopenat(dir_fd, "shadow", "re", O_NOFOLLOW, &shadow);
+        if (r == -ENOENT) {
+                log_debug("No shadow file found, assuming root is not configured.");
+                return true; /* missing */
+        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to access shadow: %m");
 
-        return arg_force; /* exists, but if --force was given we should still configure the file. */
+        struct spwd *j;
+        while ((r = fgetspent_sane(shadow, &j)) > 0) {
+                if (!streq(j->sp_namp, "root"))
+                        continue;
+
+                bool unprovisioned = streq_ptr(j->sp_pwdp, PASSWORD_UNPROVISIONED);
+                log_debug("Root account found, %s.",
+                          unprovisioned ? "with unprovisioned password, treating root as not configured" :
+                                          "treating root as configured");
+                return unprovisioned;
+        }
+        if (r < 0)
+                return log_error_errno(r, "Failed to read shadow: %m");
+        assert(r == 0);
+        log_debug("No root account found in shadow, assuming root is not configured.");
+        return true;
 }
 
 static bool locale_is_installed_bool(const char *name) {
index 5aaaf609b2cd2b95f93db265d5916c3291107629..12adad516e9f02df97fc2f1bf8acb3bf03ef30b8 100644 (file)
@@ -618,7 +618,6 @@ static int write_temporary_shadow(const char *shadow_path, FILE **ret_tmpfile, c
 
                 struct spwd n = {
                         .sp_namp = i->name,
-                        .sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID,
                         .sp_lstchg = lstchg,
                         .sp_min = -1,
                         .sp_max = -1,
@@ -641,6 +640,11 @@ static int write_temporary_shadow(const char *shadow_path, FILE **ret_tmpfile, c
 
                 if (creds_password)
                         n.sp_pwdp = creds_password;
+                else if (streq(i->name, "root"))
+                        /* Let firstboot set the password later */
+                        n.sp_pwdp = (char*) PASSWORD_UNPROVISIONED;
+                else
+                        n.sp_pwdp = (char*) PASSWORD_LOCKED_AND_INVALID;
 
                 r = putspent_sane(&n, shadow);
                 if (r < 0)
index 58f3eeb679283d7fccfcea773b469beaf05dd151..5fee85a2873b86a0eb989f798e328b6e52140cac 100644 (file)
@@ -16,7 +16,7 @@ ConditionFirstBoot=yes
 
 DefaultDependencies=no
 After=systemd-remount-fs.service
-Before=systemd-sysusers.service systemd-vconsole-setup.service sysinit.target first-boot-complete.target
+Before=systemd-vconsole-setup.service sysinit.target first-boot-complete.target
 Wants=first-boot-complete.target
 Conflicts=shutdown.target
 Before=shutdown.target