diff --git a/TODO b/TODO index 856652337b..f3e0ddaac0 100644 --- a/TODO +++ b/TODO @@ -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. diff --git a/man/systemd-ask-password.xml b/man/systemd-ask-password.xml index 433260475d..f241064d8b 100644 --- a/man/systemd-ask-password.xml +++ b/man/systemd-ask-password.xml @@ -138,6 +138,17 @@ directly. Example: --keyname=cryptsetup + + + Configure a credential to read the password from – if it exists. This may be used in + conjunction with the LoadCredential= and SetCredential= + settings in unit files. See + systemd.exec5 for + details. If not specified, defaults to password. This option has no effect if no + credentials directory is passed to the program (i.e. $CREDENTIALS_DIRECTORY is not + set) or if the no credential of the specified name exists. + + diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index 1001924902..46fe0a0682 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -283,7 +283,69 @@ + + + Credentials + + systemd-firstboot supports the service credentials logic as implemented by + LoadCredential=/SetCredential= (see + systemd.exec1 for + details). The following credentials are used when passed in: + + + + passwd.hashed-password.root + passwd.plaintext-password.root + + 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 + systemd-sysusers.service8 + service. + + + + passwd.shell.root + + 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 + systemd-sysusers.service8 + service. + + + + firstboot.locale + firstboot.locale-messages + + These credentials specify the locale settings to set during first boot, in place of + prompting the user. + + + + firstboot.keymap + + This credential specifies the keyboard setting to set during first boot, in place of + prompting the user. + + + + firstboot.timezone + + This credential specifies the system timezone setting to set during first boot, in + place of prompting the user. + + + + Note that by default the systemd-firstboot.service unit file is set up to + inherit the listed credentials + from the service manager. Thus, when invoking a container with an unpopulated /etc/ + for the first time it is possible to configure the root user's password to be systemd + like this: + + # systemd-nspawn --image=… --set-credential=firstboot.locale:de_DE.UTF-8 … + + 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. diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 6a27bab1e3..c4732507df 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -1487,7 +1487,31 @@ After=sys-subsystem-net-devices-ens1.device In order to embed binary data into the credential data for use C-style escaping (i.e. \n to embed a newline, or \x00 to embed a NUL byte. Note that the invoking shell might already apply unescaping - once, hence this might require double escaping!). + once, hence this might require double escaping!). + + The + systemd-sysusers.service8 + and + systemd-firstboot1 + 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 + where every single boot appears as first boot, since configuration + applied to /etc/ is lost on container reboot cycles. See the respective man + pages for details. Example: + + # 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 + + The above command line will invoke the specified image file image.raw in + volatile mode, i.e with an empty /etc/ and /var/, so that + the container's payload recognizes this as first boot condition, and will invoke + systemd-firstboot.service, which then read the two passed credentials to + configure the system's initial locale and root password. + diff --git a/man/systemd-sysusers.xml b/man/systemd-sysusers.xml index 950a8b4499..466a460151 100644 --- a/man/systemd-sysusers.xml +++ b/man/systemd-sysusers.xml @@ -126,7 +126,60 @@ + + + Credentials + + systemd-sysusers supports the service credentials logic as implemented by + LoadCredential=/SetCredential= (see + systemd.exec1 for + details). The following credentials are used when passed in: + + + + passwd.hashed-password.user + A UNIX hashed password string to use for the specified user, when creating an entry + for it. This is particularly useful for the root 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 /etc/ on every boot. + + + + passwd.plaintext-password.user + + Similar to passwd.hashed-password.user + 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. + + + + passwd.shell.user + + Specifies the shell binary to use for the the specified account when creating it. + + + + Note that by default the systemd-sysusers.service unit file is set up to + inherit the passwd.hashed-password.root, + passwd.plaintext-password.root and passwd.shell.root credentials + from the service manager. Thus, when invoking a container with an unpopulated /etc/ + for the first time it is possible to configure the root user's password to be systemd + like this: + + # systemd-nspawn --image=… --set-credential=password.hashed-password.root:'$y$j9T$yAuRJu1o5HioZAGDYPU5d.$F64ni6J2y2nNQve90M/p0ZP0ECP/qqzipNyaY9fjGpC' … + + 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. + + Use mkpasswd1 + for generating UNIX password hashes from the command line. @@ -141,7 +194,9 @@ systemd1, sysusers.d5, - Users, Groups, UIDs and GIDs on systemd systems + Users, Groups, UIDs and GIDs on systemd systems, + systemd.exec1, + mkpasswd1 diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 5bb9af3e7d..6ae630f615 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -2821,7 +2821,7 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy - LoadCredential=ID:PATH + LoadCredential=ID:PATH 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. The LoadCredential= 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 - AF_UNIX 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 $CREDENTIALS_DIRECTORY - 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 NUL - bytes. This option may be used multiple times, each time defining an additional credential to pass to - the unit. + 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 AF_UNIX 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 + $CREDENTIALS_DIRECTORY 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 NUL 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. 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, diff --git a/src/ask-password/ask-password.c b/src/ask-password/ask-password.c index 6b89f57e1b..09bcefbe66 100644 --- a/src/ask-password/ask-password.c +++ b/src/ask-password/ask-password.c @@ -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"); diff --git a/src/basic/creds-util.c b/src/basic/creds-util.c new file mode 100644 index 0000000000..58076705e7 --- /dev/null +++ b/src/basic/creds-util.c @@ -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); +} diff --git a/src/basic/creds-util.h b/src/basic/creds-util.h new file mode 100644 index 0000000000..5e33ca3776 --- /dev/null +++ b/src/basic/creds-util.h @@ -0,0 +1,12 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include +#include +#include + +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); diff --git a/src/basic/meson.build b/src/basic/meson.build index 60ef801a25..a7b8be26ac 100644 --- a/src/basic/meson.build +++ b/src/basic/meson.build @@ -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 diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 50ba44492e..f40f3f27e9 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -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); -} diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 74ee6362ea..c0746f68d7 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -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); diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 4a1585f663..eda21f4734 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -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" diff --git a/src/core/execute.c b/src/core/execute.c index b644b51b01..2152fa8500 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -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) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index ead7f7bf11..1a1e58976a 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -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)); diff --git a/src/core/manager.c b/src/core/manager.c index 629966ea60..57bb25ca25 100644 --- a/src/core/manager.c +++ b/src/core/manager.c @@ -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; diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c index e08f564d3f..0314831174 100644 --- a/src/cryptenroll/cryptenroll-password.c +++ b/src/cryptenroll/cryptenroll-password.c @@ -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"); diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c index a137a41c9d..7d12c427b3 100644 --- a/src/cryptenroll/cryptenroll.c +++ b/src/cryptenroll/cryptenroll.c @@ -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) diff --git a/src/cryptsetup/cryptsetup-fido2.c b/src/cryptsetup/cryptsetup-fido2.c index dfdb964360..82b83ee477 100644 --- a/src/cryptsetup/cryptsetup-fido2.c +++ b/src/cryptsetup/cryptsetup-fido2.c @@ -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"); diff --git a/src/cryptsetup/cryptsetup-pkcs11.c b/src/cryptsetup/cryptsetup-pkcs11.c index 8d7d74fb75..6d7b01176c 100644 --- a/src/cryptsetup/cryptsetup-pkcs11.c +++ b/src/cryptsetup/cryptsetup-pkcs11.c @@ -70,6 +70,7 @@ static int pkcs11_callback( data->friendly_name, "drive-harddisk", "pkcs11-pin", + "cryptsetup.pkcs11-pin", data->until, NULL); if (r < 0) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index dba26a54a3..5c55dcd197 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -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"); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index aa7251d1ef..ba0b360cc1 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -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(); diff --git a/src/home/homectl.c b/src/home/homectl.c index 9d12b9abae..cf1a2d9f9b 100644 --- a/src/home/homectl.c +++ b/src/home/homectl.c @@ -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"); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index f89fa1a573..164a330207 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -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) diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c index 729aa1fb00..04c6b5287e 100644 --- a/src/shared/ask-password-api.c +++ b/src/shared/ask-password-api.c @@ -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; } diff --git a/src/shared/ask-password-api.h b/src/shared/ask-password-api.h index 7aac5e5976..bb507b2e8d 100644 --- a/src/shared/ask-password-api.h +++ b/src/shared/ask-password-api.h @@ -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); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index c4d631c4a9..c022368dfb 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -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"); diff --git a/src/shared/libfido2-util.c b/src/shared/libfido2-util.c index 22b8aba07e..951ed09899 100644 --- a/src/shared/libfido2-util.c +++ b/src/shared/libfido2-util.c @@ -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"); diff --git a/src/shared/pkcs11-util.c b/src/shared/pkcs11-util.c index 27c209cdf8..4fa1effb2d 100644 --- a/src/shared/pkcs11-util.c +++ b/src/shared/pkcs11-util.c @@ -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; diff --git a/src/shared/pkcs11-util.h b/src/shared/pkcs11-util.h index f32ab30429..c2c852f0eb 100644 --- a/src/shared/pkcs11-util.h +++ b/src/shared/pkcs11-util.h @@ -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 diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c index 27573b74e6..8d3086e209 100644 --- a/src/sysusers/sysusers.c +++ b/src/sysusers/sysusers.c @@ -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; diff --git a/units/systemd-firstboot.service b/units/systemd-firstboot.service index 2fea88384f..2e57b064c1 100644 --- a/units/systemd-firstboot.service +++ b/units/systemd-firstboot.service @@ -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 diff --git a/units/systemd-sysusers.service b/units/systemd-sysusers.service index ff5b3db821..47373307b3 100644 --- a/units/systemd-sysusers.service +++ b/units/systemd-sysusers.service @@ -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