Features:
-* use kernel 6.3's "noswap" parameter in tmpfs in place of ramfs for storing
- credentials.
-
-* import-creds: allocate a non-swap-backed fs for /run/credentials/@system,
- like we do for services.
-
* new "systemd-pcrlock" component for dealing with PCR4. Design idea:
1. define /{etc,usr,var/lib}/pcrlock.d/<component>/<version>.pcrlock
2. these files contain list of hashes that will be measured when component is
support .microcode in PE add-ons, so that a microcode update can be shipped
independently of any kernel.
-* add clean mechanism concept for passing env/creds from initrd to host on
- switch root, so that cloud-init and similar have a clean, sane method to pass
- along the stuff they picked up, without patching any dirs. Maybe add
- SwitchRootEx() as new bus call that takes these as argument. When adding
- SwitchRootEx() we should maybe also add a flags param that allows disabling
- and enabling whether serialization is requested during switch root.
+* Maybe add SwitchRootEx() as new bus call that takes env vars to set for new
+ PID 1 as argument. When adding SwitchRootEx() we should maybe also add a
+ flags param that allows disabling and enabling whether serialization is
+ requested during switch root.
* introduce a .acpitable section for early ACPI table override
scenarios. Maybe insist sealing is done additionally against some keypair in
the TPM to which access is updated on each boot, for the next, or so?
-* open up creds for uses in generators, and document clearly that encrypted
- creds are only supported if strictly tpm bound, but not when using the host
- secret (as that is only available if /var/ is around.
-
* logind: when logging in, always take an fd to the home dir, to keep the dir
busy, so that autofs release can never happen. (this is generally a good
idea, and specifically works around the fact the autofs ignores busy by mount
* Process credentials in:
• networkd/udevd: add a way to define additional .link, .network, .netdev files
via the credentials logic.
- • fstab-generator: allow defining additional fstab-like mounts via
- credentials (similar: crypttab-generator, verity-generator,
- integrity-generator)
- • getty-generator: allow defining additional getty instances via a credential
+ • crypttab-generator: allow defining additional crypttab-like volumes via
+ credentials (similar: verity-generator, integrity-generator). Use
+ fstab-generator logic as inspiration.
• run-generator: allow defining additional commands to run via a credential
• resolved: allow defining additional /etc/hosts entries via a credential (it
might make sense to then synthesize a new combined /etc/hosts file in /run
systemd.homed.register or so with JSON user records to automatically
register if not registered yet. Usecase: deploy a system, and add an
account one can directly log into.
- • initialize machine ID from systemd credential picked up from the ESP via
- sd-stub, so that machine ID is stable even on systems where unified kernels
- are used, and hence kernel cmdline cannot be modified locally
• in gpt-auto-generator: check partition uuids against such uuids supplied via
sd-stub credentials. That way, we can support parallel OS installations with
pre-built kernels.
https://0pointer.net/blog/testing-my-system-code-in-usr-without-modifying-usr.html
https://0pointer.net/blog/running-an-container-off-the-host-usr.html
-* add a clear concept how the initrd can make up credentials on their own to
- pass to the system when transitioning into the host OS. usecase: things like
- cloud-init/ignitation and similar can parameterize the host with data they
- acquire.
-
* sd-event: compat wd reuse in inotify code: keep a set of removed watch
descriptors, and clear this set piecemeal when we see the IN_IGNORED event
for it, or when read() returns EAGAIN or on IN_Q_OVERFLOW. Then, whenever we
- kernel-install should be able to pick up initrd sysexts automatically and
place them next to EFI kernel, for sd-stub to pick them up.
- systemd-fstab-generator should look for rootfs device to mount in creds
- - pid 1 should look for machine ID in creds
- systemd-resume-generator should look for resume partition uuid in creds
- sd-stub: automatically pick up microcode from ESP (/loader/microcode/*)
and synthesize initrd from it, and measure it. Signing is not necessary, as
allow it, via `ramfs`.)
7. Credentials may be acquired from a hosting VM hypervisor (SMBIOS OEM strings
- or qemu `fw_cfg`), a hosting container manager, the kernel command line, or
- from the UEFI environment and the EFI System Partition (via
- `systemd-stub`). Such system credentials may then be propagated into
+ or qemu `fw_cfg`), a hosting container manager, the kernel command line,
+ from the initrd, or from the UEFI environment via the EFI System Partition
+ (via `systemd-stub`). Such system credentials may then be propagated into
individual services as needed.
8. Credentials are an effective way to pass parameters into services that run
1. `LoadCredential=` may be used to load a credential from disk, from an
`AF_UNIX` socket, or propagate them from a system credential.
-2. `ImportCredential=` may be used to load one or more (encrypted) credentials
- from disk or from the credential stores.
+2. `ImportCredential=` may be used to load one or more (optionally encrypted)
+ credentials from disk or from the credential stores.
-2. `SetCredential=` may be used to set a credential to a literal string encoded
+3. `SetCredential=` may be used to set a credential to a literal string encoded
in the unit file. Because unit files are world-readable (both on disk and
via D-Bus), this should only be used for credentials that aren't sensitive,
e.g. public keys or certificates, but not private keys.
-3. `LoadCredentialEncrypted=` is similar to `LoadCredential=` but will load an
+4. `LoadCredentialEncrypted=` is similar to `LoadCredential=` but will load an
encrypted credential, and decrypt it before passing it to the service. For
details on credential encryption, see below.
-4. `SetCredentialEncrypted=` is similar to `SetCredential=` but expects an
+5. `SetCredentialEncrypted=` is similar to `SetCredential=` but expects an
encrypted credential to be specified literally. Unlike `SetCredential=` it
is thus safe to be used even for sensitive information, because even though
unit files are world readable, the ciphertext included in them cannot be
supplied as environment variable, use the `%d` specifier in the `Environment=`
setting to build valid paths to specific credentials.
+Encrypted credentials are automatically decrypted/authenticated during service
+activation, so that service code only receives plaintext credentials.
+
+## Programming Interface from Generator Code
+
+[Generators](https://www.freedesktop.org/software/systemd/man/systemd.generator.html)
+may generate native unit files from external configuration or system
+parameters, such as system credentials. Note that they run outside of service
+context, and hence will not receive encrypted credentials in plaintext
+form. Specifically, credentials passed into the system in encrypted form will
+be placed as they are in a directory referenced by the
+`$ENCRYPTED_CREDENTIALS_DIRECTORY` environment variable, and those passed in
+plaintext form will be placed in `$CREDENTIALS_DIRECTORY`. Use a command such
+as `systemd-creds --system cat …` to access both forms of credentials, and
+decrypt them if needed (see
+[systemd-creds(1)](https://www.freedesktop.org/software/systemd/man/systemd-creds.html)
+for details.
+
+Note that generators typically run very early during boot (similar to initrd
+code), earlier than the `/var/` file system is necessarily mounted (which is
+where the system's credential encryption secret is located). Thus it's a good
+idea to encrypt credentials with `systemd-creds encrypt --with-key=auto-initrd`
+if they shall be consumed by a generator, to ensure they are locked to the TPM2
+only, not the credentials secret stored below `/var/`.
+
+For further details about encrypted credentials, see below.
+
## Tools
The
file `/var/lib/systemd/credential.secret`. Moreover, credentials cannot be
prepared on a machine other than the local one.
+Decryption generally takes place at the moment of service activation. This
+means credentials passed to the system can be either encrypted or plaintext and
+remain that way all the way while they are propagated to their consumers, until
+the moment of service activation when they are decrypted and authenticated, so
+that the service only sees plaintext credentials.
+
The `systemd-creds` tool provides the commands `encrypt` and `decrypt` to
encrypt and decrypt/authenticate credentials. Example:
system with a set of credentials that are then propagated to individual
services where they are ultimately consumed.
-`systemd` supports four ways to pass credentials to systems:
+`systemd` supports five ways to pass credentials to systems:
1. A container manager may set the `$CREDENTIALS_DIRECTORY` environment
variable for systemd running as PID 1 in the container, the same way as
invokes. [`systemd-nspawn(1)`](https://www.freedesktop.org/software/systemd/man/systemd-nspawn.html#Credentials)'s
`--set-credential=` and `--load-credential=` switches implement this, in
order to pass arbitrary credentials from host to container payload. Also see
- the [Container Interface](CONTAINER_INTERFACE.md)
- documentation.
+ the [Container Interface](CONTAINER_INTERFACE.md) documentation.
2. Quite similar, VMs can be passed credentials via SMBIOS OEM strings (example
qemu command line switch `-smbios
three of these specific switches would set credential `foo` to `bar`.)
Passing credentials via the SMBIOS mechanism is typically preferable over
`fw_cfg` since it is faster and less specific to the chosen VMM
- implementation. Moreover, `fw_cfg` has a 55 character limitation
- on names passed that way. So some settings may not fit.
+ implementation. Moreover, `fw_cfg` has a 55 character limitation on names
+ passed that way. So some settings may not fit.
-3. Credentials can also be passed into a system via the kernel command line,
- via the `systemd.set-credential=` kernel command line option. Note though
- that any data specified here is visible to any userspace application via
- `/proc/cmdline`. This is hence typically not useful to pass sensitive
- information.
+3. Credentials may be passed from the initrd to the host during the initrd →
+ host transition. Provisioning systems that run in the initrd may use this to
+ install credentials on the system. All files placed in
+ `/run/credentials/@initrd/` are imported into the set of file system
+ credentials during the transition. The files (and their directory) are
+ removed once this is completed.
-4. Credentials may also be passed from the UEFI environment to userspace, if
+5. Credentials may also be passed from the UEFI environment to userspace, if
the
[`systemd-stub`](https://www.freedesktop.org/software/systemd/man/systemd-stub.html)
UEFI kernel stub is used. This allows placing encrypted credentials in the
initrds, as userspace can place credentials next to these EFI kernels, and
be sure they can be accessed securely from initrd context.
+4. Credentials can also be passed into a system via the kernel command line,
+ via the `systemd.set_credential=` and `systemd.set_credential_binary=`
+ kernel command line options (the latter takes Base64 encoded binary
+ data). Note though that any data specified here is visible to all userspace
+ applications (even unprivileged ones) via `/proc/cmdline`. Typically, this
+ is hence not useful to pass sensitive information, and should be avoided.
+
Credentials passed to the system may be enumerated/displayed via `systemd-creds
--system`. They may also be propagated down to services, via the
`LoadCredential=` setting. Example:
will look for the credentials `tmpfiles.extra` with arbitrary tmpfiles.d lines.
Can be encoded in base64 to allow easily passing it on the command line.
+* Further well-known credentials are documented in
+ [`systemd.system-credentials(7)`](https://www.freedesktop.org/software/systemd/man/systemd.system-credentials.html).
+
In future more services are likely to gain support for consuming credentials.
Example:
From *service* perspective the runtime path to find loaded credentials in is
provided in the `$CREDENTIALS_DIRECTORY` environment variable.
+From *generator* perspective the runtime path to find credentials passed into
+the system in plaintext form in is provided in `$CREDENTIALS_DIRECTORY`, and
+those passed into the system in encrypted form is provided in
+`$ENCRYPTED_CREDENTIALS_DIRECTORY`.
+
At runtime, credentials passed to the *system* are placed in
`/run/credentials/@system/` (for regular credentials, such as those passed from
a container manager or via qemu) and `/run/credentials/@encrypted/` (for
`systemd-stub`).
The `ImportCredential=` setting (and the `LoadCredential=` and
-`LoadCredentialEncrypted=` settings when configured with a relative source path)
-will search for the source file to read the credential from automatically. Primarily,
-these credentials are searched among the credentials passed into the system. If
-not found there, they are searched in `/etc/credstore/`, `/run/credstore/`,
+`LoadCredentialEncrypted=` settings when configured with a relative source
+path) will search for the source file to read the credential from
+automatically. Primarily, these credentials are searched among the credentials
+passed into the system. If not found there, they are searched in
+`/etc/credstore/`, `/run/credstore/`,
`/usr/lib/credstore/`. `LoadCredentialEncrypted=` will also search
-`/etc/credstore.encrypted/` and similar directories. `ImportCredential` will search
-both the non-encrypted and encrypted directories. These directories are
+`/etc/credstore.encrypted/` and similar directories. `ImportCredential=` will
+search both the non-encrypted and encrypted directories. These directories are
hence a great place to store credentials to load on the system.
## Conditionalizing Services
KEYBOARD_KEY_ce=f20 # micmute
evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*IdeaPad*Z370*:pvr*
- KEYBOARD_KEY_a0=!mute
- KEYBOARD_KEY_ae=!volumedown
- KEYBOARD_KEY_b0=!volumeup
-
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:*:pvrIdeaPadFlex514IIL05:*
- KEYBOARD_KEY_a0=!mute
- KEYBOARD_KEY_ae=!volumedown
- KEYBOARD_KEY_b0=!volumeup
-
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:*:pvrIdeaPadFlex514ARE05:*
+# Lenovo IdeaPad Flex 5
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:*:pvrIdeaPadFlex5*
+# Lenovo Yoga S940 (9th gen)
+evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:pn81Q7*:pvrLenovoYogaS940:*
KEYBOARD_KEY_a0=!mute
KEYBOARD_KEY_ae=!volumedown
KEYBOARD_KEY_b0=!volumeup
KEYBOARD_KEY_62=unknown # Touchpad on, also emitted by "Ideapad extra buttons", ignore
KEYBOARD_KEY_63=unknown # Touchpad off, also emitted by "Ideapad extra buttons", ignore
-# Fix for volume keys on Lenovo Yoga S940
-# For 10th gen it should be pn81Q8 instead of pn81Q7 but
-# I don't have a device to test
-# perhaps pn81Q* would work for both generations
-evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO:pn81Q7*:pvrLenovoYogaS940:*
- KEYBOARD_KEY_a0=!mute
- KEYBOARD_KEY_ae=!volumedown
- KEYBOARD_KEY_b0=!volumeup
-
# Lenovo Y50-70
evdev:atkbd:dmi:bvn*:bvr*:svnLENOVO*:pn*20378*:pvr*
KEYBOARD_KEY_f3=f21 # Fn+F6 (toggle touchpad)
ACCEL_MOUNT_MATRIX=-1, 0, 0; 0, 1, 0; 0, 0, 1
# Archos 101 Cesium Educ
-sensor:modalias:acpi:KIOX000A*:dmi:*svn*ARCHOS:*pn*ARCHOS101CesiumEduc*
+sensor:modalias:acpi:KIOX000A*:dmi:*:svnARCHOS:pnARCHOS101CesiumEduc:*
ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
#########################################
sensor:modalias:acpi:KIOX000A*:dmi:bvnAmericanMegatrendsInternational,LLC.:bvrJP2V*:svnDefaultstring:*
ACCEL_MOUNT_MATRIX=1, 0, 0; 0, -1, 0; 0, 0, 1
-# T11 / Juno Tablet 2
+# T11 / Juno Tablet 2
sensor:modalias:acpi:NSA2513*:dmi:*:svnDigitek:*
- ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, 1
+ ACCEL_MOUNT_MATRIX=0, -1, 0; 1, 0, 0; 0, 0, 1
#########################################
# DEXP
<variablelist id='environment-variables' />
</refsect1>
+ <refsect1>
+ <title>System Credentials</title>
+
+ <para>System credentials understood by the system and service manager and various other
+ components:</para>
+
+ <variablelist id='system-credentials' />
+ </refsect1>
+
<refsect1>
<title>EFI variables</title>
<term><varname>systemd.setenv=</varname></term>
<term><varname>systemd.machine_id=</varname></term>
<term><varname>systemd.set_credential=</varname></term>
+ <term><varname>systemd.set_credential_binary=</varname></term>
<term><varname>systemd.import_credentials=</varname></term>
<term><varname>systemd.reload_limit_interval_sec=</varname></term>
<term><varname>systemd.reload_limit_burst=</varname></term>
<literal>isolate</literal>,
<literal>ignore-dependencies</literal>,
<literal>ignore-requirements</literal>,
- <literal>flush</literal>, or
- <literal>triggering</literal>. Defaults to
+ <literal>flush</literal>,
+ <literal>triggering</literal>, or
+ <literal>restart-dependencies</literal>. Defaults to
<literal>replace</literal>, except when the
<command>isolate</command> command is used which implies the
<literal>isolate</literal> job mode.</para>
<literal>ignore-dependencies</literal>, but only causes the
requirement dependencies to be ignored, the ordering
dependencies will still be honored.</para>
- </listitem>
<para><literal>triggering</literal> may only be used with
<command>systemctl stop</command>. In this mode, the specified
<varname>Triggers=</varname> in <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
for more information about triggering units.</para>
+ <para><literal>restart-dependencies</literal> may only be used with
+ <command>systemctl start</command>. In this mode, dependencies of
+ the specified unit will receive restart propagation, as if a restart
+ job had been enqueued for the unit.</para>
+ </listitem>
</varlistentry>
<varlistentry>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
- <variablelist>
+ <variablelist class='system-credentials'>
<varlistentry>
- <term><literal>passwd.hashed-password.root</literal></term>
- <term><literal>passwd.plaintext-password.root</literal></term>
+ <term><varname>passwd.hashed-password.root</varname></term>
+ <term><varname>passwd.plaintext-password.root</varname></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
</varlistentry>
<varlistentry>
- <term><literal>passwd.shell.root</literal></term>
+ <term><varname>passwd.shell.root</varname></term>
<listitem><para>Specifies the shell binary to use for the specified account.
Equivalent to the credential of the same name defined for the
</varlistentry>
<varlistentry>
- <term><literal>firstboot.locale</literal></term>
- <term><literal>firstboot.locale-messages</literal></term>
+ <term><varname>firstboot.locale</varname></term>
+ <term><varname>firstboot.locale-messages</varname></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>
+ <term><varname>firstboot.keymap</varname></term>
<listitem><para>This credential specifies the keyboard setting to set during first boot, in place of
prompting the user.</para>
- <para>Note the relationship to the <literal>vconsole.keymap</literal> credential understood by
+ <para>Note the relationship to the <varname>vconsole.keymap</varname> credential understood by
<citerefentry><refentrytitle>systemd-vconsole-setup.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>:
both ultimately affect the same setting, but <varname>firstboot.keymap</varname> is written into
<filename>/etc/vconsole.conf</filename> on first boot (if not already configured), and then read from
</varlistentry>
<varlistentry>
- <term><literal>firstboot.timezone</literal></term>
+ <term><varname>firstboot.timezone</varname></term>
<listitem><para>This credential specifies the system timezone setting to set during first boot, in
place of prompting the user.</para></listitem>
</variablelist>
</refsect1>
+ <refsect1>
+ <title>System Credentials</title>
+
+ <variablelist class='system-credentials'>
+ <varlistentry>
+ <term><varname>fstab.extra</varname></term>
+
+ <listitem><para>This credential may contain addition mounts to establish, in the same format as
+ <citerefentry
+ project='man-pages'><refentrytitle>fstab</refentrytitle><manvolnum>5</manvolnum></citerefentry>, with
+ one mount per line. It is read in addition to <filename>/etc/fstab</filename>.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
<refsect1>
<title>See Also</title>
<para>
</variablelist>
</refsect1>
+ <refsect1>
+ <title>System Credentials</title>
+
+ <variablelist class='system-credentials'>
+ <varlistentry>
+ <term><varname>getty.ttys.serial</varname></term>
+ <term><varname>getty.ttys.container</varname></term>
+
+ <listitem><para>These system credentials may be used to spawn additional login prompts on selected
+ TTYs. The two credentials should contain a newline-separated list of TTY names to spawn instances of
+ <filename>serial-getty@.service</filename> (in case of <varname>getty.ttys.serial</varname>) and
+ <filename>container-getty@.service</filename> (in case of <varname>getty.ttys.container</varname>)
+ on.</para></listitem>
+ </varlistentry>
+ </variablelist>
+ </refsect1>
+
<refsect1>
<title>See Also</title>
<para>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
<citerefentry><refentrytitle>kernel-command-line</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
<citerefentry project='man-pages'><refentrytitle>agetty</refentrytitle><manvolnum>8</manvolnum></citerefentry>
</para>
</refsect1>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
- <variablelist>
+ <variablelist class='system-credentials'>
<varlistentry>
<term><varname>network.dns</varname></term>
<term><varname>network.search_domains</varname></term>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
- <variablelist>
+ <variablelist class='system-credentials'>
<varlistentry>
- <term><literal>sysctl.extra</literal></term>
+ <term><varname>sysctl.extra</varname></term>
<listitem><para>The contents of this credential may contain additional lines to operate on. The
credential contents should follow the same format as any other <filename>sysctl.d/</filename> drop-in
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
- <variablelist>
+ <variablelist class='system-credentials'>
<varlistentry>
- <term><literal>passwd.hashed-password.<replaceable>user</replaceable></literal></term>
+ <term><varname>passwd.hashed-password.<replaceable>user</replaceable></varname></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
</varlistentry>
<varlistentry>
- <term><literal>passwd.plaintext-password.<replaceable>user</replaceable></literal></term>
+ <term><varname>passwd.plaintext-password.<replaceable>user</replaceable></varname></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
</varlistentry>
<varlistentry>
- <term><literal>passwd.shell.<replaceable>user</replaceable></literal></term>
+ <term><varname>passwd.shell.<replaceable>user</replaceable></varname></term>
<listitem><para>Specifies the shell binary to use for the specified account when creating it.</para></listitem>
</varlistentry>
<varlistentry>
- <term><literal>sysusers.extra</literal></term>
+ <term><varname>sysusers.extra</varname></term>
<listitem><para>The contents of this credential may contain additional lines to operate on. The
credential contents should follow the same format as any other <filename>sysusers.d/</filename>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
- <variablelist>
+ <variablelist class='system-credentials'>
<varlistentry>
- <term><literal>tmpfiles.extra</literal></term>
+ <term><varname>tmpfiles.extra</varname></term>
<listitem><para> The contents of this credential may contain additional lines to operate on. The
credential contents should follow the same format as any other <filename>tmpfiles.d/</filename>
(see <citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>1</manvolnum></citerefentry> for
details). The following credentials are used when passed in:</para>
- <variablelist>
+ <variablelist class='system-credentials'>
<varlistentry>
<term><varname>vconsole.keymap</varname></term>
<term><varname>vconsole.keymap_toggle</varname></term>
11) with a prefix of <literal>io.systemd.credential:</literal> or
<literal>io.systemd.credential.binary:</literal>. In both cases a key/value pair separated by
<literal>=</literal> is expected, in the latter case the right-hand side is Base64 decoded when
- parsed (thus permitting binary data to be passed in). Example
- <ulink url="https://www.qemu.org/docs/master/system/index.html">qemu</ulink>
- switch: <literal>-smbios
+ parsed (thus permitting binary data to be passed in). Example <ulink
+ url="https://www.qemu.org/docs/master/system/index.html">qemu</ulink> switch: <literal>-smbios
type=11,value=io.systemd.credential:xx=yy</literal>, or <literal>-smbios
type=11,value=io.systemd.credential.binary:rick=TmV2ZXIgR29ubmEgR2l2ZSBZb3UgVXA=</literal>. Alternatively,
use the <command>qemu</command> <literal>fw_cfg</literal> node
- <literal>opt/io.systemd.credentials/</literal>. Example <command>qemu</command> switch: <literal>-fw_cfg
- name=opt/io.systemd.credentials/mycred,string=supersecret</literal>. They may also be specified on
- the kernel command line using the <literal>systemd.set_credential=</literal> switch (see
- <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>) and from
- the UEFI firmware environment via
- <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para>
+ <literal>opt/io.systemd.credentials/</literal>. Example <command>qemu</command> switch:
+ <literal>-fw_cfg name=opt/io.systemd.credentials/mycred,string=supersecret</literal>. They may also
+ be passed from the UEFI firmware environment via
+ <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
+ from the initrd (see
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>), or be
+ specified on the kernel command line using the <literal>systemd.set_credential=</literal> and
+ <literal>systemd.set_credential_binary=</literal> switches (see
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry> – this is
+ not recommended since unprivileged userspace can read the kernel command line). </para>
<para>If referencing an <constant>AF_UNIX</constant> stream socket to connect to, the connection will
originate from an abstract namespace socket, that includes information about the unit and the
<varname>ConditionArchitecture=</varname> in
<citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para></listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>$CREDENTIALS_DIRECTORY</varname></term>
+ <term><varname>$ENCRYPTED_CREDENTIALS_DIRECTORY</varname></term>
+
+ <listitem><para>If set, refers to the directory system credentials have been placed in. Credentials
+ passed into the system in plaintext form will be placed in <varname>$CREDENTIALS_DIRECTORY</varname>,
+ and those passed in in encrypted form will be placed in
+ <varname>$ENCRYPTED_CREDENTIALS_DIRECTORY</varname>. Use the
+ <citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry>
+ command to automatically decrypt/authenticate credentials passed in, if needed. Specifically, use the
+ <command>systemd-creds --system cat</command> command.</para></listitem>
+ </varlistentry>
+
</variablelist>
</refsect1>
<refsect1>
<title>Well known system credentials</title>
- <variablelist>
+ <variablelist class='system-credentials'>
<varlistentry>
<term><varname>firstboot.keymap</varname></term>
<listitem>
<varlistentry>
<term><varname>firstboot.locale</varname></term>
- <term><varname>firstboot.locale-message</varname></term>
+ <term><varname>firstboot.locale-messages</varname></term>
<listitem>
<para>The system locale to set (e.g. <literal>de_DE.UTF-8</literal>). Read by
<citerefentry><refentrytitle>systemd-firstboot</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>fstab.extra</varname></term>
+
+ <listitem>
+ <para>Additional mounts to establish at boot. For details, see
+ <citerefentry><refentrytitle>systemd-fstab-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+ </listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>vconsole.keymap</varname></term>
<term><varname>vconsole.keymap_toggle</varname></term>
</listitem>
</varlistentry>
+ <varlistentry>
+ <term><varname>getty.ttys.serial</varname></term>
+ <term><varname>getty.ttys.container</varname></term>
+
+ <listitem><para>Used for spawning additional login prompts, see
+ <citerefentry><refentrytitle>systemd-getty-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry> for details.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><varname>vmm.notify_socket</varname></term>
<listitem>
<citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>system.machine_id</varname></term>
+ <listitem>
+ <para>Takes a 128bit ID to initialize the machine ID from (if it is not set yet). Interpreted by
+ the service manager (PID 1). For details see
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>.</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
<varlistentry>
<term><varname>systemd.set_credential=</varname></term>
+ <term><varname>systemd.set_credential_binary=</varname></term>
<listitem><para>Sets a system credential, which can then be propagated to system services using the
<varname>ImportCredential=</varname> or <varname>LoadCredential=</varname> setting, see
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
- details. Takes a pair of credential name and value, separated by a colon. Note that the kernel
- command line is typically accessible by unprivileged programs in
+ details. Takes a pair of credential name and value, separated by a colon. The
+ <varname>systemd.set_credential=</varname> parameter expects the credential value in literal text
+ form, the <varname>systemd.set_credential_binary=</varname> parameter takes binary data encoded in
+ Base64. Note that the kernel command line is typically accessible by unprivileged programs in
<filename>/proc/cmdline</filename>. Thus, this mechanism is not suitable for transferring sensitive
data. Use it only for data that is not sensitive (e.g. public keys/certificates, rather than private
keys), or in testing/debugging environments.</para>
<refsect1>
<title>System credentials</title>
- <para>The service manager when run as PID 1 reads the following system credentials:</para>
+ <para>During initialization the service manager will import credentials from various sources into the
+ system's set of credentials, which can then be propagated into services and consumed by
+ generators:</para>
- <variablelist>
+ <itemizedlist>
+ <listitem><para>When the service manager first initializes it will read system credentials from SMBIOS
+ Type 11 vendor strings
+ <varname>io.systemd.credential:<replaceable>name</replaceable>=<replaceable>value</replaceable></varname>,
+ and
+ <varname>io.systemd.credential.binary:<replaceable>name</replaceable>=<replaceable>value</replaceable></varname>.</para></listitem>
+
+ <listitem><para>At the same time it will import credentials from QEMU <literal>fw_cfg</literal>. (Note
+ that the SMBIOS mechanism is generally preferred, because it is faster and generic.)</para></listitem>
+
+ <listitem><para>Credentials may be passed via the kernel command line, using the
+ <varname>systemd.set-credential=</varname> parameter, see above.</para></listitem>
+
+ <listitem><para>Credentials may be passed from the UEFI environment via
+ <citerefentry><refentrytitle>systemd-stub</refentrytitle><manvolnum>7</manvolnum></citerefentry>.</para></listitem>
+
+ <listitem><para>When the service manager is invoked during the initrd → host transition it will import
+ all files in <filename>/run/credentials/@initrd/</filename> as system credentials.</para></listitem>
+ </itemizedlist>
+
+ <para>Invoke
+ <citerefentry><refentrytitle>systemd-creds</refentrytitle><manvolnum>1</manvolnum></citerefentry> as
+ follows to see the list of credentials passed into the system:</para>
+
+ <programlisting># systemd-creds --system list</programlisting>
+
+ <para>For further information see <ulink url="https://systemd.io/CREDENTIALS">System and Service
+ Credentials</ulink> documentation.</para>
+
+ <para>The service manager when run as PID 1 consumes the following system credentials:</para>
+
+ <variablelist class='system-credentials'>
<varlistentry>
<term><varname>vmm.notify_socket</varname></term>
<listitem>
notification via VSOCK when a virtual machine has finished booting.</para>
</listitem>
</varlistentry>
+
+ <varlistentry>
+ <term><varname>system.machine_id</varname></term>
+ <listitem>
+ <para>Takes a 128bit hexadecimal ID to initialize <filename>/etc/machine-id</filename> from, if the
+ file is not set up yet. See
+ <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
+ details.</para>
+ </listitem>
+ </varlistentry>
</variablelist>
</refsect1>
static int run(const char *dest, const char *dest_early, const char *dest_late) {
- if (in_initrd() > 0) {
+ if (in_initrd()) {
log_debug("Skipping generator, running in the initrd.");
- return 0;
+ return EXIT_SUCCESS;
}
if (detect_container() > 0) {
if (dfd < 0)
return -errno;
+ r = fd_acl_make_writable(dfd); /* Add the "w" bit, if we are reusing an already set up credentials dir where it was unset */
+ if (r < 0)
+ return r;
+
/* First, load credentials off disk (or acquire via AF_UNIX socket) */
HASHMAP_FOREACH(lc, context->load_credentials) {
_cleanup_close_ int sub_fd = -EBADF;
left -= add;
}
- if (fchmod(dfd, 0500) < 0) /* Now take away the "w" bit */
- return -errno;
+ r = fd_acl_make_read_only(dfd); /* Now take away the "w" bit */
+ if (r < 0)
+ return r;
/* After we created all keys with the right perms, also make sure the credential store as a whole is
* accessible */
if (r < 0)
return r;
- r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL);
+ r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false), NULL);
if (r < 0)
return r;
if (workspace_mounted < 0) {
/* Nothing is mounted on the workspace yet, let's try to mount something now */
- for (int try = 0;; try++) {
-
- if (try == 0) {
- /* Try "ramfs" first, since it's not swap backed */
- r = mount_nofollow_verbose(LOG_DEBUG, "ramfs", workspace, "ramfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, "mode=0700");
- if (r >= 0) {
- workspace_mounted = true;
- break;
- }
- } else if (try == 1) {
- _cleanup_free_ char *opts = NULL;
+ r = mount_credentials_fs(workspace, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false);
+ if (r < 0) {
+ /* If that didn't work, try to make a bind mount from the final to the workspace, so that we can make it writable there. */
+ r = mount_nofollow_verbose(LOG_DEBUG, final, workspace, NULL, MS_BIND|MS_REC, NULL);
+ if (r < 0) {
+ if (!ERRNO_IS_PRIVILEGE(r)) /* Propagate anything that isn't a permission problem */
+ return r;
- if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%zu", (size_t) CREDENTIALS_TOTAL_SIZE_MAX) < 0)
- return -ENOMEM;
+ if (must_mount) /* If we it's not OK to use the plain directory
+ * fallback, propagate all errors too */
+ return r;
- /* Fall back to "tmpfs" otherwise */
- r = mount_nofollow_verbose(LOG_DEBUG, "tmpfs", workspace, "tmpfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, opts);
- if (r >= 0) {
- workspace_mounted = true;
- break;
- }
+ /* If we lack privileges to bind mount stuff, then let's gracefully
+ * proceed for compat with container envs, and just use the final dir
+ * as is. */
+ workspace_mounted = false;
} else {
- /* If that didn't work, try to make a bind mount from the final to the workspace, so that we can make it writable there. */
- r = mount_nofollow_verbose(LOG_DEBUG, final, workspace, NULL, MS_BIND|MS_REC, NULL);
- if (r < 0) {
- if (!ERRNO_IS_PRIVILEGE(r)) /* Propagate anything that isn't a permission problem */
- return r;
-
- if (must_mount) /* If we it's not OK to use the plain directory
- * fallback, propagate all errors too */
- return r;
-
- /* If we lack privileges to bind mount stuff, then let's gracefully
- * proceed for compat with container envs, and just use the final dir
- * as is. */
-
- workspace_mounted = false;
- break;
- }
-
/* Make the new bind mount writable (i.e. drop MS_RDONLY) */
- r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL);
+ r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false), NULL);
if (r < 0)
return r;
workspace_mounted = true;
- break;
}
- }
+ } else
+ workspace_mounted = true;
}
assert(!must_mount || workspace_mounted > 0);
if (install) {
/* Make workspace read-only now, so that any bind mount we make from it defaults to read-only too */
- r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL);
+ r = mount_nofollow_verbose(LOG_DEBUG, NULL, workspace, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ true), NULL);
if (r < 0)
return r;
c->target_dir_fd = safe_close(c->target_dir_fd);
}
-static int acquire_encrypted_credential_directory(ImportCredentialContext *c) {
+static int acquire_credential_directory(ImportCredentialContext *c, const char *path, bool with_mount) {
int r;
assert(c);
+ assert(path);
if (c->target_dir_fd >= 0)
return c->target_dir_fd;
- r = mkdir_safe_label(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, 0700, 0, 0, MKDIR_WARN_MODE);
- if (r < 0)
- return log_error_errno(r, "Failed to create " ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY ": %m");
+ r = path_is_mount_point(path, NULL, 0);
+ if (r < 0) {
+ if (r != -ENOENT)
+ return log_error_errno(r, "Failed to determine if %s is a mount point: %m", path);
+
+ r = mkdir_safe_label(path, 0700, 0, 0, MKDIR_WARN_MODE);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create %s mount point: %m", path);
- c->target_dir_fd = open(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
+ r = 0; /* Now it exists and is not a mount point */
+ }
+ if (r > 0)
+ /* If already a mount point, then remount writable */
+ (void) mount_nofollow_verbose(LOG_WARNING, NULL, path, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ false), NULL);
+ else if (with_mount)
+ /* If not a mount point yet, and the credentials are not encrypted, then let's try to mount a no-swap fs there */
+ (void) mount_credentials_fs(path, CREDENTIALS_TOTAL_SIZE_MAX, /* ro= */ false);
+
+ c->target_dir_fd = open(path, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
if (c->target_dir_fd < 0)
- return log_error_errno(errno, "Failed to open " ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY ": %m");
+ return log_error_errno(errno, "Failed to open %s: %m", path);
return c->target_dir_fd;
}
if (r < 0)
log_warning_errno(r, "Failed to make '%s' a mount point, ignoring: %m", dir);
else
- (void) mount_nofollow_verbose(LOG_WARNING, NULL, dir, NULL, MS_BIND|MS_NODEV|MS_NOEXEC|MS_NOSUID|MS_RDONLY|MS_REMOUNT, NULL);
+ (void) mount_nofollow_verbose(LOG_WARNING, NULL, dir, NULL, MS_BIND|MS_REMOUNT|credentials_fs_mount_flags(/* ro= */ true), NULL);
if (setenv(envvar, dir, /* overwrite= */ true) < 0)
return log_error_errno(errno, "Failed to set $%s environment variable: %m", envvar);
if (!credential_size_ok(&context, n, st.st_size))
continue;
- r = acquire_encrypted_credential_directory(&context);
+ r = acquire_credential_directory(&context, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ false);
if (r < 0)
return r;
return 0;
}
-static int acquire_credential_directory(ImportCredentialContext *c) {
- int r;
-
- assert(c);
-
- if (c->target_dir_fd >= 0)
- return c->target_dir_fd;
-
- r = path_is_mount_point(SYSTEM_CREDENTIALS_DIRECTORY, NULL, 0);
- if (r < 0) {
- if (r != -ENOENT)
- return log_error_errno(r, "Failed to determine if " SYSTEM_CREDENTIALS_DIRECTORY " is a mount point: %m");
-
- r = mkdir_safe_label(SYSTEM_CREDENTIALS_DIRECTORY, 0700, 0, 0, MKDIR_WARN_MODE);
- if (r < 0)
- return log_error_errno(r, "Failed to create " SYSTEM_CREDENTIALS_DIRECTORY " mount point: %m");
-
- r = 0; /* Now it exists and is not a mount point */
- }
- if (r == 0)
- /* If not a mountpoint yet, try to mount a ramfs there (so that this stuff isn't swapped
- * out), but if that doesn't work, let's just use the regular tmpfs it already is. */
- (void) mount_nofollow_verbose(LOG_WARNING, "ramfs", SYSTEM_CREDENTIALS_DIRECTORY, "ramfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, "mode=0700");
-
- c->target_dir_fd = open(SYSTEM_CREDENTIALS_DIRECTORY, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
- if (c->target_dir_fd < 0)
- return log_error_errno(errno, "Failed to open " SYSTEM_CREDENTIALS_DIRECTORY ": %m");
-
- return c->target_dir_fd;
-}
-
static int proc_cmdline_callback(const char *key, const char *value, void *data) {
ImportCredentialContext *c = ASSERT_PTR(data);
+ _cleanup_free_ void *binary = NULL;
_cleanup_free_ char *n = NULL;
_cleanup_close_ int nfd = -EBADF;
- const char *colon;
+ const char *colon, *d;
+ bool base64;
size_t l;
int r;
assert(key);
- if (!proc_cmdline_key_streq(key, "systemd.set_credential"))
+ if (proc_cmdline_key_streq(key, "systemd.set_credential"))
+ base64 = false;
+ else if (proc_cmdline_key_streq(key, "systemd.set_credential_binary"))
+ base64 = true;
+ else
return 0;
colon = value ? strchr(value, ':') : NULL;
}
colon++;
- l = strlen(colon);
+
+ if (base64) {
+ r = unbase64mem(colon, SIZE_MAX, &binary, &l);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to decode binary credential '%s' data, ignoring: %m", n);
+ return 0;
+ }
+
+ d = binary;
+ } else {
+ d = colon;
+ l = strlen(colon);
+ }
if (!credential_size_ok(c, n, l))
return 0;
- r = acquire_credential_directory(c);
+ r = acquire_credential_directory(c, SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ true);
if (r < 0)
return r;
if (nfd < 0)
return nfd;
- r = loop_write(nfd, colon, l, /* do_poll= */ false);
+ r = loop_write(nfd, d, l, /* do_poll= */ false);
if (r < 0) {
(void) unlinkat(c->target_dir_fd, n, 0);
return log_error_errno(r, "Failed to write credential: %m");
continue;
}
- r = acquire_credential_directory(c);
+ r = acquire_credential_directory(c, SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ true);
if (r < 0)
return r;
if (!credential_size_ok(c, cn, cdata_len))
continue;
- r = acquire_credential_directory(c);
+ r = acquire_credential_directory(c, SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ true);
if (r < 0)
return r;
return 0;
}
+static int import_credentials_initrd(ImportCredentialContext *c) {
+ _cleanup_free_ DirectoryEntries *de = NULL;
+ _cleanup_close_ int source_dir_fd = -EBADF;
+ int r;
+
+ assert(c);
+
+ /* This imports credentials from /run/credentials/@initrd/ into our credentials directory and deletes
+ * the source directory afterwards. This is run once after the initrd → host transition. This is
+ * supposed to establish a well-defined avenue for initrd-based host configurators to pass
+ * credentials into the main system. */
+
+ if (in_initrd())
+ return 0;
+
+ source_dir_fd = open("/run/credentials/@initrd", O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
+ if (source_dir_fd < 0) {
+ if (errno == ENOENT)
+ log_debug_errno(errno, "No credentials passed from initrd.");
+ else
+ log_warning_errno(errno, "Failed to open '/run/credentials/@initrd', ignoring: %m");
+ return 0;
+ }
+
+ r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
+ if (r < 0) {
+ log_warning_errno(r, "Failed to read '/run/credentials/@initrd' contents, ignoring: %m");
+ return 0;
+ }
+
+ FOREACH_ARRAY(entry, de->entries, de->n_entries) {
+ _cleanup_close_ int cfd = -EBADF, nfd = -EBADF;
+ const struct dirent *d = *entry;
+ struct stat st;
+
+ if (!credential_name_valid(d->d_name)) {
+ log_warning("Credential '%s' has invalid name, ignoring.", d->d_name);
+ continue;
+ }
+
+ cfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_CLOEXEC);
+ if (cfd < 0) {
+ log_warning_errno(errno, "Failed to open %s, ignoring: %m", d->d_name);
+ continue;
+ }
+
+ if (fstat(cfd, &st) < 0) {
+ log_warning_errno(errno, "Failed to stat %s, ignoring: %m", d->d_name);
+ continue;
+ }
+
+ r = stat_verify_regular(&st);
+ if (r < 0) {
+ log_warning_errno(r, "Credential file %s is not a regular file, ignoring: %m", d->d_name);
+ continue;
+ }
+
+ if (!credential_size_ok(c, d->d_name, st.st_size))
+ continue;
+
+ r = acquire_credential_directory(c, SYSTEM_CREDENTIALS_DIRECTORY, /* with_mount= */ true);
+ if (r < 0)
+ return r;
+
+ nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, d->d_name);
+ if (nfd == -EEXIST)
+ continue;
+ if (nfd < 0)
+ return nfd;
+
+ r = copy_bytes(cfd, nfd, st.st_size, 0);
+ if (r < 0) {
+ (void) unlinkat(c->target_dir_fd, d->d_name, 0);
+ return log_error_errno(r, "Failed to create credential '%s': %m", d->d_name);
+ }
+
+ c->size_sum += st.st_size;
+ c->n_credentials++;
+
+ log_debug("Successfully copied initrd credential '%s'.", d->d_name);
+
+ (void) unlinkat(source_dir_fd, d->d_name, 0);
+ }
+
+ source_dir_fd = safe_close(source_dir_fd);
+
+ if (rmdir("/run/credentials/@initrd") < 0)
+ log_warning_errno(errno, "Failed to remove /run/credentials/@initrd after import, ignoring: %m");
+
+ return 0;
+}
+
static int import_credentials_trusted(void) {
_cleanup_(import_credentials_context_free) ImportCredentialContext c = {
.target_dir_fd = -EBADF,
};
- int q, w, r;
+ int q, w, r, y;
+
+ /* This is invoked during early boot when no credentials have been imported so far. (Specifically, if
+ * the $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables are not set
+ * yet.) */
r = import_credentials_qemu(&c);
w = import_credentials_smbios(&c);
q = import_credentials_proc_cmdline(&c);
+ y = import_credentials_initrd(&c);
if (c.n_credentials > 0) {
int z;
- log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg.", c.n_credentials);
+ log_debug("Imported %u credentials from kernel command line/smbios/fw_cfg/initrd.", c.n_credentials);
z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
if (z < 0)
return z;
}
- return r < 0 ? r : w < 0 ? w : q;
+ return r < 0 ? r : w < 0 ? w : q < 0 ? q : y;
+}
+
+static int merge_credentials_trusted(const char *creds_dir) {
+ _cleanup_(import_credentials_context_free) ImportCredentialContext c = {
+ .target_dir_fd = -EBADF,
+ };
+ int r;
+
+ /* This is invoked after the initrd → host transitions, when credentials already have been imported,
+ * but we might want to import some more from the initrd. */
+
+ if (in_initrd())
+ return 0;
+
+ /* Do not try to merge initrd credentials into foreign credentials directories */
+ if (!path_equal_ptr(creds_dir, SYSTEM_CREDENTIALS_DIRECTORY)) {
+ log_debug("Not importing initrd credentials, as foreign $CREDENTIALS_DIRECTORY has been set.");
+ return 0;
+ }
+
+ r = import_credentials_initrd(&c);
+
+ if (c.n_credentials > 0) {
+ int z;
+
+ log_debug("Merged %u credentials from initrd.", c.n_credentials);
+
+ z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
+ if (z < 0)
+ return z;
+ }
+
+ return r;
}
static int symlink_credential_dir(const char *envvar, const char *path, const char *where) {
return 0;
}
+static int setenv_notify_socket(void) {
+ _cleanup_free_ char *address = NULL;
+ int r;
+
+ r = read_credential_with_decryption("vmm.notify_socket", (void **)&address, /* ret_size= */ NULL);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to read 'vmm.notify_socket' credential, ignoring: %m");
+
+ if (isempty(address))
+ return 0;
+
+ if (setenv("NOTIFY_SOCKET", address, /* replace= */ 1) < 0)
+ return log_warning_errno(errno, "Failed to set $NOTIFY_SOCKET environment variable, ignoring: %m");
+
+ return 1;
+}
+
+static int report_credentials_per_func(const char *title, int (*get_directory_func)(const char **ret)) {
+ _cleanup_free_ DirectoryEntries *de = NULL;
+ _cleanup_close_ int dir_fd = -EBADF;
+ _cleanup_free_ char *ll = NULL;
+ const char *d = NULL;
+ int r, c = 0;
+
+ assert(title);
+ assert(get_directory_func);
+
+ r = get_directory_func(&d);
+ if (r < 0) {
+ if (r == -ENXIO) /* Env var not set */
+ return 0;
+
+ return log_warning_errno(r, "Failed to determine %s directory: %m", title);
+ }
+
+ dir_fd = open(d, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
+ if (dir_fd < 0)
+ return log_warning_errno(errno, "Failed to open credentials directory %s: %m", d);
+
+ r = readdir_all(dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to enumerate credentials directory %s: %m", d);
+
+ FOREACH_ARRAY(entry, de->entries, de->n_entries) {
+ const struct dirent *e = *entry;
+
+ if (!credential_name_valid(e->d_name))
+ continue;
+
+ if (!strextend_with_separator(&ll, ", ", e->d_name))
+ return log_oom();
+
+ c++;
+ }
+
+ if (ll)
+ log_info("Received %s: %s", title, ll);
+
+ return c;
+}
+
+static void report_credentials(void) {
+ int p, q;
+
+ p = report_credentials_per_func("regular credentials", get_credentials_dir);
+ q = report_credentials_per_func("untrusted credentials", get_encrypted_credentials_dir);
+
+ log_full(p > 0 || q > 0 ? LOG_INFO : LOG_DEBUG,
+ "Acquired %i regular credentials, %i untrusted credentials.",
+ p > 0 ? p : 0,
+ q > 0 ? q : 0);
+}
+
int import_credentials(void) {
const char *received_creds_dir = NULL, *received_encrypted_creds_dir = NULL;
bool envvar_set = false;
r = q;
}
+ q = merge_credentials_trusted(received_creds_dir);
+ if (r >= 0)
+ r = q;
+
} else {
_cleanup_free_ char *v = NULL;
r = q;
}
- if (r >= 0) {
- _cleanup_free_ char *address = NULL;
+ report_credentials();
- r = read_credential("vmm.notify_socket", (void **)&address, /* ret_size= */ NULL);
- if (r < 0 && !IN_SET(r, -ENOENT, -ENXIO))
- log_warning_errno(r, "Failed to read 'vmm.notify_socket' credential, ignoring: %m");
- else if (r >= 0 && !isempty(address)) {
- r = setenv("NOTIFY_SOCKET", address, /* replace= */ 1);
- if (r < 0)
- log_warning_errno(errno, "Failed to set $NOTIFY_SOCKET environment variable, ignoring: %m");
- }
- }
+ /* Propagate vmm_notify_socket credential → $NOTIFY_SOCKET env var */
+ (void) setenv_notify_socket();
return r;
}
return r;
}
+ /* Pull credentials from various sources into a common credential directory (we do
+ * this here, before setting up the machine ID, so that we can use credential info
+ * for setting up the machine ID) */
+ (void) import_credentials();
+
(void) os_release_status();
(void) hostname_setup(true);
/* Force transient machine-id on first boot. */
- machine_id_setup(NULL, /* force_transient= */ first_boot, arg_machine_id, NULL);
+ machine_id_setup(/* root= */ NULL, /* force_transient= */ first_boot, arg_machine_id, /* ret_machine_id */ NULL);
(void) loopback_setup();
bump_unix_max_dgram_qlen();
bump_file_max_and_nr_open();
(void) bump_rlimit_nofile(saved_rlimit_nofile);
(void) bump_rlimit_memlock(saved_rlimit_memlock);
- /* Pull credentials from various sources into a common credential directory */
- if (arg_runtime_scope == RUNTIME_SCOPE_SYSTEM && !skip_setup)
- (void) import_credentials();
-
return 0;
}
sd_bus_error *e) {
bool is_new;
- Unit *dep;
Job *ret;
int r;
tr->anchor_job = ret;
}
- if (is_new && !FLAGS_SET(flags, TRANSACTION_IGNORE_REQUIREMENTS) && type != JOB_NOP) {
- _cleanup_set_free_ Set *following = NULL;
+ if (!is_new || FLAGS_SET(flags, TRANSACTION_IGNORE_REQUIREMENTS) || type == JOB_NOP)
+ return 0;
- /* If we are following some other unit, make sure we add all dependencies of everybody
- * following. */
- if (unit_following_set(ret->unit, &following) > 0)
- SET_FOREACH(dep, following) {
- r = transaction_add_job_and_dependencies(tr, type, dep, ret, flags & TRANSACTION_IGNORE_ORDER, e);
- if (r < 0) {
- log_unit_full_errno(dep, r == -ERFKILL ? LOG_INFO : LOG_WARNING, r,
- "Cannot add dependency job, ignoring: %s",
- bus_error_message(e, r));
- sd_bus_error_free(e);
- }
+ _cleanup_set_free_ Set *following = NULL;
+ Unit *dep;
+
+ /* If we are following some other unit, make sure we add all dependencies of everybody following. */
+ if (unit_following_set(ret->unit, &following) > 0)
+ SET_FOREACH(dep, following) {
+ r = transaction_add_job_and_dependencies(tr, type, dep, ret, flags & TRANSACTION_IGNORE_ORDER, e);
+ if (r < 0) {
+ log_unit_full_errno(dep, r == -ERFKILL ? LOG_INFO : LOG_WARNING, r,
+ "Cannot add dependency job, ignoring: %s",
+ bus_error_message(e, r));
+ sd_bus_error_free(e);
}
+ }
- /* Finally, recursively add in all dependencies. */
- if (IN_SET(type, JOB_START, JOB_RESTART)) {
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START) {
- r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
- if (r < 0) {
- if (r != -EBADR) /* job type not applicable */
- goto fail;
+ /* Finally, recursively add in all dependencies. */
+ if (IN_SET(type, JOB_START, JOB_RESTART)) {
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START) {
+ r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
+ if (r < 0) {
+ if (r != -EBADR) /* job type not applicable */
+ goto fail;
- sd_bus_error_free(e);
- }
+ sd_bus_error_free(e);
}
+ }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START_IGNORED) {
- r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, flags & TRANSACTION_IGNORE_ORDER, e);
- if (r < 0) {
- /* unit masked, job type not applicable and unit not found are not
- * considered as errors. */
- log_unit_full_errno(dep,
- IN_SET(r, -ERFKILL, -EBADR, -ENOENT) ? LOG_DEBUG : LOG_WARNING,
- r, "Cannot add dependency job, ignoring: %s",
- bus_error_message(e, r));
- sd_bus_error_free(e);
- }
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_START_IGNORED) {
+ r = transaction_add_job_and_dependencies(tr, JOB_START, dep, ret, flags & TRANSACTION_IGNORE_ORDER, e);
+ if (r < 0) {
+ /* unit masked, job type not applicable and unit not found are not considered
+ * as errors. */
+ log_unit_full_errno(dep,
+ IN_SET(r, -ERFKILL, -EBADR, -ENOENT) ? LOG_DEBUG : LOG_WARNING,
+ r, "Cannot add dependency job, ignoring: %s",
+ bus_error_message(e, r));
+ sd_bus_error_free(e);
}
+ }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_VERIFY) {
- r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
- if (r < 0) {
- if (r != -EBADR) /* job type not applicable */
- goto fail;
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_VERIFY) {
+ r = transaction_add_job_and_dependencies(tr, JOB_VERIFY_ACTIVE, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
+ if (r < 0) {
+ if (r != -EBADR) /* job type not applicable */
+ goto fail;
- sd_bus_error_free(e);
- }
+ sd_bus_error_free(e);
}
+ }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP) {
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, TRANSACTION_MATTERS | TRANSACTION_CONFLICTS | (flags & TRANSACTION_IGNORE_ORDER), e);
- if (r < 0) {
- if (r != -EBADR) /* job type not applicable */
- goto fail;
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP) {
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, TRANSACTION_MATTERS | TRANSACTION_CONFLICTS | (flags & TRANSACTION_IGNORE_ORDER), e);
+ if (r < 0) {
+ if (r != -EBADR) /* job type not applicable */
+ goto fail;
- sd_bus_error_free(e);
- }
+ sd_bus_error_free(e);
}
+ }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP_IGNORED) {
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, flags & TRANSACTION_IGNORE_ORDER, e);
- if (r < 0) {
- log_unit_warning(dep,
- "Cannot add dependency job, ignoring: %s",
- bus_error_message(e, r));
- sd_bus_error_free(e);
- }
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PULL_IN_STOP_IGNORED) {
+ r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, flags & TRANSACTION_IGNORE_ORDER, e);
+ if (r < 0) {
+ log_unit_warning(dep,
+ "Cannot add dependency job, ignoring: %s",
+ bus_error_message(e, r));
+ sd_bus_error_free(e);
}
}
+ }
- _cleanup_set_free_ Set *propagated_restart = NULL;
-
- if (type == JOB_RESTART || (type == JOB_START && FLAGS_SET(flags, TRANSACTION_PROPAGATE_START_AS_RESTART))) {
+ if (IN_SET(type, JOB_RESTART, JOB_STOP) || (type == JOB_START && FLAGS_SET(flags, TRANSACTION_PROPAGATE_START_AS_RESTART))) {
+ bool is_stop = type == JOB_STOP;
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, is_stop ? UNIT_ATOM_PROPAGATE_STOP : UNIT_ATOM_PROPAGATE_RESTART) {
/* We propagate RESTART only as TRY_RESTART, in order not to start dependencies that
* are not around. */
+ JobType nt;
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PROPAGATE_RESTART) {
- JobType nt;
+ nt = job_type_collapse(is_stop ? JOB_STOP : JOB_TRY_RESTART, dep);
+ if (nt == JOB_NOP)
+ continue;
- r = set_ensure_put(&propagated_restart, NULL, dep);
- if (r < 0)
+ r = transaction_add_job_and_dependencies(tr, nt, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
+ if (r < 0) {
+ if (r != -EBADR) /* job type not applicable */
return r;
- nt = job_type_collapse(JOB_TRY_RESTART, dep);
- if (nt == JOB_NOP)
- continue;
-
- r = transaction_add_job_and_dependencies(tr, nt, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
- if (r < 0) {
- if (r != -EBADR) /* job type not applicable */
- return r;
-
- sd_bus_error_free(e);
- }
+ sd_bus_error_free(e);
}
}
- if (type == JOB_STOP) {
- /* The 'stop' part of a restart job is also propagated to units with
- * UNIT_ATOM_PROPAGATE_STOP */
+ /* Process UNIT_ATOM_PROPAGATE_STOP_GRACEFUL (PropagatesStopTo=) units. We need to wait until
+ * all other dependencies are processed, i.e. we're the anchor job or already in the recursion
+ * that handles it. */
+ if (!by || FLAGS_SET(flags, TRANSACTION_PROCESS_PROPAGATE_STOP_GRACEFUL))
+ UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PROPAGATE_STOP_GRACEFUL) {
+ JobType nt = JOB_STOP;
+ Job *j;
+
+ j = hashmap_get(tr->jobs, dep);
+ if (j)
+ LIST_FOREACH(transaction, i, j)
+ switch (i->type) {
+
+ case JOB_STOP:
+ case JOB_RESTART:
+ /* Nothing to worry about, an appropriate job is in-place */
+ nt = JOB_NOP;
+ break;
+
+ case JOB_START:
+ /* This unit is pulled in by other dependency types in
+ * this transaction. We will run into job type conflict
+ * if we enqueue a stop job, so let's enqueue a restart
+ * job instead. */
+ nt = JOB_RESTART;
+
+ default: /* We don't care about others */
+ ;
+
+ }
- UNIT_FOREACH_DEPENDENCY(dep, ret->unit, UNIT_ATOM_PROPAGATE_STOP) {
- /* Units experienced restart propagation are skipped */
- if (set_contains(propagated_restart, dep))
+ if (nt == JOB_NOP)
continue;
- r = transaction_add_job_and_dependencies(tr, JOB_STOP, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER), e);
+ r = transaction_add_job_and_dependencies(tr, nt, dep, ret, TRANSACTION_MATTERS | (flags & TRANSACTION_IGNORE_ORDER) | TRANSACTION_PROCESS_PROPAGATE_STOP_GRACEFUL, e);
if (r < 0) {
if (r != -EBADR) /* job type not applicable */
return r;
sd_bus_error_free(e);
}
}
- }
+ }
- if (type == JOB_RELOAD)
- transaction_add_propagate_reload_jobs(tr, ret->unit, ret, flags & TRANSACTION_IGNORE_ORDER);
+ if (type == JOB_RELOAD)
+ transaction_add_propagate_reload_jobs(tr, ret->unit, ret, flags & TRANSACTION_IGNORE_ORDER);
- /* JOB_VERIFY_ACTIVE requires no dependency handling */
- }
+ /* JOB_VERIFY_ACTIVE requires no dependency handling */
return 0;
+
fail:
/* Recursive call failed to add required jobs so let's drop top level job as well. */
log_unit_debug_errno(unit, r, "Cannot add dependency job to transaction, deleting job %s/%s again: %s",
unit->id, job_type_to_string(type), bus_error_message(e, r));
+
transaction_delete_job(tr, ret, /* delete_dependencies= */ false);
return r;
-
}
static bool shall_stop_on_isolate(Transaction *tr, Unit *u) {
DEFINE_TRIVIAL_CLEANUP_FUNC(Transaction*, transaction_abort_and_free);
typedef enum TransactionAddFlags {
- TRANSACTION_MATTERS = 1 << 0,
- TRANSACTION_CONFLICTS = 1 << 1,
- TRANSACTION_IGNORE_REQUIREMENTS = 1 << 2,
- TRANSACTION_IGNORE_ORDER = 1 << 3,
+ TRANSACTION_MATTERS = 1 << 0,
+ TRANSACTION_CONFLICTS = 1 << 1,
+ TRANSACTION_IGNORE_REQUIREMENTS = 1 << 2,
+ TRANSACTION_IGNORE_ORDER = 1 << 3,
/* Propagate a START job to other units like a RESTART */
- TRANSACTION_PROPAGATE_START_AS_RESTART = 1 << 4,
+ TRANSACTION_PROPAGATE_START_AS_RESTART = 1 << 4,
+
+ /* Indicate that we're in the recursion for processing UNIT_ATOM_PROPAGATE_STOP_GRACEFUL units */
+ TRANSACTION_PROCESS_PROPAGATE_STOP_GRACEFUL = 1 << 5,
} TransactionAddFlags;
void transaction_add_propagate_reload_jobs(
UNIT_ATOM_PROPAGATE_STOP_FAILURE,
[UNIT_PROPAGATES_STOP_TO] = UNIT_ATOM_RETROACTIVE_STOP_ON_STOP |
- UNIT_ATOM_PROPAGATE_STOP,
+ UNIT_ATOM_PROPAGATE_STOP_GRACEFUL,
/* These are simple dependency types: they consist of a single atom only */
[UNIT_ON_FAILURE] = UNIT_ATOM_ON_FAILURE,
case UNIT_ATOM_PROPAGATE_STOP_FAILURE:
return UNIT_CONFLICTED_BY;
+ case UNIT_ATOM_RETROACTIVE_STOP_ON_STOP |
+ UNIT_ATOM_PROPAGATE_STOP_GRACEFUL:
+ case UNIT_ATOM_PROPAGATE_STOP_GRACEFUL:
+ return UNIT_PROPAGATES_STOP_TO;
+
/* And now, the simple ones */
case UNIT_ATOM_ON_FAILURE:
UNIT_ATOM_PROPAGATE_INACTIVE_START_AS_FAILURE = UINT64_C(1) << 17,
/* When putting together a transaction, propagate JOB_STOP from our unit to the other. */
UNIT_ATOM_PROPAGATE_STOP = UINT64_C(1) << 18,
+ /* Like UNIT_ATOM_PROPAGATE_STOP, but enqueues a restart job if there's already a start job (avoids
+ * job type conflict). */
+ UNIT_ATOM_PROPAGATE_STOP_GRACEFUL = UINT64_C(1) << 19,
/* When putting together a transaction, propagate JOB_RESTART from our unit to the other. */
- UNIT_ATOM_PROPAGATE_RESTART = UINT64_C(1) << 19,
+ UNIT_ATOM_PROPAGATE_RESTART = UINT64_C(1) << 20,
/* Add the other unit to the default target dependency queue */
- UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE = UINT64_C(1) << 20,
+ UNIT_ATOM_ADD_DEFAULT_TARGET_DEPENDENCY_QUEUE = UINT64_C(1) << 21,
/* Recheck default target deps on other units (which are target units) */
- UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES = UINT64_C(1) << 21,
+ UNIT_ATOM_DEFAULT_TARGET_DEPENDENCIES = UINT64_C(1) << 22,
/* The remaining atoms map 1:1 to the equally named high-level deps */
- UNIT_ATOM_ON_FAILURE = UINT64_C(1) << 22,
- UNIT_ATOM_ON_SUCCESS = UINT64_C(1) << 23,
- UNIT_ATOM_ON_FAILURE_OF = UINT64_C(1) << 24,
- UNIT_ATOM_ON_SUCCESS_OF = UINT64_C(1) << 25,
- UNIT_ATOM_BEFORE = UINT64_C(1) << 26,
- UNIT_ATOM_AFTER = UINT64_C(1) << 27,
- UNIT_ATOM_TRIGGERS = UINT64_C(1) << 28,
- UNIT_ATOM_TRIGGERED_BY = UINT64_C(1) << 29,
- UNIT_ATOM_PROPAGATES_RELOAD_TO = UINT64_C(1) << 30,
- UNIT_ATOM_JOINS_NAMESPACE_OF = UINT64_C(1) << 31,
- UNIT_ATOM_REFERENCES = UINT64_C(1) << 32,
- UNIT_ATOM_REFERENCED_BY = UINT64_C(1) << 33,
- UNIT_ATOM_IN_SLICE = UINT64_C(1) << 34,
- UNIT_ATOM_SLICE_OF = UINT64_C(1) << 35,
- _UNIT_DEPENDENCY_ATOM_MAX = (UINT64_C(1) << 36) - 1,
+ UNIT_ATOM_ON_FAILURE = UINT64_C(1) << 23,
+ UNIT_ATOM_ON_SUCCESS = UINT64_C(1) << 24,
+ UNIT_ATOM_ON_FAILURE_OF = UINT64_C(1) << 25,
+ UNIT_ATOM_ON_SUCCESS_OF = UINT64_C(1) << 26,
+ UNIT_ATOM_BEFORE = UINT64_C(1) << 27,
+ UNIT_ATOM_AFTER = UINT64_C(1) << 28,
+ UNIT_ATOM_TRIGGERS = UINT64_C(1) << 29,
+ UNIT_ATOM_TRIGGERED_BY = UINT64_C(1) << 30,
+ UNIT_ATOM_PROPAGATES_RELOAD_TO = UINT64_C(1) << 31,
+ UNIT_ATOM_JOINS_NAMESPACE_OF = UINT64_C(1) << 32,
+ UNIT_ATOM_REFERENCES = UINT64_C(1) << 33,
+ UNIT_ATOM_REFERENCED_BY = UINT64_C(1) << 34,
+ UNIT_ATOM_IN_SLICE = UINT64_C(1) << 35,
+ UNIT_ATOM_SLICE_OF = UINT64_C(1) << 36,
+ _UNIT_DEPENDENCY_ATOM_MAX = (UINT64_C(1) << 37) - 1,
_UNIT_DEPENDENCY_ATOM_INVALID = -EINVAL,
} UnitDependencyAtom;
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");
+ log_debug_errno(r, "Failed to read credential firstboot.locale-messages, ignoring: %m");
else
acquired_from_creds = true;
#include "bus-error.h"
#include "bus-locator.h"
#include "chase.h"
+#include "creds-util.h"
#include "efi-loader.h"
#include "env-util.h"
#include "fd-util.h"
return ret;
}
+static int add_mounts_from_creds(void) {
+ _cleanup_free_ void *b = NULL;
+ struct mntent *me;
+ int r, ret = 0;
+ size_t bs;
+
+ r = read_credential_with_decryption(
+ in_initrd() ? "fstab.extra.initrd" : "fstab.extra",
+ &b, &bs);
+ if (r <= 0)
+ return r;
+
+ _cleanup_fclose_ FILE *f = NULL;
+ f = fmemopen_unlocked(b, bs, "r");
+ if (!f)
+ return log_oom();
+
+ while ((me = getmntent(f))) {
+ r = parse_fstab_one(
+ "/run/credentials",
+ me->mnt_fsname,
+ me->mnt_dir,
+ me->mnt_type,
+ me->mnt_opts,
+ me->mnt_passno,
+ /* initrd = */ false,
+ /* use_swap_enabled = */ true);
+ if (r < 0 && ret >= 0)
+ ret = r;
+ }
+
+ return ret;
+}
+
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
int r;
(void) determine_usr();
if (arg_sysroot_check) {
- r = parse_fstab(true);
+ r = parse_fstab(/* initrd= */ true);
if (r == 0)
log_debug("Nothing interesting found, not doing daemon-reload.");
if (r > 0)
/* Honour /etc/fstab only when that's enabled */
if (arg_fstab_enabled) {
/* Parse the local /etc/fstab, possibly from the initrd */
- r = parse_fstab(false);
+ r = parse_fstab(/* initrd= */ false);
if (r < 0 && ret >= 0)
ret = r;
/* If running in the initrd also parse the /etc/fstab from the host */
if (in_initrd())
- r = parse_fstab(true);
+ r = parse_fstab(/* initrd= */ true);
else
r = generator_enable_remount_fs_service(arg_dest);
if (r < 0 && ret >= 0)
if (r < 0 && ret >= 0)
ret = r;
+ r = add_mounts_from_creds();
+ if (r < 0 && ret >= 0)
+ ret = r;
+
return ret;
}
#include <unistd.h>
#include "alloc-util.h"
+#include "creds-util.h"
#include "errno-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "generator.h"
+#include "initrd-util.h"
#include "log.h"
#include "mkdir-label.h"
#include "parse-util.h"
#include "path-util.h"
-#include "process-util.h"
#include "proc-cmdline.h"
+#include "process-util.h"
#include "strv.h"
#include "terminal-util.h"
#include "unit-name.h"
static bool arg_enabled = true;
static int add_symlink(const char *fservice, const char *tservice) {
- char *from, *to;
- int r;
+ const char *from, *to;
assert(fservice);
assert(tservice);
(void) mkdir_parents_label(to, 0755);
- r = symlink(from, to);
- if (r < 0) {
+ if (symlink(from, to) < 0) {
/* In case console=hvc0 is passed this will very likely result in EEXIST */
if (errno == EEXIST)
return 0;
}
}
+static int add_credential_gettys(void) {
+ static const struct {
+ const char *credential_name;
+ int (*func)(const char *tty);
+ } table[] = {
+ { "getty.ttys.serial", add_serial_getty },
+ { "getty.ttys.container", add_container_getty },
+ };
+ int r;
+
+ FOREACH_ARRAY(t, table, ELEMENTSOF(table)) {
+ _cleanup_free_ char *b = NULL;
+ size_t sz = 0;
+
+ r = read_credential_with_decryption(t->credential_name, (void*) &b, &sz);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ _cleanup_fclose_ FILE *f = NULL;
+ f = fmemopen_unlocked(b, sz, "r");
+ if (!f)
+ return log_oom();
+
+ for (;;) {
+ _cleanup_free_ char *tty = NULL;
+ char *s;
+
+ r = read_line(f, PATH_MAX, &tty);
+ if (r == 0)
+ break;
+ if (r < 0) {
+ log_error_errno(r, "Failed to parse credential %s: %m", t->credential_name);
+ break;
+ }
+
+ s = strstrip(tty);
+ if (startswith(s, "#"))
+ continue;
+
+ r = t->func(s);
+ if (r < 0)
+ return r;
+ }
+ }
+
+ return 0;
+}
+
static int parse_proc_cmdline_item(const char *key, const char *value, void *data) {
int r;
assert_se(arg_dest = dest);
+ if (in_initrd()) {
+ log_debug("Skipping generator, running in the initrd.");
+ return EXIT_SUCCESS;
+ }
+
r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0);
if (r < 0)
log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m");
return 0;
}
+ r = add_credential_gettys();
+ if (r < 0)
+ return r;
+
if (detect_container() > 0)
/* Add console shell and look at $container_ttys, but don't do add any
* further magic if we are in a container. */
p = path_join("/sys/class/tty", j);
if (!p)
- return -ENOMEM;
+ return log_oom();
if (access(p, F_OK) < 0)
continue;
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end!");
offset = fdisk_partition_get_end(p);
+ assert(offset < UINT64_MAX);
+ offset++; /* The end is one sector before the next partition or padding. */
assert(offset < UINT64_MAX / secsz);
offset *= secsz;
return log_error_errno(errno, "Failed to stat block device '%s': %m", context->node);
if (S_ISREG(st.st_mode) && st.st_size == 0) {
- /* User the fallback values if we have no better idea */
+ /* Use the fallback values if we have no better idea */
context->sector_size = arg_sector_size ?: 512;
context->grain_size = 4096;
return /* from_scratch = */ true;
#include <unistd.h>
#include "generator.h"
+#include "initrd-util.h"
#include "log.h"
#include "mkdir-label.h"
#include "string-util.h"
assert_se(arg_dest = dest);
+ if (in_initrd()) {
+ log_debug("Skipping generator, running in the initrd.");
+ return EXIT_SUCCESS;
+ }
+
if (check_executable(RC_LOCAL_PATH) >= 0) {
log_debug("Automatically adding rc-local.service.");
#include "acl-util.h"
#include "alloc-util.h"
+#include "errno-util.h"
#include "string-util.h"
#include "strv.h"
#include "user-util.h"
+#if HAVE_ACL
+
int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *ret_entry) {
acl_entry_t i;
int r;
return 0;
}
+
+int fd_acl_make_read_only(int fd) {
+ _cleanup_(acl_freep) acl_t acl = NULL;
+ bool changed = false;
+ acl_entry_t i;
+ int r;
+
+ assert(fd >= 0);
+
+ /* Safely drops all W bits from all relevant ACL entries of the file, without changing entries which
+ * are masked by the ACL mask */
+
+ acl = acl_get_fd(fd);
+ if (!acl) {
+
+ if (!ERRNO_IS_NOT_SUPPORTED(errno))
+ return -errno;
+
+ /* No ACLs? Then just update the regular mode_t */
+ return fd_acl_make_read_only_fallback(fd);
+ }
+
+ for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
+ r > 0;
+ r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
+ acl_permset_t permset;
+ acl_tag_t tag;
+ int b;
+
+ if (acl_get_tag_type(i, &tag) < 0)
+ return -errno;
+
+ /* These three control the x bits overall (as ACL_MASK affects all remaining tags) */
+ if (!IN_SET(tag, ACL_USER_OBJ, ACL_MASK, ACL_OTHER))
+ continue;
+
+ if (acl_get_permset(i, &permset) < 0)
+ return -errno;
+
+ b = acl_get_perm(permset, ACL_WRITE);
+ if (b < 0)
+ return -errno;
+
+ if (b) {
+ if (acl_delete_perm(permset, ACL_WRITE) < 0)
+ return -errno;
+
+ changed = true;
+ }
+ }
+ if (r < 0)
+ return -errno;
+
+ if (!changed)
+ return 0;
+
+ if (acl_set_fd(fd, acl) < 0) {
+ if (!ERRNO_IS_NOT_SUPPORTED(errno))
+ return -errno;
+
+ return fd_acl_make_read_only_fallback(fd);
+ }
+
+ return 1;
+}
+
+int fd_acl_make_writable(int fd) {
+ _cleanup_(acl_freep) acl_t acl = NULL;
+ acl_entry_t i;
+ int r;
+
+ /* Safely adds the writable bit to the owner's ACL entry of this inode. (And only the owner's! – This
+ * not the obvious inverse of fd_acl_make_read_only() hence!) */
+
+ acl = acl_get_fd(fd);
+ if (!acl) {
+ if (!ERRNO_IS_NOT_SUPPORTED(errno))
+ return -errno;
+
+ /* No ACLs? Then just update the regular mode_t */
+ return fd_acl_make_writable_fallback(fd);
+ }
+
+ for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
+ r > 0;
+ r = acl_get_entry(acl, ACL_NEXT_ENTRY, &i)) {
+ acl_permset_t permset;
+ acl_tag_t tag;
+ int b;
+
+ if (acl_get_tag_type(i, &tag) < 0)
+ return -errno;
+
+ if (tag != ACL_USER_OBJ)
+ continue;
+
+ if (acl_get_permset(i, &permset) < 0)
+ return -errno;
+
+ b = acl_get_perm(permset, ACL_WRITE);
+ if (b < 0)
+ return -errno;
+
+ if (b)
+ return 0; /* Already set? Then there's nothing to do. */
+
+ if (acl_add_perm(permset, ACL_WRITE) < 0)
+ return -errno;
+
+ break;
+ }
+ if (r < 0)
+ return -errno;
+
+ if (acl_set_fd(fd, acl) < 0) {
+ if (!ERRNO_IS_NOT_SUPPORTED(errno))
+ return -errno;
+
+ return fd_acl_make_writable_fallback(fd);
+ }
+
+ return 1;
+}
+#endif
+
+int fd_acl_make_read_only_fallback(int fd) {
+ struct stat st;
+
+ assert(fd >= 0);
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if ((st.st_mode & 0222) == 0)
+ return 0;
+
+ if (fchmod(fd, st.st_mode & 0555) < 0)
+ return -errno;
+
+ return 1;
+}
+
+int fd_acl_make_writable_fallback(int fd) {
+ struct stat st;
+
+ assert(fd >= 0);
+
+ if (fstat(fd, &st) < 0)
+ return -errno;
+
+ if ((st.st_mode & 0200) != 0) /* already set */
+ return 0;
+
+ if (fchmod(fd, (st.st_mode & 07777) | 0200) < 0)
+ return -errno;
+
+ return 1;
+}
#include <errno.h>
#include <unistd.h>
+int fd_acl_make_read_only_fallback(int fd);
+int fd_acl_make_writable_fallback(int fd);
+
#if HAVE_ACL
#include <acl/libacl.h>
#include <stdbool.h>
int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *ret);
int fd_add_uid_acl_permission(int fd, uid_t uid, unsigned mask);
+int fd_acl_make_read_only(int fd);
+int fd_acl_make_writable(int fd);
+
/* acl_free takes multiple argument types.
* Multiple cleanup functions are necessary. */
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(acl_t, acl_free, NULL);
static inline int fd_add_uid_acl_permission(int fd, uid_t uid, unsigned mask) {
return -EOPNOTSUPP;
}
+
+static inline int fd_acl_make_read_only(int fd) {
+ return fd_acl_make_read_only_fallback(fd);
+}
+
+static inline int fd_acl_make_writable(int fd) {
+ return fd_acl_make_writable_fallback(fd);
+}
+
#endif
(char**) ret, ret_size);
}
+int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size) {
+ _cleanup_(erase_and_freep) void *data = NULL;
+ _cleanup_free_ char *fn = NULL;
+ size_t sz = 0;
+ const char *d;
+ int r;
+
+ assert(ret);
+
+ /* Just like read_credential() but will also look for encrypted credentials. Note that services only
+ * receive decrypted credentials, hence use read_credential() for those. This helper here is for
+ * generators, i.e. code that runs outside of service context, and thus has no decrypted credentials
+ * yet.
+ *
+ * Note that read_credential_harder_and_warn() logs on its own, while read_credential() does not!
+ * (It's a lot more complex and error prone given its TPM2 connectivty, and is generally called from
+ * generators only where logging is OK).
+ *
+ * Error handling is also a bit different: if we can't find a credential we'll return 0 and NULL
+ * pointers/zero size, rather than -ENXIO/-ENOENT. */
+
+ if (!credential_name_valid(name))
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid credential name: %s", name);
+
+ r = read_credential(name, ret, ret_size);
+ if (r >= 0)
+ return 1; /* found */
+ if (!IN_SET(r, -ENXIO, -ENOENT))
+ return log_error_errno(r, "Failed read unencrypted credential '%s': %m", name);
+
+ r = get_encrypted_credentials_dir(&d);
+ if (r == -ENXIO)
+ goto not_found;
+ if (r < 0)
+ return log_error_errno(r, "Failed to determine encrypted credentials directory: %m");
+
+ fn = path_join(d, name);
+ if (!fn)
+ return log_oom();
+
+ r = read_full_file_full(
+ AT_FDCWD, fn,
+ UINT64_MAX, SIZE_MAX,
+ READ_FULL_FILE_SECURE,
+ NULL,
+ (char**) data, &sz);
+ if (r == -ENOENT)
+ goto not_found;
+ if (r < 0)
+ return log_error_errno(r, "Failed to read encrypted credential data: %m");
+
+ r = decrypt_credential_and_warn(
+ name,
+ now(CLOCK_REALTIME),
+ /* tpm2_device = */ NULL,
+ /* tpm2_signature_path = */ NULL,
+ data,
+ sz,
+ ret,
+ ret_size);
+ if (r < 0)
+ return r;
+
+ return 1; /* found */
+
+not_found:
+ *ret = NULL;
+
+ if (ret_size)
+ *ret_size = 0;
+
+ return 0; /* not found */
+}
+
int read_credential_strings_many_internal(
const char *first_name, char **first_value,
...) {
#define SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@system"
#define ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY "/run/credentials/@encrypted"
-int read_credential(const char *name, void **ret, size_t *ret_size);
+int read_credential(const char *name, void **ret, size_t *ret_size); /* use in services! */
+int read_credential_with_decryption(const char *name, void **ret, size_t *ret_size); /* use in generators + pid1! */
int read_credential_strings_many_internal(const char *first_name, char **first_value, ...);
#include "alloc-util.h"
#include "chase.h"
+#include "creds-util.h"
#include "fd-util.h"
#include "id128-util.h"
#include "io-util.h"
#include "umask-util.h"
#include "virt.h"
+static int acquire_machine_id_from_credential(sd_id128_t *ret) {
+ _cleanup_free_ char *buf = NULL;
+ int r;
+
+ r = read_credential_with_decryption("system.machine_id", (void**) &buf, /* ret_size= */ NULL);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to read system.machine_id credential, ignoring: %m");
+ if (r == 0) /* not found */
+ return -ENXIO;
+
+ r = sd_id128_from_string(buf, ret);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to parse system.machine_id credential, ignoring: %m");
+
+ log_info("Initializing machine ID from credential.");
+ return 0;
+}
+
static int generate_machine_id(const char *root, sd_id128_t *ret) {
_cleanup_close_ int fd = -EBADF;
int r;
}
if (isempty(root) && running_in_chroot() <= 0) {
+ /* Let's use a system credential for the machine ID if we can */
+ r = acquire_machine_id_from_credential(ret);
+ if (r >= 0)
+ return r;
+
/* If that didn't work, see if we are running in a container,
* and a machine ID was passed in via $container_uuid the way
* libvirt/LXC does it */
# SPDX-License-Identifier: LGPL-2.1-or-later
shared_sources = files(
+ 'acl-util.c',
'acpi-fpdt.c',
'apparmor-util.c',
'ask-password-api.c',
if conf.get('HAVE_ACL') == 1
shared_sources += files(
- 'acl-util.c',
'devnode-acl.c',
)
endif
return 0;
}
+
+unsigned long credentials_fs_mount_flags(bool ro) {
+ /* A tight set of mount flags for credentials mounts */
+ return MS_NODEV|MS_NOEXEC|MS_NOSUID|ms_nosymfollow_supported()|(ro ? MS_RDONLY : 0);
+}
+
+int mount_credentials_fs(const char *path, size_t size, bool ro) {
+ _cleanup_free_ char *opts = NULL;
+ int r, noswap_supported;
+
+ /* Mounts a file system we can place credentials in, i.e. with tight access modes right from the
+ * beginning, and ideally swapping turned off. In order of preference:
+ *
+ * 1. tmpfs if it supports "noswap"
+ * 2. ramfs
+ * 3. tmpfs if it doesn't support "noswap"
+ */
+
+ noswap_supported = mount_option_supported("tmpfs", "noswap", NULL); /* Check explicitly to avoid kmsg noise */
+ if (noswap_supported > 0) {
+ _cleanup_free_ char *noswap_opts = NULL;
+
+ if (asprintf(&noswap_opts, "mode=0700,nr_inodes=1024,size=%zu,noswap", size) < 0)
+ return -ENOMEM;
+
+ /* Best case: tmpfs with noswap (needs kernel >= 6.3) */
+
+ r = mount_nofollow_verbose(
+ LOG_DEBUG,
+ "tmpfs",
+ path,
+ "tmpfs",
+ credentials_fs_mount_flags(ro),
+ noswap_opts);
+ if (r >= 0)
+ return r;
+ }
+
+ r = mount_nofollow_verbose(
+ LOG_DEBUG,
+ "ramfs",
+ path,
+ "ramfs",
+ credentials_fs_mount_flags(ro),
+ "mode=0700");
+ if (r >= 0)
+ return r;
+
+ if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%zu", size) < 0)
+ return -ENOMEM;
+
+ return mount_nofollow_verbose(
+ LOG_DEBUG,
+ "tmpfs",
+ path,
+ "tmpfs",
+ credentials_fs_mount_flags(ro),
+ opts);
+}
int make_mount_point_inode_from_path(const char *source, const char *dest, mode_t mode);
int trigger_automount_at(int dir_fd, const char *path);
+
+unsigned long credentials_fs_mount_flags(bool ro);
+int mount_credentials_fs(const char *path, size_t size, bool ro);
#include "fs-util.h"
#include "generator.h"
+#include "initrd-util.h"
#include "log.h"
#include "path-util.h"
#include "proc-cmdline.h"
assert_se(arg_dest = dest_early);
+ if (in_initrd()) {
+ log_debug("Skipping generator, running in the initrd.");
+ return EXIT_SUCCESS;
+ }
+
r = generate_symlink();
if (r <= 0)
return r;
#include "generator.h"
#include "hashmap.h"
#include "hexdecoct.h"
+#include "initrd-util.h"
#include "install.h"
#include "log.h"
#include "main-func.h"
SysvStub *service;
int r;
+ if (in_initrd()) {
+ log_debug("Skipping generator, running in the initrd.");
+ return EXIT_SUCCESS;
+ }
+
assert_se(arg_dest = dest_late);
r = lookup_paths_init_or_warn(&lp, RUNTIME_SCOPE_SYSTEM, LOOKUP_PATHS_EXCLUDE_GENERATED, NULL);
return 0;
}
+TEST(fd_acl_make_read_only) {
+ _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-empty.XXXXXX";
+ _cleanup_close_ int fd = -EBADF;
+ const char *cmd;
+ struct stat st;
+
+ fd = mkostemp_safe(fn);
+ assert_se(fd >= 0);
+
+ /* make it more exciting */
+ (void) fd_add_uid_acl_permission(fd, 1, ACL_READ|ACL_WRITE|ACL_EXECUTE);
+
+ assert_se(fstat(fd, &st) >= 0);
+ assert_se((st.st_mode & 0200) == 0200);
+
+ cmd = strjoina("getfacl -p ", fn);
+ assert_se(system(cmd) == 0);
+
+ cmd = strjoina("stat ", fn);
+ assert_se(system(cmd) == 0);
+
+ log_info("read-only");
+ assert_se(fd_acl_make_read_only(fd));
+
+ assert_se(fstat(fd, &st) >= 0);
+ assert_se((st.st_mode & 0222) == 0000);
+
+ cmd = strjoina("getfacl -p ", fn);
+ assert_se(system(cmd) == 0);
+
+ cmd = strjoina("stat ", fn);
+ assert_se(system(cmd) == 0);
+
+ log_info("writable");
+ assert_se(fd_acl_make_writable(fd));
+
+ assert_se(fstat(fd, &st) >= 0);
+ assert_se((st.st_mode & 0222) == 0200);
+
+ cmd = strjoina("getfacl -p ", fn);
+ assert_se(system(cmd) == 0);
+
+ cmd = strjoina("stat ", fn);
+ assert_se(system(cmd) == 0);
+
+ log_info("read-only");
+ assert_se(fd_acl_make_read_only(fd));
+
+ assert_se(fstat(fd, &st) >= 0);
+ assert_se((st.st_mode & 0222) == 0000);
+
+ cmd = strjoina("getfacl -p ", fn);
+ assert_se(system(cmd) == 0);
+
+ cmd = strjoina("stat ", fn);
+ assert_se(system(cmd) == 0);
+}
+
DEFINE_TEST_MAIN(LOG_INFO);
"-smbios type=11,value=io.systemd.credential.binary:binarysmbioscredential=bWFnaWNiaW5hcnlkYXRh"
"-smbios type=11,value=io.systemd.credential.binary:sysusers.extra=dSBjcmVkdGVzdHVzZXIK"
"-smbios type=11,value=io.systemd.credential.binary:tmpfiles.extra=ZiAvdG1wL3NvdXJjZWRmcm9tY3JlZGVudGlhbCAtIC0gLSAtIHRtcGZpbGVzc2VjcmV0Cg=="
+ "-smbios type=11,value=io.systemd.credential.binary:fstab.extra=aW5qZWN0ZWQgL2luamVjdGVkIHRtcGZzIFgtbW91bnQubWtkaXIgMCAwCg=="
+ "-smbios type=11,value=io.systemd.credential:getty.ttys.container=idontexist"
)
QEMU_OPTIONS="${QEMU_OPTIONS:-} ${QEMU_CREDS[*]}"
"systemd.set_credential=sysctl.extra:kernel.domainname=sysctltest"
"systemd.set_credential=login.motd:hello"
"systemd.set_credential=login.issue:welcome"
+ "systemd.set_credential_binary=waldi:d29vb29mZmZ3dWZmZnd1ZmYK"
"rd.systemd.import_credentials=no"
)
KERNEL_APPEND="${KERNEL_APPEND:-} ${KERNEL_CREDS[*]}"
generate_module_dependencies
}
+run_qemu_hook() {
+ local td="$WORKDIR"/initrd.extra."$RANDOM"
+ mkdir -m 755 "$td"
+ add_at_exit_handler "rm -rf $td"
+ mkdir -m 755 "$td/etc" "$td"/etc/systemd "$td"/etc/systemd/system "$td"/etc/systemd/system/initrd.target.wants
+
+ cat > "$td"/etc/systemd/system/initrdcred.service <<EOF
+[Unit]
+Description=populate initrd credential dir
+
+[Service]
+Type=oneshot
+RemainAfterExit=yes
+ExecStart=sh -c "mkdir -m 0755 -p /run/credentials && mkdir -m 0700 /run/credentials/@initrd && umask 0077 && echo guatemala > /run/credentials/@initrd/myinitrdcred"
+EOF
+ ln -s ../initrdcred.service "$td"/etc/systemd/system/initrd.target.wants/initrdcred.service
+
+ ( cd "$td" && find . | cpio -o -H newc -R root:root > "$td".cpio )
+ add_at_exit_handler "rm $td.cpio"
+
+ INITRD_EXTRA="$td.cpio"
+}
+
do_test "$@"
printf "%s\n%s\n" "$1" "$qemu_ver" | sort -V -C
}
+# Pads a file to multiple of 4 bytes
+pad4_file() {
+ local size
+ size=$(stat -c "%s" "$1")
+ local padded
+ padded=$((((size + 3) / 4) * 4))
+ truncate -s "$padded" "$1"
+}
+
# Return 0 if qemu did run (then you must check the result state/logs for actual
# success), or 1 if qemu is not available.
run_qemu() {
+ if declare -F run_qemu_hook >/dev/null; then
+ if ! run_qemu_hook "${workspace}"; then
+ derror "check_qemu_hook() returned with EC > 0"
+ ret=4
+ fi
+ fi
+
# If the test provided its own initrd, use it (e.g. TEST-24)
if [[ -z "$INITRD" && -f "${TESTDIR:?}/initrd.img" ]]; then
INITRD="$TESTDIR/initrd.img"
fi
if [[ -n "$INITRD" ]]; then
- qemu_options+=(-initrd "$INITRD")
+ if [[ -n "$INITRD_EXTRA" ]]; then
+ # An addition initrd has been specified, let's combine it with the main one.
+ local t="$WORKDIR"/initrd.combined."$RANDOM"
+
+ # First, show contents of additional initrd
+ echo "Additional initrd contents:"
+ cpio -tv < "$INITRD_EXTRA"
+
+ # Copy the main initrd
+ zstd -d -c -f "$INITRD" > "$t"
+ add_at_exit_handler "rm $t"
+ # Kernel requires this to be padded to multiple of 4 bytes with zeroes
+ pad4_file "$t"
+
+ # Copy the additional initrd
+ cat "$INITRD_EXTRA" >> "$t"
+ pad4_file "$t"
+
+ qemu_options+=(-initrd "$t")
+ else
+ qemu_options+=(-initrd "$INITRD")
+ fi
fi
# Let's use KVM if possible
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Requires=sleep-infinity-simple.service
+After=sleep-infinity-simple.service
+PropagatesStopTo=sleep-infinity-simple.service
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+PropagatesStopTo=sleep-infinity-simple.service
--- /dev/null
+# SPDX-License-Identifier: LGPL-2.1-or-later
+[Unit]
+Description=Sleep infinitely
+
+[Service]
+Type=simple
+ExecStart=/bin/sleep infinity
set -eux
set -o pipefail
+# shellcheck source=test/units/util.sh
+. "$(dirname "$0")"/util.sh
+
# Simple test for that daemon-reexec works in container.
# See: https://github.com/systemd/systemd/pull/23883
systemctl daemon-reexec
systemctl start "transaction-cycle$i.service"
done
+# Test PropagatesStopTo= when restart (issue #26839)
+systemctl start propagatestopto-and-pullin.target
+systemctl --quiet is-active propagatestopto-and-pullin.target
+
+systemctl restart propagatestopto-and-pullin.target
+systemctl --quiet is-active propagatestopto-and-pullin.target
+systemctl --quiet is-active sleep-infinity-simple.service
+
+systemctl start propagatestopto-only.target
+systemctl --quiet is-active propagatestopto-only.target
+systemctl --quiet is-active sleep-infinity-simple.service
+
+systemctl restart propagatestopto-only.target
+assert_rc 3 systemctl --quiet is-active sleep-infinity-simple.service
+
touch /testok
elif [ -d /sys/firmware/qemu_fw_cfg/by_name ]; then
# Verify that passing creds through kernel cmdline works
[ "$(systemd-creds --system cat kernelcmdlinecred)" = "uff" ]
+ [ "$(systemd-creds --system cat waldi)" = "woooofffwufffwuff" ]
# And that it also works via SMBIOS
[ "$(systemd-creds --system cat smbioscredential)" = "magicdata" ]
--pipe \
true | cmp /etc/os-release
+if ! systemd-detect-virt -q -c ; then
+ # Validate that the credential we inserted via the initrd logic arrived
+ test "$(systemd-creds cat --system myinitrdcred)" = "guatemala"
+
+ # Check that the fstab credential logic worked
+ test -d /injected
+ grep -q /injected /proc/self/mountinfo
+
+ # Make sure the getty generator processed the credentials properly
+ systemctl -P Wants show getty.target | grep -q container-getty@idontexist.service
+fi
+
systemd-analyze log-level info
echo OK >/testok
systemd-dissect -U "$imgs/mnt"
}
+testcase_free_area_calculation() {
+ local defs imgs output
+
+ if ! command -v mksquashfs >/dev/null; then
+ echo "Skipping free area calculation test without squashfs."
+ return
+ fi
+
+ defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")"
+ imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")"
+ # shellcheck disable=SC2064
+ trap "rm -rf '$defs' '$imgs'" RETURN
+ chmod a+rx "$defs"
+
+ # https://github.com/systemd/systemd/issues/28225
+ echo "*** free area calculation ***"
+
+ tee "$defs/00-ESP.conf" <<EOF
+[Partition]
+Type = esp
+Label = ESP
+Format = vfat
+
+SizeMinBytes = 128M
+SizeMaxBytes = 128M
+
+# Sufficient for testing
+CopyFiles = /etc:/
+EOF
+
+ tee "$defs/10-os.conf" <<EOF
+[Partition]
+Type = root-${architecture}
+Label = test
+Format = squashfs
+
+Minimize = best
+# Sufficient for testing
+CopyFiles = /etc/:/
+
+VerityMatchKey = os
+Verity = data
+EOF
+
+ tee "$defs/11-os-verity.conf" <<EOF
+[Partition]
+Type = root-${architecture}-verity
+Label = test
+
+Minimize = best
+
+VerityMatchKey = os
+Verity = hash
+EOF
+
+ # Set sector size for VFAT to 512 bytes because there will not be enough FAT clusters otherwise
+ output1=$(SYSTEMD_REPART_MKFS_OPTIONS_VFAT="-S 512" systemd-repart \
+ --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ --empty=create \
+ --size=auto \
+ --sector-size=4096 \
+ --defer-partitions=esp \
+ --json=pretty \
+ "$imgs/zzz")
+
+ # The second invocation
+ output2=$(SYSTEMD_REPART_MKFS_OPTIONS_VFAT="-S 512" systemd-repart \
+ --definitions="$defs" \
+ --seed="$seed" \
+ --dry-run=no \
+ --empty=allow \
+ --size=auto \
+ --sector-size=4096 \
+ --defer-partitions=esp \
+ --json=pretty \
+ "$imgs/zzz")
+
+ diff -u <(echo "$output1" | grep -E "(offset|raw_size|raw_padding)") <(echo "$output2" | grep -E "(offset|raw_size|raw_padding)")
+}
+
test_sector() {
local defs imgs output loop
local start size ratio
[Unit]
Description=Load/Save Screen Backlight Brightness of %i
Documentation=man:systemd-backlight@.service(8)
+ConditionPathExists=!/etc/initrd-release
+
DefaultDependencies=no
Conflicts=shutdown.target
Before=sysinit.target shutdown.target
[Unit]
Description=Automatic Boot Loader Update
Documentation=man:bootctl(1)
+ConditionPathExists=!/etc/initrd-release
DefaultDependencies=no
After=local-fs.target
[Unit]
Description=Load/Save RF Kill Switch Status
Documentation=man:systemd-rfkill.service(8)
+ConditionPathExists=!/etc/initrd-release
+
DefaultDependencies=no
BindsTo=sys-devices-virtual-misc-rfkill.device
Conflicts=shutdown.target
[Unit]
Description=Load/Save RF Kill Switch Status /dev/rfkill Watch
Documentation=man:systemd-rfkill.socket(8)
+ConditionPathExists=!/etc/initrd-release
DefaultDependencies=no
BindsTo=sys-devices-virtual-misc-rfkill.device
[Unit]
Description=Record Runlevel Change in UTMP
Documentation=man:systemd-update-utmp-runlevel.service(8) man:utmp(5)
+ConditionPathExists=!/etc/initrd-release
+
DefaultDependencies=no
RequiresMountsFor=/var/log/wtmp
Conflicts=shutdown.target
[Unit]
Description=Record System Boot/Shutdown in UTMP
Documentation=man:systemd-update-utmp.service(8) man:utmp(5)
+ConditionPathExists=!/etc/initrd-release
DefaultDependencies=no
After=systemd-remount-fs.service systemd-tmpfiles-setup.service auditd.service