sysusers: read passwords from the credentials logic

Let's make use of our own credentials infrastructure in our tools: let's
hook up systemd-sysusers with the credentials logic, so that the root
password can be provisioned this way. This is really useful when working
with stateless systems, in particular nspawn's "--volatile=yes" switch,
as this works now:

 # systemd-nspawn -i foo.raw --volatile=yes --set-credential=passwd.plaintext-password:foo

For the first time we have a nice, non-interactive way to provision the
root password for a fully stateless system from the container manager.
Yay!
This commit is contained in:
Lennart Poettering 2021-03-11 10:34:20 +01:00
parent fc682be261
commit 99e9f896fb
3 changed files with 110 additions and 1 deletions

View file

@ -126,7 +126,60 @@
<xi:include href="standard-options.xml" xpointer="help" />
<xi:include href="standard-options.xml" xpointer="version" />
</variablelist>
</refsect1>
<refsect1>
<title>Credentials</title>
<para><command>systemd-sysusers</command> supports the service credentials logic as implemented by
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> (see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
<variablelist>
<varlistentry>
<term><literal>passwd.hashed-password.<replaceable>user</replaceable></literal></term>
<listitem><para>A UNIX hashed password string to use for the specified user, when creating an entry
for it. This is particularly useful for the <literal>root</literal> user as it allows provisioning
the default root password to use via a unit file drop-in or from a container manager passing in this
credential. Note that setting this credential has no effect if the specified user account already
exists. This credential is hence primarily useful in first boot scenarios or systems that are fully
stateless and come up with an empty <filename>/etc/</filename> on every boot.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>passwd.plaintext-password.<replaceable>user</replaceable></literal></term>
<listitem><para>Similar to <literal>passwd.hashed-password.<replaceable>user</replaceable></literal>
but expect a literal, plaintext password, which is then automatically hashed before used for the user
account. If both the hashed and the plaintext credential are specified for the same user the
former takes precedence. It's generally recommended to specify the hashed version; however in test
environments with weaker requirements on security it might be easier to pass passwords in plaintext
instead.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>passwd.shell.<replaceable>user</replaceable></literal></term>
<listitem><para>Specifies the shell binary to use for the the specified account when creating it.</para></listitem>
</varlistentry>
</variablelist>
<para>Note that by default the <filename>systemd-sysusers.service</filename> unit file is set up to
inherit the <literal>passwd.hashed-password.root</literal>,
<literal>passwd.plaintext-password.root</literal> and <literal>passwd.shell.root</literal> credentials
from the service manager. Thus, when invoking a container with an unpopulated <filename>/etc/</filename>
for the first time it is possible to configure the root user's password to be <literal>systemd</literal>
like this:</para>
<para><programlisting># systemd-nspawn --image=… --set-credential=password.hashed-password.root:'$y$j9T$yAuRJu1o5HioZAGDYPU5d.$F64ni6J2y2nNQve90M/p0ZP0ECP/qqzipNyaY9fjGpC' …</programlisting></para>
<para>Note again that the data specified in these credentials is consulted only when creating an account
for the first time, it may not be used for changing the password or shell of an account that already
exists.</para>
<para>Use <citerefentry><refentrytitle>mkpasswd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for generating UNIX password hashes from the command line.</para>
</refsect1>
<refsect1>
@ -141,7 +194,9 @@
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>sysusers.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>,
<ulink url="https://systemd.io/UIDS-GIDS">Users, Groups, UIDs and GIDs on systemd systems</ulink>
<ulink url="https://systemd.io/UIDS-GIDS">Users, Groups, UIDs and GIDs on systemd systems</ulink>,
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>mkpasswd</refentrytitle><manvolnum>1</manvolnum></citerefentry>
</para>
</refsect1>

View file

@ -6,6 +6,7 @@
#include "alloc-util.h"
#include "conf-files.h"
#include "copy.h"
#include "creds-util.h"
#include "def.h"
#include "dissect-image.h"
#include "fd-util.h"
@ -13,7 +14,9 @@
#include "format-util.h"
#include "fs-util.h"
#include "hashmap.h"
#include "libcrypt-util.h"
#include "main-func.h"
#include "memory-util.h"
#include "mount-util.h"
#include "nscd-flush.h"
#include "pager.h"
@ -429,6 +432,8 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
}
ORDERED_HASHMAP_FOREACH(i, todo_uids) {
_cleanup_free_ char *creds_shell = NULL, *cn = NULL;
struct passwd n = {
.pw_name = i->name,
.pw_uid = i->uid,
@ -446,6 +451,17 @@ static int write_temporary_passwd(const char *passwd_path, FILE **tmpfile, char
.pw_shell = i->shell ?: (char*) default_shell(i->uid),
};
/* Try to pick up the shell for this account via the credentials logic */
cn = strjoin("passwd.shell.", i->name);
if (!cn)
return -ENOMEM;
r = read_credential(cn, (void**) &creds_shell, NULL);
if (r < 0)
log_debug_errno(r, "Couldn't read credential '%s', ignoring: %m", cn);
else
n.pw_shell = creds_shell;
r = putpwent_sane(&n, passwd);
if (r < 0)
return r;
@ -530,6 +546,9 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
}
ORDERED_HASHMAP_FOREACH(i, todo_uids) {
_cleanup_(erase_and_freep) char *creds_password = NULL;
_cleanup_free_ char *cn = NULL;
struct spwd n = {
.sp_namp = i->name,
.sp_pwdp = (char*) "!*", /* lock this password, and make it invalid */
@ -542,6 +561,34 @@ static int write_temporary_shadow(const char *shadow_path, FILE **tmpfile, char
.sp_flag = ULONG_MAX, /* this appears to be what everybody does ... */
};
/* Try to pick up the password for this account via the credentials logic */
cn = strjoin("passwd.hashed-password.", i->name);
if (!cn)
return -ENOMEM;
r = read_credential(cn, (void**) &creds_password, NULL);
if (r == -ENOENT) {
_cleanup_(erase_and_freep) char *plaintext_password = NULL;
free(cn);
cn = strjoin("passwd.plaintext-password.", i->name);
if (!cn)
return -ENOMEM;
r = read_credential(cn, (void**) &plaintext_password, NULL);
if (r < 0)
log_debug_errno(r, "Couldn't read credential '%s', ignoring: %m", cn);
else {
r = hash_password(plaintext_password, &creds_password);
if (r < 0)
return log_debug_errno(r, "Failed to hash password: %m");
}
} else if (r < 0)
log_debug_errno(r, "Couldn't read credential '%s', ignoring: %m", cn);
if (creds_password)
n.sp_pwdp = creds_password;
r = putspent_sane(&n, shadow);
if (r < 0)
return r;

View file

@ -21,3 +21,10 @@ Type=oneshot
RemainAfterExit=yes
ExecStart=systemd-sysusers
TimeoutSec=90s
# Optionally, pick up a root password and shell for the root user from a
# credential passed to the service manager. This is useful for importing this
# data from nspawn's --set-credential= switch.
LoadCredential=passwd.hashed-password.root
LoadCredential=passwd.plaintext-password.root
LoadCredential=passwd.shell.root