creds,firstboot: add support for ? and $ via credentials
Now that we support the `$` we want to also make this available inside the system.hostname and firstboot.hostname credentials and the firstboot --hostname option. This commit adds it (and also `?`).
This commit is contained in:
@@ -79,6 +79,20 @@
|
||||
<literal>?</literal> characters and separators) stays within this limit. An expanded name that exceeds
|
||||
the limit is considered invalid and the built-in fallback hostname is used.</para>
|
||||
|
||||
<para>Because the name only stays stable as long as the pattern and word lists are unchanged, the
|
||||
preferred way to obtain a stable, generated hostname is to provide the pattern through the
|
||||
<varname>firstboot.hostname</varname> credential (see
|
||||
<citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry>)
|
||||
rather than placing it directly in this file. Concretely, write the pattern to
|
||||
<filename>/etc/credstore/firstboot.hostname</filename> (see the description of
|
||||
<varname>LoadCredential=</varname> in
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for the
|
||||
credential store): when firstboot runs on the live system the credential is resolved once and the
|
||||
concrete result is persisted here, so the word lists may be updated afterwards without changing the
|
||||
hostname. A pattern placed directly in <filename>/etc/hostname</filename> (or provisioned into an offline
|
||||
image) is instead re-evaluated on every boot, which only really makes sense for systems that keep the
|
||||
wordlist and the pattern stable.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v262"/>
|
||||
|
||||
<para>You may use
|
||||
|
||||
@@ -60,7 +60,20 @@
|
||||
hostname, and only has an effect on first boot, unlike <varname>system.hostname</varname> (see
|
||||
below). Read by
|
||||
<citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
and only honoured if no static hostname has been configured before.</para>
|
||||
and only honoured if no static hostname has been configured before. The value may use the wildcard
|
||||
patterns documented in
|
||||
<citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>5</manvolnum></citerefentry> (e.g.
|
||||
<literal>$-$</literal> or <literal>foo-????</literal>). When the credential is applied on the running
|
||||
system (the usual case, by <filename>systemd-firstboot.service</filename> on first boot), the wildcards
|
||||
are resolved against the machine ID and the resulting concrete name is written to
|
||||
<filename>/etc/hostname</filename>. The name is thus <emphasis>persisted</emphasis> once at first boot and
|
||||
does not change on subsequent boots even if the word lists are later updated or the pattern is changed
|
||||
(for example to add more <literal>?</literal>/<literal>$</literal> tokens for additional entropy), which is the recommended
|
||||
way to obtain a stable, per-machine generated hostname. (When
|
||||
<citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>
|
||||
operates on an offline image via <option>--root=</option>/<option>--image=</option>, the target's
|
||||
machine ID is not yet known, so the pattern is written verbatim and, like a pattern placed directly in
|
||||
<filename>/etc/hostname</filename>, re-derived on every boot rather than persisted.)</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v261"/>
|
||||
</listitem>
|
||||
@@ -430,7 +443,15 @@
|
||||
in <filename>/etc/hostname</filename>, if configured, takes precedence over this setting.
|
||||
Interpreted by the service manager (PID 1). For details see
|
||||
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>. Also
|
||||
see <varname>firstboot.hostname</varname> above.</para>
|
||||
see <varname>firstboot.hostname</varname> above. The value may use the wildcard patterns documented
|
||||
in <citerefentry><refentrytitle>hostname</refentrytitle><manvolnum>5</manvolnum></citerefentry>
|
||||
(<literal>?</literal> and <literal>$</literal>), which are expanded deterministically from the
|
||||
machine ID when the credential is applied. As this is a transient hostname that is re-applied on
|
||||
every boot, a wildcard value is re-derived each boot (and may change if the word lists change) and
|
||||
is never persisted; pass an already-resolved name if a stable hostname is required. Note also that
|
||||
the word-list tokens require the word lists to be present, which is generally not the case in the
|
||||
initrd so during initrd the default hostname is used and in late boot the resolved one becomes
|
||||
available.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v254"/>
|
||||
</listitem>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "fs-util.h"
|
||||
#include "glyph-util.h"
|
||||
#include "help-util.h"
|
||||
#include "hostname-setup.h"
|
||||
#include "hostname-util.h"
|
||||
#include "image-policy.h"
|
||||
#include "kbd-util.h"
|
||||
@@ -676,7 +677,7 @@ static int prompt_hostname(int rfd, sd_varlink **mute_console_link) {
|
||||
r = read_credential("firstboot.hostname", (void**) &hn, NULL);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to read credential firstboot.hostname, ignoring: %m");
|
||||
else if (!hostname_is_valid(hn, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK))
|
||||
else if (!hostname_is_valid(hn, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN))
|
||||
log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Hostname '%s' supplied via credential is not valid, ignoring.", hn);
|
||||
else {
|
||||
log_debug("Acquired hostname from credentials.");
|
||||
@@ -738,7 +739,23 @@ static int process_hostname(int rfd, sd_varlink **mute_console_link) {
|
||||
if (isempty(arg_hostname))
|
||||
return 0;
|
||||
|
||||
r = write_string_file_at(pfd, f, arg_hostname,
|
||||
/* On running systems we have a machine ID, so resolve any '?'/'$' wildcards now and persist them.
|
||||
* This "freezes" the name, so later word list updates do not change it. When operating on an offline
|
||||
* image (--root=/--image=) the target's machine ID is not known yet, so write the template verbatim
|
||||
* and let it be resolved on each first boot. */
|
||||
const char *hostname = arg_hostname;
|
||||
_cleanup_free_ char *resolved = NULL;
|
||||
if (!arg_root) {
|
||||
r = hostname_substitute_wildcards(arg_hostname, &resolved);
|
||||
if (r < 0)
|
||||
log_warning_errno(r, "Failed to resolve wildcards in hostname '%s', writing it verbatim: %m", arg_hostname);
|
||||
else if (!hostname_is_valid(resolved, VALID_HOSTNAME_TRAILING_DOT))
|
||||
log_warning("Resolved hostname '%s' is invalid, writing template '%s' verbatim instead.", resolved, arg_hostname);
|
||||
else
|
||||
hostname = resolved;
|
||||
}
|
||||
|
||||
r = write_string_file_at(pfd, f, hostname,
|
||||
WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_SYNC|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_LABEL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to write /etc/hostname: %m");
|
||||
@@ -1407,7 +1424,7 @@ static int parse_argv(int argc, char *argv[]) {
|
||||
break;
|
||||
|
||||
OPTION_LONG("hostname", "NAME", "Set hostname"):
|
||||
if (!hostname_is_valid(opts.arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK))
|
||||
if (!hostname_is_valid(opts.arg, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
|
||||
"Host name %s is not valid.", opts.arg);
|
||||
|
||||
|
||||
@@ -96,11 +96,19 @@ static int acquire_hostname_from_credential(char **ret) {
|
||||
if (r == 0) /* not found */
|
||||
return -ENXIO;
|
||||
|
||||
if (!hostname_is_valid(cred, VALID_HOSTNAME_TRAILING_DOT)) /* check that the hostname we return is valid */
|
||||
if (!hostname_is_valid(cred, VALID_HOSTNAME_TRAILING_DOT|VALID_HOSTNAME_QUESTION_MARK|VALID_HOSTNAME_WORD_TOKEN))
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname specified in system.hostname credential is invalid, ignoring: %s", cred);
|
||||
|
||||
_cleanup_free_ char *substituted = NULL;
|
||||
r = hostname_substitute_wildcards(cred, &substituted);
|
||||
if (r < 0)
|
||||
return log_warning_errno(r, "Failed to substitute wildcards in system.hostname credential, ignoring: %m");
|
||||
|
||||
if (!hostname_is_valid(substituted, VALID_HOSTNAME_TRAILING_DOT)) /* check that the expanded hostname is valid */
|
||||
return log_warning_errno(SYNTHETIC_ERRNO(EBADMSG), "Hostname from system.hostname credential is invalid after expansion, ignoring: %s", substituted);
|
||||
|
||||
log_info("Initializing hostname from credential.");
|
||||
*ret = TAKE_PTR(cred);
|
||||
*ret = TAKE_PTR(substituted);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,14 @@ if ! command -v systemd-firstboot >/dev/null; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
restore_etc_hostname() {
|
||||
if [[ -e /tmp/etc-hostname.bak ]]; then
|
||||
mv /tmp/etc-hostname.bak /etc/hostname
|
||||
elif [[ -e /tmp/etc-hostname.absent ]]; then
|
||||
rm -f /etc/hostname /tmp/etc-hostname.absent
|
||||
fi
|
||||
}
|
||||
|
||||
at_exit() {
|
||||
if [[ -n "${ROOT:-}" ]]; then
|
||||
ls -lR "$ROOT"
|
||||
@@ -22,6 +30,11 @@ at_exit() {
|
||||
rm -rf /etc/otherpath
|
||||
fi
|
||||
|
||||
[[ -n "${WROOT:-}" ]] && rm -rf "$WROOT"
|
||||
[[ -n "${WCREDS:-}" ]] && rm -rf "$WCREDS"
|
||||
[[ -n "${WWORDS:-}" ]] && rm -rf "$WWORDS"
|
||||
restore_etc_hostname
|
||||
|
||||
restore_locale
|
||||
}
|
||||
|
||||
@@ -82,6 +95,57 @@ readlink "$ROOT/etc/localtime" | grep "Europe/Berlin" >/dev/null
|
||||
systemd-firstboot --root="$ROOT" --hostname "foobar"
|
||||
grep -q "foobar" "$ROOT/etc/hostname"
|
||||
|
||||
# Hostname wildcard templates (see hostname(5)) must be accepted by both the --hostname option and the
|
||||
# firstboot.hostname credential (both go through hostname_is_valid() with the wildcard flags). Since these
|
||||
# invocations operate on an offline image (--root=), the template is written verbatim and only resolved
|
||||
# later, on the target's first boot (the resolve-and-freeze path is taken only when running on the live
|
||||
# system, where the machine ID is final).
|
||||
WROOT=test-root-hostname-wildcard
|
||||
|
||||
# '$' word token via --hostname, stored as-is
|
||||
rm -rf "$WROOT"; mkdir -p "$WROOT"
|
||||
systemd-firstboot --root="$WROOT" --hostname='$-$'
|
||||
assert_eq "$(cat "$WROOT/etc/hostname")" '$-$'
|
||||
|
||||
# '?' hex token via --hostname, stored as-is
|
||||
rm -rf "$WROOT"; mkdir -p "$WROOT"
|
||||
systemd-firstboot --root="$WROOT" --hostname='foo-????'
|
||||
assert_eq "$(cat "$WROOT/etc/hostname")" 'foo-????'
|
||||
|
||||
# the firstboot.hostname credential is accepted and written verbatim too
|
||||
rm -rf "$WROOT"; mkdir -p "$WROOT"
|
||||
WCREDS="$(mktemp -d)"
|
||||
echo -n '$-$-????' >"$WCREDS/firstboot.hostname"
|
||||
CREDENTIALS_DIRECTORY="$WCREDS" systemd-firstboot --root="$WROOT"
|
||||
assert_eq "$(cat "$WROOT/etc/hostname")" '$-$-????'
|
||||
rm -rf "$WCREDS"
|
||||
|
||||
# an invalid hostname (disallowed character) is refused and nothing is written
|
||||
rm -rf "$WROOT"; mkdir -p "$WROOT"
|
||||
(! systemd-firstboot --root="$WROOT" --hostname='foo_bar')
|
||||
[[ ! -e "$WROOT/etc/hostname" ]]
|
||||
|
||||
rm -rf "$WROOT"
|
||||
|
||||
# When run on the live system (no --root=) the machine ID is final, so the wildcards are resolved
|
||||
# immediately and the concrete result is persisted, "freezing" the generated name (see hostname(5)).
|
||||
if [[ -f /etc/hostname ]]; then
|
||||
cp /etc/hostname /tmp/etc-hostname.bak
|
||||
else
|
||||
touch /tmp/etc-hostname.absent
|
||||
fi
|
||||
WWORDS="$(mktemp -d)"
|
||||
printf 'wildly\nquietly\n' >"$WWORDS/1"
|
||||
printf 'happy\nsad\n' >"$WWORDS/2"
|
||||
SYSTEMD_HOSTNAME_WORDLIST_PATH="$WWORDS" systemd-firstboot --force --hostname='$-$-????'
|
||||
H="$(cat /etc/hostname)"
|
||||
IFS='-' read -r w1 w2 suffix <<<"$H"
|
||||
grep -Fx -- "$w1" "$WWORDS/1" >/dev/null
|
||||
grep -Fx -- "$w2" "$WWORDS/2" >/dev/null
|
||||
[[ "$suffix" =~ ^[0-9a-f]{4}$ ]]
|
||||
rm -rf "$WWORDS"
|
||||
restore_etc_hostname
|
||||
|
||||
systemd-firstboot --root="$ROOT" --machine-id=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
grep -q "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "$ROOT/etc/machine-id"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user