Merge pull request #18971 from poettering/sysusers-creds

let's read LoadCredentials=/SetCredentials= style cred in sysusers/firstboot and when asking for passwords
This commit is contained in:
Lennart Poettering 2021-03-31 10:35:17 +02:00 committed by GitHub
commit f9d8325e69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 492 additions and 88 deletions

5
TODO
View file

@ -61,11 +61,6 @@ Features:
With all that in place if nspawn host and container payload are up-to-date
enough we have a very simple way to make host users available in containers.
* systemd-sysusers: pick up passwords from credentials logic, so that users can
easily set root user pw. enable cred inheriting for root user from PID 1, so
that for containers we can configure the root pw automatically via nspawn's
--set-credential= switch. (Also do this for systemd-firstboot)
* whenever we receive fds via SCM_RIGHTS make sure none got dropped due to the
reception limit the kernel silently enforces.

View file

@ -138,6 +138,17 @@
directly. Example: <literal>--keyname=cryptsetup</literal></para></listitem>
</varlistentry>
<varlistentry>
<term><option>--credential=</option></term>
<listitem><para>Configure a credential to read the password from if it exists. This may be used in
conjunction with the <varname>LoadCredential=</varname> and <varname>SetCredential=</varname>
settings in unit files. See
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
details. If not specified, defaults to <literal>password</literal>. This option has no effect if no
credentials directory is passed to the program (i.e. <varname>$CREDENTIALS_DIRECTORY</varname> is not
set) or if the no credential of the specified name exists.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>--timeout=</option></term>

View file

@ -283,7 +283,69 @@
<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-firstboot</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.root</literal></term>
<term><literal>passwd.plaintext-password.root</literal></term>
<listitem><para>A hashed or plaintext version of the root password to use, in place of prompting the
user. These credentials are equivalent to the same ones defined for the
<citerefentry><refentrytitle>systemd-sysusers.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
service.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>passwd.shell.root</literal></term>
<listitem><para>Specifies the shell binary to use for the the specified account when creating
it. Equivalent to the credential of the same name defined for the
<citerefentry><refentrytitle>systemd-sysusers.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
service.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>firstboot.locale</literal></term>
<term><literal>firstboot.locale-messages</literal></term>
<listitem><para>These credentials specify the locale settings to set during first boot, in place of
prompting the user.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>firstboot.keymap</literal></term>
<listitem><para>This credential specifies the keyboard setting to set during first boot, in place of
prompting the user.</para></listitem>
</varlistentry>
<varlistentry>
<term><literal>firstboot.timezone</literal></term>
<listitem><para>This credential specifies the system timezone setting to set during first boot, in
place of prompting the user.</para></listitem>
</varlistentry>
</variablelist>
<para>Note that by default the <filename>systemd-firstboot.service</filename> unit file is set up to
inherit the listed 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=firstboot.locale:de_DE.UTF-8 …</programlisting></para>
<para>Note that these credentials are only read and applied during the first boot process. Once they are
applied they remain applied for subsequent boots, and the credentials are not considered anymore.</para>
</refsect1>
<refsect1>

View file

@ -1487,7 +1487,31 @@ After=sys-subsystem-net-devices-ens1.device</programlisting>
<para>In order to embed binary data into the credential data for <option>--set-credential=</option>
use C-style escaping (i.e. <literal>\n</literal> to embed a newline, or <literal>\x00</literal> to
embed a <constant>NUL</constant> byte. Note that the invoking shell might already apply unescaping
once, hence this might require double escaping!).</para></listitem>
once, hence this might require double escaping!).</para>
<para>The
<citerefentry><refentrytitle>systemd-sysusers.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
and
<citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
services read credentials configured this way for the purpose of configuring the container's root
user's password and shell, as well as system locale, keymap and timezone during the first boot
process of the container. This is particularly useful in combination with
<option>--volatile=yes</option> where every single boot appears as first boot, since configuration
applied to <filename>/etc/</filename> is lost on container reboot cycles. See the respective man
pages for details. Example:</para>
<programlisting># systemd-nspawn -i image.raw \
--volatile=yes \
--set-credential=firstboot.locale:de_DE.UTF-8 \
--set-credential=passwd.hashed-password.root:'$y$j9T$yAuRJu1o5HioZAGDYPU5d.$F64ni6J2y2nNQve90M/p0ZP0ECP/qqzipNyaY9fjGpC' \
-b</programlisting>
<para>The above command line will invoke the specified image file <filename>image.raw</filename> in
volatile mode, i.e with an empty <filename>/etc/</filename> and <filename>/var/</filename>, so that
the container's payload recognizes this as first boot condition, and will invoke
<filename>systemd-firstboot.service</filename>, which then read the two passed credentials to
configure the system's initial locale and root password.</para>
</listitem>
</varlistentry>
</variablelist>

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

@ -2821,7 +2821,7 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
<variablelist class='unit-directives'>
<varlistentry>
<term><varname>LoadCredential=</varname><replaceable>ID</replaceable>:<replaceable>PATH</replaceable></term>
<term><varname>LoadCredential=</varname><replaceable>ID</replaceable><optional>:<replaceable>PATH</replaceable></optional></term>
<listitem><para>Pass a credential to the unit. Credentials are limited-size binary or textual objects
that may be passed to unit processes. They are primarily used for passing cryptographic keys (both
@ -2834,19 +2834,21 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
environment variable to the unit's processes.</para>
<para>The <varname>LoadCredential=</varname> setting takes a textual ID to use as name for a
credential plus a file system path. The ID must be a short ASCII string suitable as filename in the
filesystem, and may be chosen freely by the user. If the specified path is absolute it is opened as
regular file and the credential data is read from it. If the absolute path refers to an
<constant>AF_UNIX</constant> stream socket in the file system a connection is made to it (only once
at unit start-up) and the credential data read from the connection, providing an easy IPC integration
point for dynamically providing credentials from other services. If the specified path is not
absolute and itself qualifies as valid credential identifier it is understood to refer to a
credential that the service manager itself received via the <varname>$CREDENTIALS_DIRECTORY</varname>
environment variable, which may be used to propagate credentials from an invoking environment (e.g. a
container manager that invoked the service manager) into a service. The contents of the file/socket
may be arbitrary binary or textual data, including newline characters and <constant>NUL</constant>
bytes. This option may be used multiple times, each time defining an additional credential to pass to
the unit.</para>
credential plus a file system path, separated by a colon. The ID must be a short ASCII string
suitable as filename in the filesystem, and may be chosen freely by the user. If the specified path
is absolute it is opened as regular file and the credential data is read from it. If the absolute
path refers to an <constant>AF_UNIX</constant> stream socket in the file system a connection is made
to it (only once at unit start-up) and the credential data read from the connection, providing an
easy IPC integration point for dynamically providing credentials from other services. If the
specified path is not absolute and itself qualifies as valid credential identifier it is understood
to refer to a credential that the service manager itself received via the
<varname>$CREDENTIALS_DIRECTORY</varname> environment variable, which may be used to propagate
credentials from an invoking environment (e.g. a container manager that invoked the service manager)
into a service. The contents of the file/socket may be arbitrary binary or textual data, including
newline characters and <constant>NUL</constant> bytes. If the file system path is omitted it is
chosen identical to the credential name, i.e. this is a terse way do declare credentials to inherit
from the service manager into a service. This option may be used multiple times, each time defining
an additional credential to pass to the unit.</para>
<para>The credential files/IPC sockets must be accessible to the service manager, but don't have to
be directly accessible to the unit's processes: the credential data is read and copied into separate,

View file

@ -12,10 +12,12 @@
#include "main-func.h"
#include "pretty-print.h"
#include "strv.h"
#include "terminal-util.h"
static const char *arg_icon = NULL;
static const char *arg_id = NULL;
static const char *arg_keyname = NULL;
static const char *arg_id = NULL; /* identifier for 'ask-password' protocol */
static const char *arg_key_name = NULL; /* name in kernel keyring */
static const char *arg_credential_name = NULL; /* name in $CREDENTIALS_DIRECTORY directory */
static char *arg_message = NULL;
static usec_t arg_timeout = DEFAULT_TIMEOUT_USEC;
static bool arg_multiple = false;
@ -32,21 +34,26 @@ static int help(void) {
if (r < 0)
return log_oom();
printf("%s [OPTIONS...] MESSAGE\n\n"
"Query the user for a system passphrase, via the TTY or an UI agent.\n\n"
printf("%1$s [OPTIONS...] MESSAGE\n\n"
"%3$sQuery the user for a system passphrase, via the TTY or an UI agent.%4$s\n\n"
" -h --help Show this help\n"
" --icon=NAME Icon name\n"
" --id=ID Query identifier (e.g. \"cryptsetup:/dev/sda5\")\n"
" --keyname=NAME Kernel key name for caching passwords (e.g. \"cryptsetup\")\n"
" --credential=NAME\n"
" Credential name for LoadCredential=/SetCredential=\n"
" credentials\n"
" --timeout=SEC Timeout in seconds\n"
" --echo Do not mask input (useful for usernames)\n"
" --no-tty Ask question via agent even on TTY\n"
" --accept-cached Accept cached passwords\n"
" --multiple List multiple passwords if available\n"
" --no-output Do not print password to standard output\n"
"\nSee the %s for details.\n",
"\nSee the %2$s for details.\n",
program_invocation_short_name,
link);
link,
ansi_highlight(),
ansi_normal());
return 0;
}
@ -64,6 +71,7 @@ static int parse_argv(int argc, char *argv[]) {
ARG_KEYNAME,
ARG_NO_OUTPUT,
ARG_VERSION,
ARG_CREDENTIAL,
};
static const struct option options[] = {
@ -78,6 +86,7 @@ static int parse_argv(int argc, char *argv[]) {
{ "id", required_argument, NULL, ARG_ID },
{ "keyname", required_argument, NULL, ARG_KEYNAME },
{ "no-output", no_argument, NULL, ARG_NO_OUTPUT },
{ "credential", required_argument, NULL, ARG_CREDENTIAL },
{}
};
@ -128,13 +137,17 @@ static int parse_argv(int argc, char *argv[]) {
break;
case ARG_KEYNAME:
arg_keyname = optarg;
arg_key_name = optarg;
break;
case ARG_NO_OUTPUT:
arg_no_output = true;
break;
case ARG_CREDENTIAL:
arg_credential_name = optarg;
break;
case '?':
return -EINVAL;
@ -170,7 +183,7 @@ static int run(int argc, char *argv[]) {
else
timeout = 0;
r = ask_password_auto(arg_message, arg_icon, arg_id, arg_keyname, timeout, arg_flags, &l);
r = ask_password_auto(arg_message, arg_icon, arg_id, arg_key_name, arg_credential_name ?: "password", timeout, arg_flags, &l);
if (r < 0)
return log_error_errno(r, "Failed to query password: %m");

54
src/basic/creds-util.c Normal file
View file

@ -0,0 +1,54 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "creds-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "path-util.h"
bool credential_name_valid(const char *s) {
/* We want that credential names are both valid in filenames (since that's our primary way to pass
* them around) and as fdnames (which is how we might want to pass them around eventually) */
return filename_is_valid(s) && fdname_is_valid(s);
}
int get_credentials_dir(const char **ret) {
const char *e;
assert(ret);
e = secure_getenv("CREDENTIALS_DIRECTORY");
if (!e)
return -ENXIO;
if (!path_is_absolute(e) || !path_is_normalized(e))
return -EINVAL;
*ret = e;
return 0;
}
int read_credential(const char *name, void **ret, size_t *ret_size) {
_cleanup_free_ char *fn = NULL;
const char *d;
int r;
assert(ret);
if (!credential_name_valid(name))
return -EINVAL;
r = get_credentials_dir(&d);
if (r < 0)
return r;
fn = path_join(d, name);
if (!fn)
return -ENOMEM;
return read_full_file_full(
AT_FDCWD, fn,
UINT64_MAX, SIZE_MAX,
READ_FULL_FILE_SECURE,
NULL,
(char**) ret, ret_size);
}

12
src/basic/creds-util.h Normal file
View file

@ -0,0 +1,12 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
#include <inttypes.h>
#include <stdbool.h>
#include <sys/types.h>
bool credential_name_valid(const char *s);
int get_credentials_dir(const char **ret);
int read_credential(const char *name, void **ret, size_t *ret_size);

View file

@ -35,6 +35,8 @@ basic_sources = files('''
conf-files.h
copy.c
copy.h
creds-util.c
creds-util.h
def.h
device-nodes.c
device-nodes.h

View file

@ -1190,9 +1190,3 @@ bool prefixed_path_strv_contains(char **l, const char *path) {
return false;
}
bool credential_name_valid(const char *s) {
/* We want that credential names are both valid in filenames (since that's our primary way to pass
* them around) and as fdnames (which is how we might want to pass them around eventually) */
return filename_is_valid(s) && fdname_is_valid(s);
}

View file

@ -183,5 +183,3 @@ static inline const char *empty_to_root(const char *path) {
bool path_strv_contains(char **l, const char *path);
bool prefixed_path_strv_contains(char **l, const char *path);
bool credential_name_valid(const char *s);

View file

@ -13,6 +13,7 @@
#include "cap-list.h"
#include "capability-util.h"
#include "cpu-set-util.h"
#include "creds-util.h"
#include "dbus-execute.h"
#include "dbus-util.h"
#include "env-util.h"

View file

@ -2564,6 +2564,7 @@ static int acquire_credentials(
ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
_cleanup_(erase_and_freep) char *data = NULL;
_cleanup_free_ char *j = NULL, *bindname = NULL;
bool missing_ok = true;
const char *source;
size_t size, add;
@ -2577,6 +2578,8 @@ static int acquire_credentials(
if (asprintf(&bindname, "@%" PRIx64"/unit/%s/%s", random_u64(), unit, *id) < 0)
return -ENOMEM;
missing_ok = false;
} else if (params->received_credentials) {
/* If this is a relative path, take it relative to the credentials we received
* ourselves. We don't support the AF_UNIX stuff in this mode, since we are operating
@ -2589,16 +2592,23 @@ static int acquire_credentials(
} else
source = NULL;
if (source)
r = read_full_file_full(AT_FDCWD, source, UINT64_MAX, SIZE_MAX, flags, bindname, &data, &size);
else
r = -ENOENT;
if (r == -ENOENT &&
faccessat(dfd, *id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) /* If the source file doesn't exist, but we already acquired the key otherwise, then don't fail */
if (r == -ENOENT && (missing_ok || faccessat(dfd, *id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0)) {
/* Make a missing inherited credential non-fatal, let's just continue. After all apps
* will get clear errors if we don't pass such a missing credential on as they
* themselves will get ENOENT when trying to read them, which should not be much
* worse than when we handle the error here and make it fatal.
*
* Also, if the source file doesn't exist, but we already acquired the key otherwise,
* then don't fail either. */
log_debug_errno(r, "Couldn't read inherited credential '%s', skipping: %m", *fn);
continue;
}
if (r < 0)
return r;
return log_debug_errno(r, "Failed to read credential '%s': %m", *fn);
add = strlen(*id) + size;
if (add > left)

View file

@ -16,8 +16,8 @@
#include "sd-messages.h"
#include "af-list.h"
#include "alloc-util.h"
#include "all-units.h"
#include "alloc-util.h"
#include "bpf-firewall.h"
#include "bus-error.h"
#include "bus-internal.h"
@ -28,6 +28,7 @@
#include "conf-parser.h"
#include "core-varlink.h"
#include "cpu-set-util.h"
#include "creds-util.h"
#include "env-util.h"
#include "errno-list.h"
#include "escape.h"
@ -4607,14 +4608,23 @@ int config_parse_load_credential(
log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", k);
return 0;
}
r = unit_full_printf(u, p, &q);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", p);
return 0;
}
if (path_is_absolute(q) ? !path_is_normalized(q) : !credential_name_valid(q)) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Credential source \"%s\" not valid, ignoring.", q);
return 0;
if (isempty(p)) {
/* If only one field field is specified take it as shortcut for inheriting a credential named
* the same way from our parent */
q = strdup(k);
if (!q)
return log_oom();
} else {
r = unit_full_printf(u, p, &q);
if (r < 0) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", p);
return 0;
}
if (path_is_absolute(q) ? !path_is_normalized(q) : !credential_name_valid(q)) {
log_syntax(unit, LOG_WARNING, filename, line, r, "Credential source \"%s\" not valid, ignoring.", q);
return 0;
}
}
r = strv_consume_pair(&context->load_credentials, TAKE_PTR(k), TAKE_PTR(q));

View file

@ -30,6 +30,7 @@
#include "clean-ipc.h"
#include "clock-util.h"
#include "core-varlink.h"
#include "creds-util.h"
#include "dbus-job.h"
#include "dbus-manager.h"
#include "dbus-unit.h"
@ -49,8 +50,8 @@
#include "install.h"
#include "io-util.h"
#include "label.h"
#include "locale-setup.h"
#include "load-fragment.h"
#include "locale-setup.h"
#include "log.h"
#include "macro.h"
#include "manager.h"
@ -852,8 +853,8 @@ int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager
if (r < 0)
return r;
e = secure_getenv("CREDENTIALS_DIRECTORY");
if (e) {
r = get_credentials_dir(&e);
if (r >= 0) {
m->received_credentials = strdup(e);
if (!m->received_credentials)
return -ENOMEM;

View file

@ -57,7 +57,7 @@ int enroll_password(
if (!question)
return log_oom();
r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY, 0, &passwords);
r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", "cryptenroll.new-passphrase", USEC_INFINITY, 0, &passwords);
if (r < 0)
return log_error_errno(r, "Failed to query password: %m");
@ -68,7 +68,7 @@ int enroll_password(
if (!question)
return log_oom();
r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY, 0, &passwords2);
r = ask_password_auto(question, "drive-harddisk", id, "cryptenroll", "cryptenroll.new-passphrase", USEC_INFINITY, 0, &passwords2);
if (r < 0)
return log_error_errno(r, "Failed to query password: %m");

View file

@ -417,7 +417,7 @@ static int prepare_luks(
"Too many attempts, giving up:");
r = ask_password_auto(
question, "drive-harddisk", id, "cryptenroll", USEC_INFINITY,
question, "drive-harddisk", id, "cryptenroll", "cryptenroll.passphrase", USEC_INFINITY,
ask_password_flags,
&passwords);
if (r < 0)

View file

@ -88,7 +88,7 @@ int acquire_fido2_key(
pins = strv_free_erase(pins);
r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", until, flags, &pins);
r = ask_password_auto("Please enter security token PIN:", "drive-harddisk", NULL, "fido2-pin", "cryptsetup.fido2-pin", until, flags, &pins);
if (r < 0)
return log_error_errno(r, "Failed to ask for user password: %m");

View file

@ -70,6 +70,7 @@ static int pkcs11_callback(
data->friendly_name,
"drive-harddisk",
"pkcs11-pin",
"cryptsetup.pkcs11-pin",
data->until,
NULL);
if (r < 0)

View file

@ -545,7 +545,7 @@ static int get_password(
id = strjoina("cryptsetup:", disk_path);
r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until,
r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", "cryptsetup.passphrase", until,
ASK_PASSWORD_PUSH_CACHE | (accept_cached*ASK_PASSWORD_ACCEPT_CACHED),
&passwords);
if (r < 0)
@ -561,7 +561,7 @@ static int get_password(
id = strjoina("cryptsetup-verification:", disk_path);
r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", until, ASK_PASSWORD_PUSH_CACHE, &passwords2);
r = ask_password_auto(text, "drive-harddisk", id, "cryptsetup", "cryptsetup.passphrase", until, ASK_PASSWORD_PUSH_CACHE, &passwords2);
if (r < 0)
return log_error_errno(r, "Failed to query verification password: %m");

View file

@ -10,6 +10,7 @@
#include "alloc-util.h"
#include "ask-password-api.h"
#include "copy.h"
#include "creds-util.h"
#include "dissect-image.h"
#include "env-file.h"
#include "fd-util.h"
@ -43,8 +44,8 @@
static char *arg_root = NULL;
static char *arg_image = NULL;
static char *arg_locale = NULL; /* $LANG */
static char *arg_keymap = NULL;
static char *arg_locale_messages = NULL; /* $LC_MESSAGES */
static char *arg_keymap = NULL;
static char *arg_timezone = NULL;
static char *arg_hostname = NULL;
static sd_id128_t arg_machine_id = {};
@ -232,11 +233,29 @@ static bool locale_is_ok(const char *name) {
static int prompt_locale(void) {
_cleanup_strv_free_ char **locales = NULL;
bool acquired_from_creds = false;
int r;
if (arg_locale || arg_locale_messages)
return 0;
r = read_credential("firstboot.locale", (void**) &arg_locale, NULL);
if (r < 0)
log_debug_errno(r, "Failed to read credential firstboot.locale, ignoring: %m");
else
acquired_from_creds = true;
r = read_credential("firstboot.locale-messages", (void**) &arg_locale_messages, NULL);
if (r < 0)
log_debug_errno(r, "Failed to read credential firstboot.locale-message, ignoring: %m");
else
acquired_from_creds = true;
if (acquired_from_creds) {
log_debug("Acquired locale from credentials.");
return 0;
}
if (!arg_prompt_locale)
return 0;
@ -336,6 +355,14 @@ static int prompt_keymap(void) {
if (arg_keymap)
return 0;
r = read_credential("firstboot.keymap", (void**) &arg_keymap, NULL);
if (r < 0)
log_debug_errno(r, "Failed to read credential firstboot.keymap, ignoring: %m");
else {
log_debug("Acquired keymap from credential.");
return 0;
}
if (!arg_prompt_keymap)
return 0;
@ -407,6 +434,14 @@ static int prompt_timezone(void) {
if (arg_timezone)
return 0;
r = read_credential("firstboot.timezone", (void**) &arg_timezone, NULL);
if (r < 0)
log_debug_errno(r, "Failed to read credential firstboot.timezone, ignoring: %m");
else {
log_debug("Acquired timezone from credential.");
return 0;
}
if (!arg_prompt_timezone)
return 0;
@ -558,6 +593,22 @@ static int prompt_root_password(void) {
if (arg_root_password)
return 0;
r = read_credential("passwd.hashed-password.root", (void**) &arg_root_password, NULL);
if (r == -ENOENT) {
r = read_credential("passwd.plaintext-password.root", (void**) &arg_root_password, NULL);
if (r < 0)
log_debug_errno(r, "Couldn't read credential 'passwd.{hashed|plaintext}-password.root', ignoring: %m");
else {
arg_root_password_is_hashed = false;
return 0;
}
} else if (r < 0)
log_debug_errno(r, "Couldn't read credential 'passwd.hashed-password.root', ignoring: %m");
else {
arg_root_password_is_hashed = true;
return 0;
}
if (!arg_prompt_root_password)
return 0;
@ -631,7 +682,18 @@ static int find_shell(const char *path, const char *root) {
static int prompt_root_shell(void) {
int r;
if (arg_root_shell || !arg_prompt_root_shell)
if (arg_root_shell)
return 0;
r = read_credential("passwd.shell.root", (void**) &arg_root_shell, NULL);
if (r < 0)
log_debug_errno(r, "Failed to read credential passwd.shell.root, ignoring: %m");
else {
log_debug("Acquired root shell from credential.");
return 0;
}
if (!arg_prompt_root_shell)
return 0;
print_welcome();

View file

@ -221,7 +221,7 @@ static int acquire_existing_password(const char *user_name, UserRecord *hr, bool
user_name) < 0)
return log_oom();
r = ask_password_auto(question, "user-home", NULL, "home-password", USEC_INFINITY, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &password);
r = ask_password_auto(question, "user-home", NULL, "home-password", "home.password", USEC_INFINITY, ASK_PASSWORD_ACCEPT_CACHED|ASK_PASSWORD_PUSH_CACHE, &password);
if (r < 0)
return log_error_errno(r, "Failed to acquire password: %m");
@ -257,7 +257,7 @@ static int acquire_token_pin(const char *user_name, UserRecord *hr) {
return log_oom();
/* We never cache or use cached PINs, since usually there are only very few attempts allowed before the PIN is blocked */
r = ask_password_auto(question, "user-home", NULL, "token-pin", USEC_INFINITY, 0, &pin);
r = ask_password_auto(question, "user-home", NULL, "token-pin", "home.token-pin", USEC_INFINITY, 0, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire security token PIN: %m");
@ -1010,7 +1010,7 @@ static int acquire_new_password(
if (asprintf(&question, "Please enter new password for user %s:", user_name) < 0)
return log_oom();
r = ask_password_auto(question, "user-home", NULL, "home-password", USEC_INFINITY, 0, &first);
r = ask_password_auto(question, "user-home", NULL, "home-password", "home.new-password", USEC_INFINITY, 0, &first);
if (r < 0)
return log_error_errno(r, "Failed to acquire password: %m");
@ -1018,7 +1018,7 @@ static int acquire_new_password(
if (asprintf(&question, "Please enter new password for user %s (repeat):", user_name) < 0)
return log_oom();
r = ask_password_auto(question, "user-home", NULL, "home-password", USEC_INFINITY, 0, &second);
r = ask_password_auto(question, "user-home", NULL, "home-password", "home.new-password", USEC_INFINITY, 0, &second);
if (r < 0)
return log_error_errno(r, "Failed to acquire password: %m");

View file

@ -35,6 +35,7 @@
#include "cgroup-util.h"
#include "copy.h"
#include "cpu-set-util.h"
#include "creds-util.h"
#include "dev-setup.h"
#include "discover-image.h"
#include "dissect-image.h"
@ -1592,9 +1593,9 @@ static int parse_argv(int argc, char *argv[]) {
else {
const char *e;
e = getenv("CREDENTIALS_DIRECTORY");
if (!e)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential not available (no credentials passed at all): %s", word);
r = get_credentials_dir(&e);
if (r < 0)
return log_error_errno(r, "Credential not available (no credentials passed at all): %s", word);
j = path_join(e, p);
if (!j)

View file

@ -20,6 +20,7 @@
#include "alloc-util.h"
#include "ask-password-api.h"
#include "creds-util.h"
#include "def.h"
#include "fd-util.h"
#include "fileio.h"
@ -971,11 +972,33 @@ finish:
return r;
}
static int ask_password_credential(const char *credential_name, AskPasswordFlags flags, char ***ret) {
_cleanup_(erase_and_freep) char *buffer = NULL;
size_t size;
char **l;
int r;
assert(credential_name);
assert(ret);
r = read_credential(credential_name, (void**) &buffer, &size);
if (IN_SET(r, -ENXIO, -ENOENT)) /* No credentials passed or this credential not defined? */
return -ENOKEY;
l = strv_parse_nulstr(buffer, size);
if (!l)
return -ENOMEM;
*ret = l;
return 0;
}
int ask_password_auto(
const char *message,
const char *icon,
const char *id,
const char *keyname,
const char *id, /* id in "ask-password" protocol */
const char *key_name, /* name in kernel keyring */
const char *credential_name, /* name in $CREDENTIALS_DIRECTORY directory */
usec_t until,
AskPasswordFlags flags,
char ***ret) {
@ -984,20 +1007,26 @@ int ask_password_auto(
assert(ret);
if (!(flags & ASK_PASSWORD_NO_CREDENTIAL) && credential_name) {
r = ask_password_credential(credential_name, flags, ret);
if (r != -ENOKEY)
return r;
}
if ((flags & ASK_PASSWORD_ACCEPT_CACHED) &&
keyname &&
key_name &&
((flags & ASK_PASSWORD_NO_TTY) || !isatty(STDIN_FILENO)) &&
(flags & ASK_PASSWORD_NO_AGENT)) {
r = ask_password_keyring(keyname, flags, ret);
r = ask_password_keyring(key_name, flags, ret);
if (r != -ENOKEY)
return r;
}
if (!(flags & ASK_PASSWORD_NO_TTY) && isatty(STDIN_FILENO))
return ask_password_tty(-1, message, keyname, until, flags, NULL, ret);
return ask_password_tty(-1, message, key_name, until, flags, NULL, ret);
if (!(flags & ASK_PASSWORD_NO_AGENT))
return ask_password_agent(message, icon, id, keyname, until, flags, ret);
return ask_password_agent(message, icon, id, key_name, until, flags, ret);
return -EUNATCH;
}

View file

@ -6,16 +6,17 @@
#include "time-util.h"
typedef enum AskPasswordFlags {
ASK_PASSWORD_ACCEPT_CACHED = 1 << 0,
ASK_PASSWORD_PUSH_CACHE = 1 << 1,
ASK_PASSWORD_ACCEPT_CACHED = 1 << 0, /* read from kernel keyring */
ASK_PASSWORD_PUSH_CACHE = 1 << 1, /* write to kernel keyring after getting password from elsewhere */
ASK_PASSWORD_ECHO = 1 << 2, /* show the password literally while reading, instead of "*" */
ASK_PASSWORD_SILENT = 1 << 3, /* do no show any password at all while reading */
ASK_PASSWORD_NO_TTY = 1 << 4,
ASK_PASSWORD_NO_AGENT = 1 << 5,
ASK_PASSWORD_NO_TTY = 1 << 4, /* never ask for password on tty */
ASK_PASSWORD_NO_AGENT = 1 << 5, /* never ask for password via agent */
ASK_PASSWORD_CONSOLE_COLOR = 1 << 6, /* Use color if /dev/console points to a console that supports color */
ASK_PASSWORD_NO_CREDENTIAL = 1 << 7, /* never use $CREDENTIALS_DIRECTORY data */
} AskPasswordFlags;
int ask_password_tty(int tty_fd, const char *message, const char *keyname, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret);
int ask_password_tty(int tty_fd, const char *message, const char *key_name, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret);
int ask_password_plymouth(const char *message, usec_t until, AskPasswordFlags flags, const char *flag_file, char ***ret);
int ask_password_agent(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret);
int ask_password_auto(const char *message, const char *icon, const char *id, const char *keyname, usec_t until, AskPasswordFlags flag, char ***ret);
int ask_password_agent(const char *message, const char *icon, const char *id, const char *key_name, usec_t until, AskPasswordFlags flag, char ***ret);
int ask_password_auto(const char *message, const char *icon, const char *id, const char *key_name, const char *credential_name, usec_t until, AskPasswordFlags flag, char ***ret);

View file

@ -2077,7 +2077,7 @@ int dissected_image_decrypt_interactively(
z = strv_free(z);
r = ask_password_auto("Please enter image passphrase:", NULL, "dissect", "dissect", USEC_INFINITY, 0, &z);
r = ask_password_auto("Please enter image passphrase:", NULL, "dissect", "dissect", "dissect.passphrase", USEC_INFINITY, 0, &z);
if (r < 0)
return log_error_errno(r, "Failed to query for passphrase: %m");

View file

@ -566,7 +566,7 @@ int fido2_generate_hmac_hash(
if (!has_client_pin)
log_warning("Weird, device asked for client PIN, but does not advertise it as feature. Ignoring.");
r = ask_password_auto("Please enter security token PIN:", askpw_icon_name, NULL, "fido2-pin", USEC_INFINITY, 0, &pin);
r = ask_password_auto("Please enter security token PIN:", askpw_icon_name, NULL, "fido2-pin", "fido2-pin", USEC_INFINITY, 0, &pin);
if (r < 0)
return log_error_errno(r, "Failed to acquire user PIN: %m");

View file

@ -181,7 +181,8 @@ int pkcs11_token_login(
const CK_TOKEN_INFO *token_info,
const char *friendly_name,
const char *icon_name,
const char *keyname,
const char *key_name,
const char *credential_name,
usec_t until,
char **ret_used_pin) {
@ -269,7 +270,7 @@ int pkcs11_token_login(
return log_oom();
/* We never cache PINs, simply because it's fatal if we use wrong PINs, since usually there are only 3 tries */
r = ask_password_auto(text, icon_name, id, keyname, until, 0, &passwords);
r = ask_password_auto(text, icon_name, id, key_name, credential_name, until, 0, &passwords);
if (r < 0)
return log_error_errno(r, "Failed to query PIN for security token '%s': %m", token_label);
}
@ -959,7 +960,7 @@ static int pkcs11_acquire_certificate_callback(
/* Called for every token matching our URI */
r = pkcs11_token_login(m, session, slot_id, token_info, data->askpw_friendly_name, data->askpw_icon_name, "pkcs11-pin", UINT64_MAX, &pin_used);
r = pkcs11_token_login(m, session, slot_id, token_info, data->askpw_friendly_name, data->askpw_icon_name, "pkcs11-pin", "pkcs11-pin", UINT64_MAX, &pin_used);
if (r < 0)
return r;

View file

@ -30,7 +30,7 @@ char *pkcs11_token_label(const CK_TOKEN_INFO *token_info);
char *pkcs11_token_manufacturer_id(const CK_TOKEN_INFO *token_info);
char *pkcs11_token_model(const CK_TOKEN_INFO *token_info);
int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *keyname, usec_t until, char **ret_used_pin);
int pkcs11_token_login(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, CK_SLOT_ID slotid, const CK_TOKEN_INFO *token_info, const char *friendly_name, const char *icon_name, const char *key_name, const char *credential_name, usec_t until, char **ret_used_pin);
int pkcs11_token_find_x509_certificate(CK_FUNCTION_LIST *m, CK_SESSION_HANDLE session, P11KitUri *search_uri, CK_OBJECT_HANDLE *ret_object);
#if HAVE_OPENSSL

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

@ -13,7 +13,7 @@ Documentation=man:systemd-firstboot(1)
DefaultDependencies=no
Conflicts=shutdown.target
After=systemd-remount-fs.service
Before=systemd-sysusers.service sysinit.target first-boot-complete.target shutdown.target
Before=systemd-sysusers.service systemd-vconsole-setup.service sysinit.target first-boot-complete.target shutdown.target
Wants=first-boot-complete.target
ConditionPathIsReadWrite=/etc
ConditionFirstBoot=yes
@ -25,3 +25,14 @@ ExecStart=systemd-firstboot --prompt-locale --prompt-timezone --prompt-root-pass
StandardOutput=tty
StandardInput=tty
StandardError=tty
# Optionally, pick up basic fields from credentials 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
LoadCredential=firstboot.locale
LoadCredential=firstboot.locale-messages
LoadCredential=firstboot.keymap
LoadCredential=firstboot.timezone

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