mirror of
https://github.com/systemd/systemd-stable.git
synced 2024-12-23 17:34:00 +03:00
Merge pull request #23170 from poettering/creds-copy
import system credentials from sd-stub + qemu fw_cfg + kernel cmdline explicitly in PID 1
This commit is contained in:
commit
41be3b099f
12
TODO
12
TODO
@ -166,11 +166,6 @@ Features:
|
||||
don't query this unnecessarily in entirely uninitialized
|
||||
containers. (i.e. containers with empty /etc).
|
||||
|
||||
* systemd creds hookup with qemu fw_cfg. (Quite possibly might not need any
|
||||
code at all, given the fw_cfg stuff are just files, but we should then
|
||||
document how to use it). Goal: provide symmetric ways to pass creds to nspawn
|
||||
containers and qemu VMs. (maybe also pick up env vars from fw_cfg?)
|
||||
|
||||
* beef up sd_notify() to support AV_VSOCK in $NOTIFY_SOCKET, so that VM
|
||||
managers can get ready notifications from VMs, just like container managers
|
||||
from their payload. Also pick up address from qemu/fw_cfg if set there.
|
||||
@ -534,14 +529,7 @@ Features:
|
||||
|
||||
* expose MS_NOSYMFOLLOW in various places
|
||||
|
||||
* make LoadCredential= automatically find credentials in /etc/creds,
|
||||
/run/creds, … and so on, if path component is unqualified
|
||||
|
||||
* teach LoadCredential=/LoadCredentialEncrypted= to load credentials from
|
||||
kernel cmdline, maybe: LoadCredentialEncrypted=foobar:proc-cmdline:foobar
|
||||
|
||||
* credentials system:
|
||||
- acquire from kernel command line
|
||||
- acquire from EFI variable?
|
||||
- acquire via via ask-password?
|
||||
- acquire creds via keyring?
|
||||
|
388
docs/CREDENTIALS.md
Normal file
388
docs/CREDENTIALS.md
Normal file
@ -0,0 +1,388 @@
|
||||
---
|
||||
title: Credentials
|
||||
category: Concepts
|
||||
layout: default
|
||||
SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
---
|
||||
|
||||
# System and Service Credentials
|
||||
|
||||
The `systemd` service manager supports a "credential" concept for securely
|
||||
acquiring and passing credential data to systems and services. The precise
|
||||
nature of the credential data is up to applications, but the concept is
|
||||
intended to provide systems and services with potentially security sensitive
|
||||
cryptographic keys, certificates, passwords, identity information and similar
|
||||
types of information. It may also be used as generic infrastructure for
|
||||
parameterizing systems and services.
|
||||
|
||||
Traditionally, data of this nature has often been provided to services via
|
||||
environment variables (which is problematic because by default they are
|
||||
inherited down the process tree, have size limitations, and issues with binary
|
||||
data) or simple, unencrypted files on disk. `systemd`'s system and service
|
||||
credentials are supposed to provide a better alternative for this
|
||||
purpose. Specifically, the following features are provided:
|
||||
|
||||
1. Service credentials are acquired at the moment of service activation, and
|
||||
released on service deactivation. They are immutable during the service
|
||||
runtime.
|
||||
|
||||
2. Service credentials are accessible to service code as regular files, the
|
||||
path to access them is derived from the environment variable
|
||||
`$CREDENTIALS_DIRECTORY`.
|
||||
|
||||
3. Access to credentials is restricted to the service's user. Unlike
|
||||
environment variables the credential data is not propagated down the process
|
||||
tree. Instead each time a credential is accessed an access check is enforced
|
||||
by the kernel. If the service is using file system namespacing the loaded
|
||||
credential data is invisble to any other services.
|
||||
|
||||
4. Service credentials may be acquired from files on disk, specified as literal
|
||||
strings in unit files, acquired from another service dynamically via an
|
||||
`AF_UNIX` socket, or inherited from the system credentials the system itself
|
||||
received.
|
||||
|
||||
5. Credentials may optionally be encrypted and authenticated, either with a key
|
||||
derived from a local TPM2 chip, or one stored in `/var/`, or both. This
|
||||
encryption is supposed to *just* *work*, and requires no manual setup. (That
|
||||
is besides first encrypting relevant credentials with one simple command,
|
||||
see below.)
|
||||
|
||||
6. Service credentials are placed in non-swappable memory. (If permissions
|
||||
allow it, via `ramfs`.)
|
||||
|
||||
7. Credentials may be acquired from a hosting VM hypervisor (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 individual services as needed.
|
||||
|
||||
8. Credentials are an effective way to pass parameters into services that run
|
||||
with `RootImage=` or `RootDirectory=` and thus cannot read these resources
|
||||
directly from the host directory tree. Specifically, [Portable
|
||||
Services](https://systemd.io/PORTABLE_SERVICES) may be parameterized this
|
||||
way securely and robustly.
|
||||
|
||||
9. Credentials can be binary and relatively large (though currently an overall
|
||||
size limit of 1M per service is enforced).
|
||||
|
||||
## Configuring per-Service Credentials
|
||||
|
||||
Within unit files, there are four settings to configure service credentials.
|
||||
|
||||
1. `LoadCredential=` may be used to load a credential from disk, from an
|
||||
`AF_UNIX` socket, or propagate them from a system credential.
|
||||
|
||||
2. `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,
|
||||
i.e. public keys/certificates – but not private keys.
|
||||
|
||||
3. `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
|
||||
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
|
||||
decoded unless access to TPM2/encryption key is available.
|
||||
|
||||
Each credential configured with these options carries a short name (suitable
|
||||
for inclusion in a filename) in the unit file, under which the invoked service
|
||||
code can then retrieve it. Each name should only be specified once.
|
||||
|
||||
For details about these four settings [see the man
|
||||
page](https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Credentials).
|
||||
|
||||
It is a good idea to also enable mount namespacing for services that process
|
||||
credentials configured this way. If so, the runtime credential directory of the
|
||||
specific service is not visible to any other service. Use `PrivateMounts=` as
|
||||
minimal option to enable such namespacing. Note that many other sandboxing
|
||||
settings (e.g. `ProtectSystem=`, `ReadOnlyPaths=` and similar) imply
|
||||
`PrivateMounts=`, hence oftentimes it's not necessary to set this option
|
||||
explicitly.
|
||||
|
||||
## Programming Interface from Service Code
|
||||
|
||||
When a service is invoked with one or more credentials set it will have an
|
||||
environment variable `$CREDENTIALS_DIRECTORY` set. It contains an absolute path
|
||||
to a directory the credentials are placed in. In this directory for each
|
||||
configured credential one file is placed. In addition to the
|
||||
`$CREDENTIALS_DIRECTORY` environment variable passed to the service processes
|
||||
the `%d` specifier in unit files resolves to the service's credential
|
||||
directory.
|
||||
|
||||
Example unit file:
|
||||
|
||||
```
|
||||
…
|
||||
[Service]
|
||||
ExecStart=/usr/bin/myservice.sh
|
||||
LoadCredential=foobar:/etc/myfoobarcredential.txt
|
||||
Environment=FOOBARPATH=%d/foobar
|
||||
…
|
||||
```
|
||||
|
||||
Associated service shell script `/usr/bin/myservice.sh`:
|
||||
|
||||
```sh
|
||||
#!/bin/sh
|
||||
|
||||
sha256sum $CREDENTIAL_PATH/foobar
|
||||
sha256sum $FOOBARPATH
|
||||
|
||||
```
|
||||
|
||||
A service defined like this will get the contents of the file
|
||||
`/etc/myfoobarcredential.txt` passed as credential `foobar`, which is hence
|
||||
accessible under `$CREDENTIALS_DIRECTORY/foobar`. Since we additionally pass
|
||||
the path to it as environment variable `$FOOBARPATH` the credential is also
|
||||
accessible as the path in that environment variable. When invoked, the service
|
||||
will hence show the same SHA256 hash value of `/etc/myfoobarcredential.txt`
|
||||
twice.
|
||||
|
||||
In an ideal world, well-behaved service code would directly support credentials
|
||||
passed this way, i.e. look for `$CREDENTIALS_DIRECTORY` and load the credential
|
||||
data it needs from there. For daemons that do not support this but allow
|
||||
passing credentials via a path supplied over the command line use
|
||||
`${CREDENTIAL_PATH}` in the `ExecStart=` command line to reference the
|
||||
credentials directory. For daemons that allow passing credentials via a path
|
||||
supplied as environment variabe, use the `%d` specifier in the `Environment=`
|
||||
setting to build valid paths to specific credentials.
|
||||
|
||||
## Tools
|
||||
|
||||
The
|
||||
[`systemd-creds`](https://www.freedesktop.org/software/systemd/man/systemd-creds.html)
|
||||
tool is provided to work with system and service credentials. It may be used to
|
||||
access and enumerate system and service credentials, or to encrypt/decrypt credentials
|
||||
(for details about the latter, see below).
|
||||
|
||||
When invoked from service context, `systemd-creds` passed without further
|
||||
parameters will list passed credentials. The `systemd-creds cat xyz` command
|
||||
may be used to write the contents of credential `xyz` to standard output. If
|
||||
these calls are combined with the `--system` switch credentials passed to the
|
||||
system as a whole are shown, instead of the those passed to the service the
|
||||
command is invoked from.
|
||||
|
||||
Example use:
|
||||
|
||||
```sh
|
||||
systemd-run -P --wait -p LoadCredential=abc:/etc/hosts systemd-creds cat abc
|
||||
```
|
||||
|
||||
This will invoke a transient service with a credential `abc` sourced from the
|
||||
system's `/etc/hosts` file. This credential is then written to standard output
|
||||
via `systemd-creds cat`.
|
||||
|
||||
## Encryption
|
||||
|
||||
Credentials are supposed to be useful for carrying sensitive information, such
|
||||
as cryptographic key material. For this kind of data (symmetric) encryption and
|
||||
authentication is provided to make storage of the data at rest safer. The data
|
||||
may be encrypted and authenticated with AES256-GCM. The encryption key can
|
||||
either be one derived from the local TPM2 device, or one stored in
|
||||
`/var/lib/systemd/credential.secret`, or a combination of both. If a TPM2
|
||||
device is available and `/var/` resides on persistent storage the default
|
||||
behaviour is to use the combination of both for encryption, thus ensuring that
|
||||
credentials protected this way can only be decrypted and validated on the
|
||||
local hardware and OS installation. Encrypted credentials stored on disk thus
|
||||
cannot be decrypted without access to the TPM2 chip and the aforementioned key
|
||||
file `/var/lib/systemd/credential.secret`. Moreover, credentials cannot be
|
||||
prepared on another machine than the local one.
|
||||
|
||||
The `systemd-creds` tool provides the commands `encrypt` and `decrypt` to
|
||||
encrypt and decrypt/authenticate credentials. Example:
|
||||
|
||||
```sh
|
||||
systemd-creds encrypt plaintext.txt ciphertext.cred
|
||||
shred -u plaintext-txt
|
||||
systemd-run -P --wait -p LoadCredentialEncrypted=foobar:$(pwd)/ciphertext.cred systemd-creds cat foobar
|
||||
```
|
||||
|
||||
This will first create an encrypted copy of the file `plaintext.txt` in the
|
||||
encrypted credential file `ciphertext.cred`. It then securely removes the
|
||||
source file. It then runs a transient service, that reads the encrypted file
|
||||
and passes it as decrypted credential `foobar` to the invoked service binary
|
||||
(which here is the `systemd-creds` tool, which just writes the data
|
||||
it received to standard output).
|
||||
|
||||
Instead of storing the encrypted credential as a separate file on disk, it can
|
||||
also be embedded in the unit file. Example:
|
||||
|
||||
```
|
||||
systemd-creds encrypt -p --name=foobar plaintext.txt -
|
||||
```
|
||||
|
||||
This will output a `SetCredentialEncrypted=` line that can directly be used in
|
||||
a unit file. e.g.:
|
||||
|
||||
```
|
||||
…
|
||||
[Service]
|
||||
ExecStart=/usr/bin/systemd-creds cat foobar
|
||||
SetCredentialEncrypted=foobar: \
|
||||
k6iUCUh0RJCQyvL8k8q1UyAAAAABAAAADAAAABAAAAC1lFmbWAqWZ8dCCQkAAAAAgAAAA \
|
||||
AAAAAALACMA0AAAACAAAAAAfgAg9uNpGmj8LL2nHE0ixcycvM3XkpOCaf+9rwGscwmqRJ \
|
||||
cAEO24kB08FMtd/hfkZBX8PqoHd/yPTzRxJQBoBsvo9VqolKdy9Wkvih0HQnQ6NkTKEdP \
|
||||
HQ08+x8sv5sr+Mkv4ubp3YT1Jvv7CIPCbNhFtag1n5y9J7bTOKt2SQwBOAAgACwAAABIA \
|
||||
ID8H3RbsT7rIBH02CIgm/Gv1ukSXO3DMHmVQkDG0wEciABAAII6LvrmL60uEZcp5qnEkx \
|
||||
SuhUjsDoXrJs0rfSWX4QAx5PwfdFuxPusgEfTYIiCb8a/W6RJc7cMweZVCQMbTARyIAAA \
|
||||
AAJt7Q9F/Gz0pBv1Lc4Dpn1WpebyBBm+vQ5N/lSKW2XSm8cONwCopxpDc7wJjXg7OTR6r \
|
||||
xGCpIvGXLt3ibwJl81woLya2RRjIvc/R2zNm/yWzZAjiOLPih4SuHthqiX98ey8PUmZJB \
|
||||
VGXglCZFjBx+d7eCqTIdghtp5pkDGwMJT6pjw4FfyFK2nJPawFKPAqzw9DK2iYttFeXi5 \
|
||||
19xCfLBH9NKS/idlYXrhp+XIEtsr26s4lx5y10Goyc3qDOR3RD2cuZj0gHwV35hhhhcCz \
|
||||
JaYytef1X/YL+7fYH5kuE4rxSksoUuA/LhtjszBeGbcbIT+O8SuvBJHLKTSHxPL8FTyk3 \
|
||||
L4FSkEHs0rYwUIkKmnGohDdsYrMJ2fjH3yDNBP16aD1+f/Nuh75cjhUnGsDLt9K4hGg== \
|
||||
…
|
||||
```
|
||||
|
||||
## Inheritance from Container Managers, Hypervisors, Kernel Command Line, or the UEFI Boot Environment
|
||||
|
||||
Sometimes it is useful to parameterize whole systems the same way as services,
|
||||
via `systemd` credentials. In particular, it might make sense to boot a
|
||||
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:
|
||||
|
||||
1. A container manager may set the `$CREDENTIALS_DIRECTORY` environment
|
||||
variable for systemd running as PID 1 in the container, the same way as
|
||||
systemd would set it for a service it
|
||||
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](https://systemd.io/CONTAINER_INTERFACE)
|
||||
documentation.
|
||||
|
||||
2. Quite similar, qemu VMs can be invoked with `-fw_cfg
|
||||
name=opt/io.systemd.credentials/foo,string=bar` to pass credentials from
|
||||
host through the hypervisor into the VM. (This specific switch would set
|
||||
credential `foo` to `bar`.)
|
||||
|
||||
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.
|
||||
|
||||
4. 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
|
||||
EFI System Partition, which are then picked up by `systemd-stub` and passed
|
||||
to the kernel and ultimately userpace where systemd receives them. This is
|
||||
useful to implement secure parameterization of vendor-built and signed
|
||||
initial RAM disks, as userspace can place credentials next to these EFI
|
||||
kernels, and be sure they can be accessed securely from initrd context.
|
||||
|
||||
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:
|
||||
|
||||
```
|
||||
systemd-nspawn --set-credential=mycred:supersecret -i test.raw -b
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
qemu-system-x86_64 \
|
||||
-machine type=q35,accel=kvm,smm=on \
|
||||
-smp 2 \
|
||||
-m 1G \
|
||||
-cpu host \
|
||||
-nographic \
|
||||
-nodefaults \
|
||||
-serial mon:stdio \
|
||||
-drive if=none,id=hd,file=test.raw,format=raw \
|
||||
-device virtio-scsi-pci,id=scsi \
|
||||
-device scsi-hd,drive=hd,bootindex=1 \
|
||||
-fw_cfg name=opt/io.systemd.credentials/mycred,string=supersecret
|
||||
```
|
||||
|
||||
Either of these lines will boot a disk image `test.raw`, once as container via
|
||||
`systemd-nspawn`, and once as VM via `qemu`. In each case the credential
|
||||
`mycred` is set to `supersecret`.
|
||||
|
||||
Inside of the system invoked that way the credential may then be viewed:
|
||||
|
||||
```sh
|
||||
systemd-creds --system cat mycred
|
||||
```
|
||||
|
||||
Or propagated to services further down:
|
||||
|
||||
```
|
||||
systemd-run -p LoadCredential=mycred -P --wait systemd-creds cat mycred
|
||||
```
|
||||
|
||||
## Well-Known Credentials
|
||||
|
||||
Various services shipped with `systemd` consume credentials for tweaking behaviour:
|
||||
|
||||
* [`systemd-sysusers(8)`](https://www.freedesktop.org/software/systemd/man/systemd-sysusers.html)
|
||||
will look for the credentials `passwd.hashed-password.<username>`,
|
||||
`passwd.plaintext-password.<username>` and `passwd.shell.<username>` to
|
||||
configure the password (either in UNIX hashed form, or plaintext) or shell of
|
||||
system users created. Replace `<username>` with the system user of your
|
||||
choice, for example `root`.
|
||||
|
||||
* [`systemd-firstboot(1)`](https://www.freedesktop.org/software/systemd/man/systemd-firstboot.html)
|
||||
will look for the credentials `firstboot.locale`, `firstboot.locale-messages`,
|
||||
`firstboot.keymap`, `firstboot.timezone`, that configure locale, keymap or
|
||||
timezone settings in case the data is not yet set in `/etc/`.
|
||||
|
||||
In future more services are likely to gain support for consuming credentials.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
systemd-nspawn -i test.raw \
|
||||
--set-credential=passwd.hashed-password.root:$(mkpasswd mysecret) \
|
||||
--set-credential=firstboot.locale:C.UTF-8 \
|
||||
-b
|
||||
```
|
||||
|
||||
This boots the specified disk image as `systemd-nspawn` container, and passes
|
||||
the root password `mysecret`and default locale `C.UTF-8` to use to it. This
|
||||
data is then propagated by default to `systemd-sysusers.service` and
|
||||
`systemd-firstboot.service`, where it is applied. (Note that these services
|
||||
will only do so if these settings in `/etc/` are so far unset, i.e. they only
|
||||
have an effect on *unprovisioned* systems, and will never override data already
|
||||
established in `/etc/`.) A similar line for qemu is:
|
||||
|
||||
```
|
||||
qemu-system-x86_64 \
|
||||
-machine type=q35,accel=kvm,smm=on \
|
||||
-smp 2 \
|
||||
-m 1G \
|
||||
-cpu host \
|
||||
-nographic \
|
||||
-nodefaults \
|
||||
-serial mon:stdio \
|
||||
-drive if=none,id=hd,file=test.raw,format=raw \
|
||||
-device virtio-scsi-pci,id=scsi \
|
||||
-device scsi-hd,drive=hd,bootindex=1 \
|
||||
-fw_cfg name=opt/io.systemd.credentials/passwd.hashed-password.root,string=$(mkpasswd mysecret) \
|
||||
-fw_cfg name=opt/io.systemd.credentials/firstboot.locale,string=C.UTF-8
|
||||
```
|
||||
|
||||
## Relevant Paths
|
||||
|
||||
From *service* perspective the runtime path to find loaded credentials in is
|
||||
provided in the `$CREDENTIALS_DIRECTORY` environment variable.
|
||||
|
||||
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
|
||||
credentials that must be decrypted/validated before use, such as those from
|
||||
`systemd-stub`).
|
||||
|
||||
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/`,
|
||||
`/usr/lib/credstore/`. `LoadCredentialEncrypted=` will also search
|
||||
`/etc/credstore.encrypted/` and similar directories. These directories are
|
||||
hence a great place to store credentials to load on the system.
|
@ -73,6 +73,8 @@
|
||||
<term><varname>systemd.machine_id=</varname></term>
|
||||
<term><varname>systemd.unified_cgroup_hierarchy</varname></term>
|
||||
<term><varname>systemd.legacy_systemd_cgroup_controller</varname></term>
|
||||
<term><varname>systemd.set_credential=</varname></term>
|
||||
<term><varname>systemd.import_credentials=</varname></term>
|
||||
<listitem>
|
||||
<para>Parameters understood by the system and service
|
||||
manager to control system behavior. For details, see
|
||||
|
@ -41,6 +41,9 @@
|
||||
<varname>SetCredentialEncrypted=</varname> settings, see
|
||||
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||
details.</para>
|
||||
|
||||
<para>For further information see <ulink url="https://systemd.io/CREDENTIALS">System and Service
|
||||
Credentials</ulink> documentation.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
|
@ -3039,20 +3039,31 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
||||
is absolute it is opened as regular file and the credential data is read from it. If the absolute
|
||||
path refers to an <constant>AF_UNIX</constant> stream socket in the file system a connection is made
|
||||
to it (only once at unit start-up) and the credential data read from the connection, providing an
|
||||
easy IPC integration point for dynamically providing credentials from other services. If the
|
||||
specified path is not absolute and itself qualifies as valid credential identifier it is understood
|
||||
to refer to a credential that the service manager itself received via the
|
||||
<varname>$CREDENTIALS_DIRECTORY</varname> environment variable, which may be used to propagate
|
||||
credentials from an invoking environment (e.g. a container manager that invoked the service manager)
|
||||
into a service. The contents of the file/socket may be arbitrary binary or textual data, including
|
||||
newline characters and <constant>NUL</constant> bytes. If the file system path is omitted it is
|
||||
chosen identical to the credential name, i.e. this is a terse way do declare credentials to inherit
|
||||
from the service manager into a service. This option may be used multiple times, each time defining
|
||||
an additional credential to pass to the unit. Alternatively, if the path is a directory, every file
|
||||
in that directory will be loaded as a separate credential. The ID for each credential will be the
|
||||
easy IPC integration point for dynamically transferring credentials from other services.</para>
|
||||
|
||||
<para>If the specified path is not absolute and itself qualifies as valid credential identifier it is
|
||||
attempted to find a credential that the service manager itself received under the specified name —
|
||||
which may be used to propagate credentials from an invoking environment (e.g. a container manager
|
||||
that invoked the service manager) into a service. If no matching system credential is found, the
|
||||
directories <filename>/etc/credstore/</filename>, <filename>/run/credstore/</filename> and
|
||||
<filename>/usr/lib/credstore/</filename> are searched for files under the credential's name — which
|
||||
hence are recommended locations for credential data on disk. If
|
||||
<varname>LoadCredentialEncrypted=</varname> is used <filename>/run/credstore.encrypted/</filename>,
|
||||
<filename>/etc/credstore.encrypted/</filename>, and
|
||||
<filename>/usr/lib/credstore.encrypted/</filename> are searched as well.</para>
|
||||
|
||||
<para>If the file system path is omitted it is chosen identical to the credential name, i.e. this is
|
||||
a terse way to declare credentials to inherit from the service manager into a service. This option
|
||||
may be used multiple times, each time defining an additional credential to pass to the unit.</para>
|
||||
|
||||
<para>If an absolute path referring to a directory is specified, every file in that directory
|
||||
(recursively) will be loaded as a separate credential. The ID for each credential will be the
|
||||
provided ID suffixed with <literal>_$FILENAME</literal> (e.g., <literal>Key_file1</literal>). When
|
||||
loading from a directory, symlinks will be ignored.</para>
|
||||
|
||||
<para>The contents of the file/socket may be arbitrary binary or textual data, including newline
|
||||
characters and <constant>NUL</constant> bytes.</para>
|
||||
|
||||
<para>The <varname>LoadCredentialEncrypted=</varname> setting is identical to
|
||||
<varname>LoadCredential=</varname>, except that the credential data is decrypted and authenticated
|
||||
before being passed on to the executed processes. Specifically, the referenced path should refer to a
|
||||
@ -3077,10 +3088,23 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
||||
|
||||
<para>In order to reference the path a credential may be read from within a
|
||||
<varname>ExecStart=</varname> command line use <literal>${CREDENTIALS_DIRECTORY}/mycred</literal>,
|
||||
e.g. <literal>ExecStart=cat ${CREDENTIALS_DIRECTORY}/mycred</literal>.</para>
|
||||
e.g. <literal>ExecStart=cat ${CREDENTIALS_DIRECTORY}/mycred</literal>. In order to reference the path
|
||||
a credential may be read from within a <varname>Environment=</varname> line use
|
||||
<literal>%d/mycred</literal>, e.g. <literal>Environment=MYCREDPATH=%d/mycred</literal>.</para>
|
||||
|
||||
<para>Currently, an accumulated credential size limit of 1 MB per unit is enforced.</para>
|
||||
|
||||
<para>The service manager itself may receive system credentials that can be propagated to services
|
||||
from a hosting container manager or VM hypervisor. See the <ulink
|
||||
url="https://systemd.io/CONTAINER_INTERFACE">Container Interface</ulink> documentation for details
|
||||
about the former. For the latter, use the <command>qemu</command> <literal>fw_cfg</literal> node
|
||||
<literal>opt/io.systemd.credentials/</literal>. Example qemu 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>
|
||||
|
||||
<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
|
||||
credential ID in its socket name. Use <citerefentry
|
||||
@ -3094,7 +3118,10 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
||||
ID requested. Example: <literal>\0adf9d86b6eda275e/unit/foobar.service/credx</literal> in case the
|
||||
credential <literal>credx</literal> is requested for a unit <literal>foobar.service</literal>. This
|
||||
functionality is useful for using a single listening socket to serve credentials to multiple
|
||||
consumers.</para></listitem>
|
||||
consumers.</para>
|
||||
|
||||
<para>For further information see <ulink url="https://systemd.io/CREDENTIALS">System and Service
|
||||
Credentials</ulink> documentation.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
|
@ -944,6 +944,29 @@
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.set_credential=</varname></term>
|
||||
|
||||
<listitem><para>Sets a system credential, which can then be propagated to system services using the
|
||||
<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
|
||||
<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>
|
||||
|
||||
<para>For further information see <ulink url="https://systemd.io/CREDENTIALS">System and Service
|
||||
Credentials</ulink> documentation.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>systemd.import_credentials=</varname></term>
|
||||
|
||||
<listitem><para>Takes a boolean argument. If false disables importing credentials from the kernel
|
||||
command line, qemu_fw_cfg subsystem or the kernel command line.</para></listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><varname>quiet</varname></term>
|
||||
|
||||
|
@ -2595,6 +2595,42 @@ static int write_credential(
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char **credential_search_path(
|
||||
const ExecParameters *params,
|
||||
bool encrypted) {
|
||||
|
||||
_cleanup_strv_free_ char **l = NULL;
|
||||
|
||||
assert(params);
|
||||
|
||||
/* Assemble a search path to find credentials in. We'll look in /etc/credstore/ (and similar
|
||||
* directories in /usr/lib/ + /run/) for all types of credentials. If we are looking for encrypted
|
||||
* credentials, also look in /etc/credstore.encrypted/ (and similar dirs). */
|
||||
|
||||
if (encrypted) {
|
||||
if (strv_extend(&l, params->received_encrypted_credentials_directory) < 0)
|
||||
return NULL;
|
||||
|
||||
if (strv_extend_strv(&l, CONF_PATHS_STRV("credstore.encrypted"), /* filter_duplicates= */ true) < 0)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (params->received_credentials_directory)
|
||||
if (strv_extend(&l, params->received_credentials_directory) < 0)
|
||||
return NULL;
|
||||
|
||||
if (strv_extend_strv(&l, CONF_PATHS_STRV("credstore"), /* filter_duplicates= */ true) < 0)
|
||||
return NULL;
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
_cleanup_free_ char *t = strv_join(l, ":");
|
||||
|
||||
log_debug("Credential search path is: %s", t);
|
||||
}
|
||||
|
||||
return TAKE_PTR(l);
|
||||
}
|
||||
|
||||
static int load_credential(
|
||||
const ExecContext *context,
|
||||
const ExecParameters *params,
|
||||
@ -2609,11 +2645,12 @@ static int load_credential(
|
||||
uint64_t *left) {
|
||||
|
||||
ReadFullFileFlags flags = READ_FULL_FILE_SECURE|READ_FULL_FILE_FAIL_WHEN_LARGER;
|
||||
_cleanup_strv_free_ char **search_path = NULL;
|
||||
_cleanup_(erase_and_freep) char *data = NULL;
|
||||
_cleanup_free_ char *j = NULL, *bindname = NULL;
|
||||
_cleanup_free_ char *bindname = NULL;
|
||||
const char *source = NULL;
|
||||
bool missing_ok = true;
|
||||
const char *source;
|
||||
size_t size, add;
|
||||
size_t size, add, maxsz;
|
||||
int r;
|
||||
|
||||
assert(context);
|
||||
@ -2624,10 +2661,25 @@ static int load_credential(
|
||||
assert(write_dfd >= 0);
|
||||
assert(left);
|
||||
|
||||
if (path_is_absolute(path) || read_dfd >= 0) {
|
||||
/* If this is an absolute path (or a directory fd is specifier relative which to read), read
|
||||
* the data directly from it, and support AF_UNIX sockets */
|
||||
if (read_dfd >= 0) {
|
||||
/* If a directory fd is specified, then read the file directly from that dir. In this case we
|
||||
* won't do AF_UNIX stuff (we simply don't want to recursively iterate down a tree of AF_UNIX
|
||||
* IPC sockets). It's OK if a file vanishes here in the time we enumerate it and intend to
|
||||
* open it. */
|
||||
|
||||
if (!filename_is_valid(path)) /* safety check */
|
||||
return -EINVAL;
|
||||
|
||||
missing_ok = true;
|
||||
source = path;
|
||||
|
||||
} else if (path_is_absolute(path)) {
|
||||
/* If this is an absolute path, read the data directly from it, and support AF_UNIX
|
||||
* sockets */
|
||||
|
||||
if (!path_is_valid(path)) /* safety check */
|
||||
return -EINVAL;
|
||||
|
||||
flags |= READ_FULL_FILE_CONNECT_SOCKET;
|
||||
|
||||
/* Pass some minimal info about the unit and the credential name we are looking to acquire
|
||||
@ -2636,25 +2688,50 @@ static int load_credential(
|
||||
return -ENOMEM;
|
||||
|
||||
missing_ok = false;
|
||||
source = path;
|
||||
|
||||
} else if (params->received_credentials) {
|
||||
/* If this is a relative path, take it relative to the credentials we received
|
||||
* ourselves. We don't support the AF_UNIX stuff in this mode, since we are operating
|
||||
* on a credential store, i.e. this is guaranteed to be regular files. */
|
||||
j = path_join(params->received_credentials, path);
|
||||
if (!j)
|
||||
} else if (credential_name_valid(path)) {
|
||||
/* If this is a relative path, take it as credential name relative to the credentials
|
||||
* directory we received ourselves. We don't support the AF_UNIX stuff in this mode, since we
|
||||
* are operating on a credential store, i.e. this is guaranteed to be regular files. */
|
||||
|
||||
search_path = credential_search_path(params, encrypted);
|
||||
if (!search_path)
|
||||
return -ENOMEM;
|
||||
|
||||
source = j;
|
||||
missing_ok = true;
|
||||
} else
|
||||
source = NULL;
|
||||
|
||||
if (source)
|
||||
if (encrypted)
|
||||
flags |= READ_FULL_FILE_UNBASE64;
|
||||
|
||||
maxsz = encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX;
|
||||
|
||||
if (search_path) {
|
||||
STRV_FOREACH(d, search_path) {
|
||||
_cleanup_free_ char *j = NULL;
|
||||
|
||||
j = path_join(*d, path);
|
||||
if (!j)
|
||||
return -ENOMEM;
|
||||
|
||||
r = read_full_file_full(
|
||||
AT_FDCWD, j, /* path is absolute, hence pass AT_FDCWD as nop dir fd here */
|
||||
UINT64_MAX,
|
||||
maxsz,
|
||||
flags,
|
||||
NULL,
|
||||
&data, &size);
|
||||
if (r != -ENOENT)
|
||||
break;
|
||||
}
|
||||
} else if (source)
|
||||
r = read_full_file_full(
|
||||
read_dfd, source,
|
||||
UINT64_MAX,
|
||||
encrypted ? CREDENTIAL_ENCRYPTED_SIZE_MAX : CREDENTIAL_SIZE_MAX,
|
||||
flags | (encrypted ? READ_FULL_FILE_UNBASE64 : 0),
|
||||
maxsz,
|
||||
flags,
|
||||
bindname,
|
||||
&data, &size);
|
||||
else
|
||||
|
@ -406,7 +406,8 @@ struct ExecParameters {
|
||||
const char *cgroup_path;
|
||||
|
||||
char **prefix;
|
||||
const char *received_credentials;
|
||||
const char *received_credentials_directory;
|
||||
const char *received_encrypted_credentials_directory;
|
||||
|
||||
const char *confirm_spawn;
|
||||
|
||||
|
551
src/core/import-creds.c
Normal file
551
src/core/import-creds.c
Normal file
@ -0,0 +1,551 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/mount.h>
|
||||
|
||||
#include "copy.h"
|
||||
#include "creds-util.h"
|
||||
#include "fileio.h"
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "import-creds.h"
|
||||
#include "io-util.h"
|
||||
#include "mkdir-label.h"
|
||||
#include "mount-util.h"
|
||||
#include "mountpoint-util.h"
|
||||
#include "parse-util.h"
|
||||
#include "path-util.h"
|
||||
#include "proc-cmdline.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "strv.h"
|
||||
|
||||
/* This imports credentials passed in from environments higher up (VM manager, boot loader, …) and rearranges
|
||||
* them so that later code can access them using our regular credential protocol
|
||||
* (i.e. $CREDENTIALS_DIRECTORY). It's supposed to be minimal glue to unify behaviour how PID 1 (and
|
||||
* generators invoked by it) can acquire credentials from outside, to mimic how we support it for containers,
|
||||
* but on VM/physical environments.
|
||||
*
|
||||
* This does three things:
|
||||
*
|
||||
* 1. It imports credentials picked up by sd-boot (and placed in the /.extra/credentials/ dir in the initrd)
|
||||
* and puts them in /run/credentials/@encrypted/. Note that during the initrd→host transition the initrd root
|
||||
* file system is cleaned out, thus it is essential we pick up these files before they are deleted. Note
|
||||
* that these credentials originate from an untrusted source, i.e. the ESP and are not
|
||||
* pre-authenticated. They still have to be authenticated before use.
|
||||
*
|
||||
* 2. It imports credentials from /proc/cmdline and puts them in /run/credentials/@system/. These come from a
|
||||
* trusted environment (i.e. the boot loader), and are typically authenticated (if authentication is done
|
||||
* at all). However, they are world-readable, which might be less than ideal. Hence only use this for data
|
||||
* that doesn't require trust.
|
||||
*
|
||||
* 3. It imports credentials passed in through qemu's fw_cfg logic. Specifically, credential data passed in
|
||||
* /sys/firmware/qemu_fw_cfg/by_name/opt/io.systemd.credentials/ is picked up and also placed in
|
||||
* /run/credentials/@system/.
|
||||
*
|
||||
* If it picked up any credentials it will set the $CREDENTIALS_DIRECTORY and
|
||||
* $ENCRYPTED_CREDENTIALS_DIRECTORY environment variables to point to these directories, so that processes
|
||||
* can find them there later on. If "ramfs" is available $CREDENTIALS_DIRECTORY will be backed by it (but
|
||||
* $ENCRYPTED_CREDENTIALS_DIRECTORY is just a regular tmpfs).
|
||||
*
|
||||
* Net result: the service manager can pick up trusted credentials from $CREDENTIALS_DIRECTORY afterwards,
|
||||
* and untrusted ones from $ENCRYPTED_CREDENTIALS_DIRECTORY. */
|
||||
|
||||
typedef struct ImportCredentialContext {
|
||||
int target_dir_fd;
|
||||
size_t size_sum;
|
||||
unsigned n_credentials;
|
||||
} ImportCredentialContext;
|
||||
|
||||
static void import_credentials_context_free(ImportCredentialContext *c) {
|
||||
assert(c);
|
||||
|
||||
c->target_dir_fd = safe_close(c->target_dir_fd);
|
||||
}
|
||||
|
||||
static int acquire_encrypted_credential_directory(ImportCredentialContext *c) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
|
||||
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");
|
||||
|
||||
c->target_dir_fd = open(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, 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 c->target_dir_fd;
|
||||
}
|
||||
|
||||
static int open_credential_file_for_write(int target_dir_fd, const char *dir_name, const char *n) {
|
||||
int fd;
|
||||
|
||||
assert(target_dir_fd >= 0);
|
||||
assert(dir_name);
|
||||
assert(n);
|
||||
|
||||
fd = openat(target_dir_fd, n, O_WRONLY|O_CLOEXEC|O_CREAT|O_EXCL|O_NOFOLLOW, 0400);
|
||||
if (fd < 0) {
|
||||
if (errno == EEXIST) /* In case of EEXIST we'll only debug log! */
|
||||
return log_debug_errno(errno, "Credential '%s' set twice, ignoring.", n);
|
||||
|
||||
return log_error_errno(errno, "Failed to create %s/%s: %m", dir_name, n);
|
||||
}
|
||||
|
||||
return fd;
|
||||
}
|
||||
|
||||
static bool credential_size_ok(ImportCredentialContext *c, const char *name, uint64_t size) {
|
||||
assert(c);
|
||||
assert(name);
|
||||
|
||||
if (size > CREDENTIAL_SIZE_MAX) {
|
||||
log_warning("Credential '%s' is larger than allowed limit (%s > %s), skipping.", name, FORMAT_BYTES(size), FORMAT_BYTES(CREDENTIAL_SIZE_MAX));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (size > CREDENTIALS_TOTAL_SIZE_MAX - c->size_sum) {
|
||||
log_warning("Accumulated credential size would be above allowed limit (%s+%s > %s), skipping '%s'.",
|
||||
FORMAT_BYTES(c->size_sum), FORMAT_BYTES(size), FORMAT_BYTES(CREDENTIALS_TOTAL_SIZE_MAX), name);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int finalize_credentials_dir(const char *dir, const char *envvar) {
|
||||
int r;
|
||||
|
||||
assert(dir);
|
||||
assert(envvar);
|
||||
|
||||
/* Try to make the credentials directory read-only now */
|
||||
|
||||
r = make_mount_point(dir);
|
||||
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);
|
||||
|
||||
if (setenv(envvar, dir, /* overwrite= */ true) < 0)
|
||||
return log_error_errno(errno, "Failed to set $%s environment variable: %m", envvar);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int import_credentials_boot(void) {
|
||||
_cleanup_(import_credentials_context_free) ImportCredentialContext context = {
|
||||
.target_dir_fd = -1,
|
||||
};
|
||||
int r;
|
||||
|
||||
/* systemd-stub will wrap sidecar *.cred files from the UEFI kernel image directory into initrd
|
||||
* cpios, so that they unpack into /.extra/. We'll pick them up from there and copy them into /run/
|
||||
* so that we can access them during the entire runtime (note that the initrd file system is erased
|
||||
* during the initrd → host transition). Note that these credentials originate from an untrusted
|
||||
* source (i.e. the ESP typically) and thus need to be authenticated later. We thus put them in a
|
||||
* directory separate from the usual credentials which are from a trusted source. */
|
||||
|
||||
if (!in_initrd())
|
||||
return 0;
|
||||
|
||||
FOREACH_STRING(p,
|
||||
"/.extra/credentials/", /* specific to this boot menu */
|
||||
"/.extra/global_credentials/") { /* boot partition wide */
|
||||
|
||||
_cleanup_free_ DirectoryEntries *de = NULL;
|
||||
_cleanup_close_ int source_dir_fd = -1;
|
||||
|
||||
source_dir_fd = open(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (source_dir_fd < 0) {
|
||||
if (errno == ENOENT) {
|
||||
log_debug("No credentials passed via %s.", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
log_warning_errno(errno, "Failed to open '%s', ignoring: %m", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = readdir_all(source_dir_fd, RECURSE_DIR_SORT|RECURSE_DIR_IGNORE_DOT, &de);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to read '%s' contents, ignoring: %m", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < de->n_entries; i++) {
|
||||
const struct dirent *d = de->entries[i];
|
||||
_cleanup_close_ int cfd = -1, nfd = -1;
|
||||
_cleanup_free_ char *n = NULL;
|
||||
const char *e;
|
||||
struct stat st;
|
||||
|
||||
e = endswith(d->d_name, ".cred");
|
||||
if (!e)
|
||||
continue;
|
||||
|
||||
/* drop .cred suffix (which we want in the ESP sidecar dir, but not for our internal
|
||||
* processing) */
|
||||
n = strndup(d->d_name, e - d->d_name);
|
||||
if (!n)
|
||||
return log_oom();
|
||||
|
||||
if (!credential_name_valid(n)) {
|
||||
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(&context, n, st.st_size))
|
||||
continue;
|
||||
|
||||
r = acquire_encrypted_credential_directory(&context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
nfd = open_credential_file_for_write(context.target_dir_fd, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, n);
|
||||
if (nfd == -EEXIST)
|
||||
continue;
|
||||
if (nfd < 0)
|
||||
return r;
|
||||
|
||||
r = copy_bytes(cfd, nfd, st.st_size, 0);
|
||||
if (r < 0) {
|
||||
(void) unlinkat(context.target_dir_fd, n, 0);
|
||||
return log_error_errno(r, "Failed to create credential '%s': %m", n);
|
||||
}
|
||||
|
||||
context.size_sum += st.st_size;
|
||||
context.n_credentials++;
|
||||
|
||||
log_debug("Successfully copied boot credential '%s'.", n);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.n_credentials > 0) {
|
||||
log_debug("Imported %u credentials from boot loader.", context.n_credentials);
|
||||
|
||||
r = finalize_credentials_dir(ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY, "ENCRYPTED_CREDENTIALS_DIRECTORY");
|
||||
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_ char *n = NULL;
|
||||
_cleanup_close_ int nfd = -1;
|
||||
const char *colon;
|
||||
size_t l;
|
||||
int r;
|
||||
|
||||
assert(key);
|
||||
|
||||
if (!proc_cmdline_key_streq(key, "systemd.set_credential"))
|
||||
return 0;
|
||||
|
||||
colon = value ? strchr(value, ':') : NULL;
|
||||
if (!colon) {
|
||||
log_warning("Credential assignment through kernel command line lacks ':' character, ignoring: %s", value);
|
||||
return 0;
|
||||
}
|
||||
|
||||
n = strndup(value, colon - value);
|
||||
if (!n)
|
||||
return log_oom();
|
||||
|
||||
if (!credential_name_valid(n)) {
|
||||
log_warning("Credential name '%s' is invalid, ignoring.", n);
|
||||
return 0;
|
||||
}
|
||||
|
||||
colon++;
|
||||
l = strlen(colon);
|
||||
|
||||
if (!credential_size_ok(c, n, l))
|
||||
return 0;
|
||||
|
||||
r = acquire_credential_directory(c);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
nfd = open_credential_file_for_write(c->target_dir_fd, SYSTEM_CREDENTIALS_DIRECTORY, n);
|
||||
if (nfd == -EEXIST)
|
||||
return 0;
|
||||
if (nfd < 0)
|
||||
return r;
|
||||
|
||||
r = loop_write(nfd, colon, 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");
|
||||
}
|
||||
|
||||
c->size_sum += l;
|
||||
c->n_credentials++;
|
||||
|
||||
log_debug("Successfully processed kernel command line credential '%s'.", n);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int import_credentials_proc_cmdline(ImportCredentialContext *c) {
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
|
||||
r = proc_cmdline_parse(proc_cmdline_callback, c, 0);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse /proc/cmdline: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define QEMU_FWCFG_PATH "/sys/firmware/qemu_fw_cfg/by_name/opt/io.systemd.credentials"
|
||||
|
||||
static int import_credentials_qemu(ImportCredentialContext *c) {
|
||||
_cleanup_free_ DirectoryEntries *de = NULL;
|
||||
_cleanup_close_ int source_dir_fd = -1;
|
||||
int r;
|
||||
|
||||
assert(c);
|
||||
|
||||
source_dir_fd = open(QEMU_FWCFG_PATH, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
|
||||
if (source_dir_fd < 0) {
|
||||
if (errno == ENOENT) {
|
||||
log_debug("No credentials passed via fw_cfg.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
log_warning_errno(errno, "Failed to open '" QEMU_FWCFG_PATH "', 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 '" QEMU_FWCFG_PATH "' contents, ignoring: %m");
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < de->n_entries; i++) {
|
||||
const struct dirent *d = de->entries[i];
|
||||
_cleanup_close_ int vfd = -1, rfd = -1, nfd = -1;
|
||||
_cleanup_free_ char *szs = NULL;
|
||||
uint64_t sz;
|
||||
|
||||
if (!credential_name_valid(d->d_name)) {
|
||||
log_warning("Credential '%s' has invalid name, ignoring.", d->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
vfd = openat(source_dir_fd, d->d_name, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
|
||||
if (vfd < 0) {
|
||||
log_warning_errno(errno, "Failed to open '" QEMU_FWCFG_PATH "'/%s/, ignoring: %m", d->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = read_virtual_file_at(vfd, "size", LINE_MAX, &szs, NULL);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to read '" QEMU_FWCFG_PATH "'/%s/size, ignoring: %m", d->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = safe_atou64(strstrip(szs), &sz);
|
||||
if (r < 0) {
|
||||
log_warning_errno(r, "Failed to parse size of credential '%s', ignoring: %s", d->d_name, szs);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!credential_size_ok(c, d->d_name, sz))
|
||||
continue;
|
||||
|
||||
/* Ideally we'd just symlink the data here. Alas the kernel driver exports the raw file as
|
||||
* having size zero, and we'd rather not have applications support such credential
|
||||
* files. Let's hence copy the files to make them regular. */
|
||||
|
||||
rfd = openat(vfd, "raw", O_RDONLY|O_CLOEXEC);
|
||||
if (rfd < 0) {
|
||||
log_warning_errno(r, "Failed to open '" QEMU_FWCFG_PATH "'/%s/raw, ignoring: %m", d->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = acquire_credential_directory(c);
|
||||
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 r;
|
||||
|
||||
r = copy_bytes(rfd, nfd, sz, 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 += sz;
|
||||
c->n_credentials++;
|
||||
|
||||
log_debug("Successfully copied qemu fw_cfg credential '%s'.", d->d_name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int import_credentials_trusted(void) {
|
||||
_cleanup_(import_credentials_context_free) ImportCredentialContext c = {
|
||||
.target_dir_fd = -1,
|
||||
};
|
||||
int q, r;
|
||||
|
||||
r = import_credentials_qemu(&c);
|
||||
q = import_credentials_proc_cmdline(&c);
|
||||
|
||||
if (c.n_credentials > 0) {
|
||||
int z;
|
||||
|
||||
log_debug("Imported %u credentials from kernel command line/fw_cfg.", c.n_credentials);
|
||||
|
||||
z = finalize_credentials_dir(SYSTEM_CREDENTIALS_DIRECTORY, "CREDENTIALS_DIRECTORY");
|
||||
if (z < 0)
|
||||
return z;
|
||||
}
|
||||
|
||||
return r < 0 ? r : q;
|
||||
}
|
||||
|
||||
static int symlink_credential_dir(const char *envvar, const char *path, const char *where) {
|
||||
int r;
|
||||
|
||||
assert(envvar);
|
||||
assert(path);
|
||||
assert(where);
|
||||
|
||||
if (!path_is_valid(path) || !path_is_absolute(path))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String specified via $%s is not a valid absolute path, refusing: %s", envvar, path);
|
||||
|
||||
/* If the env var already points to where we intend to create the symlink, then most likely we
|
||||
* already imported some creds earlier, and thus set the env var, and hence don't need to do
|
||||
* anything. */
|
||||
if (path_equal(path, where))
|
||||
return 0;
|
||||
|
||||
r = symlink_idempotent(path, where, /* make_relative= */ true);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to link $%s to %s: %m", envvar, where);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int import_credentials(void) {
|
||||
const char *received_creds_dir = NULL, *received_encrypted_creds_dir = NULL;
|
||||
bool envvar_set = false;
|
||||
int r, q;
|
||||
|
||||
r = get_credentials_dir(&received_creds_dir);
|
||||
if (r < 0 && r != -ENXIO) /* ENXIO → env var not set yet */
|
||||
log_warning_errno(r, "Failed to determine credentials directory, ignoring: %m");
|
||||
|
||||
envvar_set = r >= 0;
|
||||
|
||||
r = get_encrypted_credentials_dir(&received_encrypted_creds_dir);
|
||||
if (r < 0 && r != -ENXIO) /* ENXIO → env var not set yet */
|
||||
log_warning_errno(r, "Failed to determine encrypted credentials directory, ignoring: %m");
|
||||
|
||||
envvar_set = envvar_set || r >= 0;
|
||||
|
||||
if (envvar_set) {
|
||||
/* Maybe an earlier stage initrd already set this up? If so, don't try to import anything again. */
|
||||
log_debug("Not importing credentials, $CREDENTIALS_DIRECTORY or $ENCRYPTED_CREDENTIALS_DIRECTORY already set.");
|
||||
|
||||
/* But, let's make sure the creds are available from our regular paths. */
|
||||
if (received_creds_dir)
|
||||
r = symlink_credential_dir("CREDENTIALS_DIRECTORY", received_creds_dir, SYSTEM_CREDENTIALS_DIRECTORY);
|
||||
else
|
||||
r = 0;
|
||||
|
||||
if (received_encrypted_creds_dir) {
|
||||
q = symlink_credential_dir("ENCRYPTED_CREDENTIALS_DIRECTORY", received_encrypted_creds_dir, ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY);
|
||||
if (r >= 0)
|
||||
r = q;
|
||||
}
|
||||
|
||||
} else {
|
||||
_cleanup_free_ char *v = NULL;
|
||||
|
||||
r = proc_cmdline_get_key("systemd.import_credentials", PROC_CMDLINE_STRIP_RD_PREFIX, &v);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to check if 'systemd.import_credentials=' kernel command line option is set, ignoring: %m");
|
||||
else if (r > 0) {
|
||||
r = parse_boolean(v);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to parse 'systemd.import_credentials=' parameter, ignoring: %m");
|
||||
else if (r == 0) {
|
||||
log_notice("systemd.import_credentials=no is set, skipping importing of credentials.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
r = import_credentials_boot();
|
||||
|
||||
q = import_credentials_trusted();
|
||||
if (r >= 0)
|
||||
r = q;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
4
src/core/import-creds.h
Normal file
4
src/core/import-creds.h
Normal file
@ -0,0 +1,4 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
int import_credentials(void);
|
@ -10,6 +10,7 @@
|
||||
#include "macro.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "string-util.h"
|
||||
#include "virt.h"
|
||||
|
||||
#if HAVE_KMOD
|
||||
#include "module-util.h"
|
||||
@ -80,6 +81,10 @@ static bool has_virtio_rng(void) {
|
||||
|
||||
return r > 0;
|
||||
}
|
||||
|
||||
static bool in_qemu(void) {
|
||||
return IN_SET(detect_vm(), VIRTUALIZATION_KVM, VIRTUALIZATION_QEMU);
|
||||
}
|
||||
#endif
|
||||
|
||||
int kmod_setup(void) {
|
||||
@ -109,6 +114,9 @@ int kmod_setup(void) {
|
||||
#endif
|
||||
/* virtio_rng would be loaded by udev later, but real entropy might be needed very early */
|
||||
{ "virtio_rng", NULL, false, false, has_virtio_rng },
|
||||
|
||||
/* qemu_fw_cfg would be loaded by udev later, but we want to import credentials from it super early */
|
||||
{ "qemu_fw_cfg", "/sys/firmware/qemu_fw_cfg", false, false, in_qemu },
|
||||
};
|
||||
_cleanup_(kmod_unrefp) struct kmod_ctx *ctx = NULL;
|
||||
unsigned i;
|
||||
|
@ -51,6 +51,7 @@
|
||||
#include "hexdecoct.h"
|
||||
#include "hostname-setup.h"
|
||||
#include "ima-setup.h"
|
||||
#include "import-creds.h"
|
||||
#include "killall.h"
|
||||
#include "kmod-setup.h"
|
||||
#include "limits-util.h"
|
||||
@ -2180,6 +2181,10 @@ static int initialize_runtime(
|
||||
(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_system && !skip_setup)
|
||||
(void) import_credentials();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -777,9 +777,37 @@ static int manager_setup_sigchld_event_source(Manager *m) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int manager_find_credentials_dirs(Manager *m) {
|
||||
const char *e;
|
||||
int r;
|
||||
|
||||
assert(m);
|
||||
|
||||
r = get_credentials_dir(&e);
|
||||
if (r < 0) {
|
||||
if (r != -ENXIO)
|
||||
log_debug_errno(r, "Failed to determine credentials directory, ignoring: %m");
|
||||
} else {
|
||||
m->received_credentials_directory = strdup(e);
|
||||
if (!m->received_credentials_directory)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
r = get_encrypted_credentials_dir(&e);
|
||||
if (r < 0) {
|
||||
if (r != -ENXIO)
|
||||
log_debug_errno(r, "Failed to determine encrypted credentials directory, ignoring: %m");
|
||||
} else {
|
||||
m->received_encrypted_credentials_directory = strdup(e);
|
||||
if (!m->received_encrypted_credentials_directory)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int manager_new(LookupScope scope, ManagerTestRunFlags test_run_flags, Manager **_m) {
|
||||
_cleanup_(manager_freep) Manager *m = NULL;
|
||||
const char *e;
|
||||
int r;
|
||||
|
||||
assert(_m);
|
||||
@ -883,12 +911,9 @@ int manager_new(LookupScope scope, ManagerTestRunFlags test_run_flags, Manager *
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = get_credentials_dir(&e);
|
||||
if (r >= 0) {
|
||||
m->received_credentials = strdup(e);
|
||||
if (!m->received_credentials)
|
||||
return -ENOMEM;
|
||||
}
|
||||
r = manager_find_credentials_dirs(m);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_event_default(&m->event);
|
||||
if (r < 0)
|
||||
@ -1533,7 +1558,8 @@ Manager* manager_free(Manager *m) {
|
||||
|
||||
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++)
|
||||
m->prefix[dt] = mfree(m->prefix[dt]);
|
||||
free(m->received_credentials);
|
||||
free(m->received_credentials_directory);
|
||||
free(m->received_encrypted_credentials_directory);
|
||||
|
||||
free(m->watchdog_pretimeout_governor);
|
||||
free(m->watchdog_pretimeout_governor_overridden);
|
||||
|
@ -438,7 +438,8 @@ struct Manager {
|
||||
|
||||
/* Prefixes of e.g. RuntimeDirectory= */
|
||||
char *prefix[_EXEC_DIRECTORY_TYPE_MAX];
|
||||
char *received_credentials;
|
||||
char *received_credentials_directory;
|
||||
char *received_encrypted_credentials_directory;
|
||||
|
||||
/* Used in the SIGCHLD and sd_notify() message invocation logic to avoid that we dispatch the same event
|
||||
* multiple times on the same unit. */
|
||||
|
@ -73,6 +73,8 @@ libcore_sources = '''
|
||||
generator-setup.h
|
||||
ima-setup.c
|
||||
ima-setup.h
|
||||
import-creds.c
|
||||
import-creds.h
|
||||
job.c
|
||||
job.h
|
||||
kill.c
|
||||
|
@ -5032,7 +5032,8 @@ int unit_set_exec_params(Unit *u, ExecParameters *p) {
|
||||
p->cgroup_path = u->cgroup_path;
|
||||
SET_FLAG(p->flags, EXEC_CGROUP_DELEGATE, unit_cgroup_delegate(u));
|
||||
|
||||
p->received_credentials = u->manager->received_credentials;
|
||||
p->received_credentials_directory = u->manager->received_credentials_directory;
|
||||
p->received_encrypted_credentials_directory = u->manager->received_encrypted_credentials_directory;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -60,63 +60,76 @@ static const char* transcode_mode_table[_TRANSCODE_MAX] = {
|
||||
|
||||
DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING(transcode_mode, TranscodeMode);
|
||||
|
||||
static int open_credential_directory(DIR **ret) {
|
||||
_cleanup_free_ char *j = NULL;
|
||||
static int open_credential_directory(
|
||||
DIR **ret_dir,
|
||||
const char **ret_prefix,
|
||||
bool encrypted) {
|
||||
|
||||
const char *p;
|
||||
DIR *d;
|
||||
int r;
|
||||
|
||||
if (arg_system) {
|
||||
_cleanup_free_ char *cd = NULL;
|
||||
assert(ret_dir);
|
||||
|
||||
r = getenv_for_pid(1, "CREDENTIALS_DIRECTORY", &cd);
|
||||
if (arg_system)
|
||||
/* PID 1 ensures that system credentials are always accessible under the same fixed path. It
|
||||
* will create symlinks if necessary to guarantee that. */
|
||||
p = encrypted ?
|
||||
ENCRYPTED_SYSTEM_CREDENTIALS_DIRECTORY :
|
||||
SYSTEM_CREDENTIALS_DIRECTORY;
|
||||
else {
|
||||
/* Otherwise take the dirs from the env vars we got passed */
|
||||
r = (encrypted ? get_encrypted_credentials_dir : get_credentials_dir)(&p);
|
||||
if (r == -ENXIO) /* No environment variable? */
|
||||
goto not_found;
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!cd)
|
||||
return -ENXIO;
|
||||
|
||||
if (!path_is_absolute(cd) || !path_is_normalized(cd))
|
||||
return -EINVAL;
|
||||
|
||||
j = path_join("/proc/1/root", cd);
|
||||
if (!j)
|
||||
return -ENOMEM;
|
||||
|
||||
p = j;
|
||||
} else {
|
||||
r = get_credentials_dir(&p);
|
||||
if (r < 0)
|
||||
return r;
|
||||
return log_error_errno(r, "Failed to get credentials directory: %m");
|
||||
}
|
||||
|
||||
d = opendir(p);
|
||||
if (!d)
|
||||
return -errno;
|
||||
if (!d) {
|
||||
/* No such dir? Then no creds where passed. (We conditionalize this on arg_system, since for
|
||||
* the per-service case a non-existing path would indicate an issue since the env var would
|
||||
* be set incorrectly in that case.) */
|
||||
if (arg_system && errno == ENOENT)
|
||||
goto not_found;
|
||||
|
||||
return log_error_errno(errno, "Failed to open credentials directory '%s': %m", p);
|
||||
}
|
||||
|
||||
*ret_dir = d;
|
||||
|
||||
if (ret_prefix)
|
||||
*ret_prefix = p;
|
||||
|
||||
return 1;
|
||||
|
||||
not_found:
|
||||
*ret_dir = NULL;
|
||||
|
||||
if (ret_prefix)
|
||||
*ret_prefix = NULL;
|
||||
|
||||
*ret = d;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_list(int argc, char **argv, void *userdata) {
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
static int add_credentials_to_table(Table *t, bool encrypted) {
|
||||
_cleanup_(closedirp) DIR *d = NULL;
|
||||
const char *prefix;
|
||||
int r;
|
||||
|
||||
r = open_credential_directory(&d);
|
||||
if (r == -ENXIO)
|
||||
return log_error_errno(r, "No credentials received. (i.e. $CREDENTIALS_DIRECTORY not set or pointing to empty directory.)");
|
||||
assert(t);
|
||||
|
||||
r = open_credential_directory(&d, &prefix, encrypted);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open credentials directory: %m");
|
||||
|
||||
t = table_new("name", "secure", "size");
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
(void) table_set_align_percent(t, table_get_cell(t, 0, 2), 100);
|
||||
return r;
|
||||
if (!d)
|
||||
return 0; /* No creds dir set */
|
||||
|
||||
for (;;) {
|
||||
_cleanup_close_ int fd = -1;
|
||||
_cleanup_free_ char *j = NULL;
|
||||
const char *secure, *secure_color = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
struct dirent *de;
|
||||
struct stat st;
|
||||
|
||||
@ -149,7 +162,10 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
||||
if (!S_ISREG(st.st_mode))
|
||||
continue;
|
||||
|
||||
if ((st.st_mode & 0377) != 0) {
|
||||
if (encrypted) {
|
||||
secure = "encrypted";
|
||||
secure_color = ansi_highlight_green();
|
||||
} else if ((st.st_mode & 0377) != 0) {
|
||||
secure = "insecure"; /* Anything that is accessible more than read-only to its owner is insecure */
|
||||
secure_color = ansi_highlight_red();
|
||||
} else {
|
||||
@ -161,16 +177,49 @@ static int verb_list(int argc, char **argv, void *userdata) {
|
||||
secure_color = r ? ansi_highlight_green() : ansi_highlight_yellow4();
|
||||
}
|
||||
|
||||
j = path_join(prefix, de->d_name);
|
||||
if (!j)
|
||||
return log_oom();
|
||||
|
||||
r = table_add_many(
|
||||
t,
|
||||
TABLE_STRING, de->d_name,
|
||||
TABLE_STRING, secure,
|
||||
TABLE_SET_COLOR, secure_color,
|
||||
TABLE_SIZE, (uint64_t) st.st_size);
|
||||
TABLE_SIZE, (uint64_t) st.st_size,
|
||||
TABLE_STRING, j);
|
||||
if (r < 0)
|
||||
return table_log_add_error(r);
|
||||
}
|
||||
|
||||
return 1; /* Creds dir set */
|
||||
}
|
||||
|
||||
static int verb_list(int argc, char **argv, void *userdata) {
|
||||
_cleanup_(table_unrefp) Table *t = NULL;
|
||||
int r, q;
|
||||
|
||||
t = table_new("name", "secure", "size", "path");
|
||||
if (!t)
|
||||
return log_oom();
|
||||
|
||||
(void) table_set_align_percent(t, table_get_cell(t, 0, 2), 100);
|
||||
|
||||
r = add_credentials_to_table(t, /* encrypted= */ true);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
q = add_credentials_to_table(t, /* encrypted= */ false);
|
||||
if (q < 0)
|
||||
return q;
|
||||
|
||||
if (r == 0 && q == 0) {
|
||||
if (arg_system)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed to system.");
|
||||
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "No credentials passed. (i.e. $CREDENTIALS_DIRECTORY not set.)");
|
||||
}
|
||||
|
||||
if ((arg_json_format_flags & JSON_FORMAT_OFF) && table_get_rows(t) <= 1) {
|
||||
log_info("No credentials");
|
||||
return 0;
|
||||
@ -310,18 +359,15 @@ static int write_blob(FILE *f, const void *data, size_t size) {
|
||||
}
|
||||
|
||||
static int verb_cat(int argc, char **argv, void *userdata) {
|
||||
_cleanup_(closedirp) DIR *d = NULL;
|
||||
usec_t timestamp;
|
||||
int r, ret = 0;
|
||||
|
||||
r = open_credential_directory(&d);
|
||||
if (r == -ENXIO)
|
||||
return log_error_errno(r, "No credentials passed.");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open credentials directory: %m");
|
||||
timestamp = arg_timestamp != USEC_INFINITY ? arg_timestamp : now(CLOCK_REALTIME);
|
||||
|
||||
STRV_FOREACH(cn, strv_skip(argv, 1)) {
|
||||
_cleanup_(erase_and_freep) void *data = NULL;
|
||||
size_t size = 0;
|
||||
int encrypted;
|
||||
|
||||
if (!credential_name_valid(*cn)) {
|
||||
log_error("Credential name '%s' is not valid.", *cn);
|
||||
@ -330,19 +376,58 @@ static int verb_cat(int argc, char **argv, void *userdata) {
|
||||
continue;
|
||||
}
|
||||
|
||||
r = read_full_file_full(
|
||||
dirfd(d), *cn,
|
||||
UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE,
|
||||
NULL,
|
||||
(char**) &data, &size);
|
||||
if (r < 0) {
|
||||
/* Look both in regular and in encrypted credentials */
|
||||
for (encrypted = 0; encrypted < 2; encrypted ++) {
|
||||
_cleanup_(closedirp) DIR *d = NULL;
|
||||
|
||||
r = open_credential_directory(&d, NULL, encrypted);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to open credentials directory: %m");
|
||||
if (!d) /* Not set */
|
||||
continue;
|
||||
|
||||
r = read_full_file_full(
|
||||
dirfd(d), *cn,
|
||||
UINT64_MAX, SIZE_MAX,
|
||||
READ_FULL_FILE_SECURE|READ_FULL_FILE_WARN_WORLD_READABLE,
|
||||
NULL,
|
||||
(char**) &data, &size);
|
||||
if (r == -ENOENT) /* Not found */
|
||||
continue;
|
||||
if (r >= 0) /* Found */
|
||||
break;
|
||||
|
||||
log_error_errno(r, "Failed to read credential '%s': %m", *cn);
|
||||
if (ret >= 0)
|
||||
ret = r;
|
||||
}
|
||||
|
||||
if (encrypted >= 2) { /* Found nowhere */
|
||||
log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Credential '%s' not set.", *cn);
|
||||
if (ret >= 0)
|
||||
ret = -ENOENT;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (encrypted) {
|
||||
_cleanup_(erase_and_freep) void *plaintext = NULL;
|
||||
size_t plaintext_size;
|
||||
|
||||
r = decrypt_credential_and_warn(
|
||||
*cn,
|
||||
timestamp,
|
||||
arg_tpm2_device,
|
||||
data, size,
|
||||
&plaintext, &plaintext_size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
erase_and_free(data);
|
||||
data = TAKE_PTR(plaintext);
|
||||
size = plaintext_size;
|
||||
}
|
||||
|
||||
r = write_blob(stdout, data, size);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
@ -33,12 +33,12 @@ bool credential_name_valid(const char *s) {
|
||||
return filename_is_valid(s) && fdname_is_valid(s);
|
||||
}
|
||||
|
||||
int get_credentials_dir(const char **ret) {
|
||||
static int get_credentials_dir_internal(const char *envvar, const char **ret) {
|
||||
const char *e;
|
||||
|
||||
assert(ret);
|
||||
|
||||
e = secure_getenv("CREDENTIALS_DIRECTORY");
|
||||
e = secure_getenv(envvar);
|
||||
if (!e)
|
||||
return -ENXIO;
|
||||
|
||||
@ -49,6 +49,14 @@ int get_credentials_dir(const char **ret) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_credentials_dir(const char **ret) {
|
||||
return get_credentials_dir_internal("CREDENTIALS_DIRECTORY", ret);
|
||||
}
|
||||
|
||||
int get_encrypted_credentials_dir(const char **ret) {
|
||||
return get_credentials_dir_internal("ENCRYPTED_CREDENTIALS_DIRECTORY", ret);
|
||||
}
|
||||
|
||||
int read_credential(const char *name, void **ret, size_t *ret_size) {
|
||||
_cleanup_free_ char *fn = NULL;
|
||||
const char *d;
|
||||
|
@ -26,7 +26,13 @@
|
||||
|
||||
bool credential_name_valid(const char *s);
|
||||
|
||||
/* Where creds have been passed to the local execution context */
|
||||
int get_credentials_dir(const char **ret);
|
||||
int get_encrypted_credentials_dir(const char **ret);
|
||||
|
||||
/* Where creds have been passed to the system */
|
||||
#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);
|
||||
|
||||
|
@ -3,9 +3,16 @@
|
||||
set -e
|
||||
|
||||
TEST_DESCRIPTION="test credentials"
|
||||
NSPAWN_ARGUMENTS="--set-credential=mynspawncredential:strangevalue"
|
||||
NSPAWN_ARGUMENTS="${NSPAWN_ARGUMENTS:-} --set-credential=mynspawncredential:strangevalue"
|
||||
QEMU_OPTIONS="${QEMU_OPTIONS:-} -fw_cfg name=opt/io.systemd.credentials/myqemucredential,string=othervalue"
|
||||
KERNEL_APPEND="${KERNEL_APPEND:-} systemd.set_credential=kernelcmdlinecred:uff rd.systemd.import_credentials=no"
|
||||
|
||||
# shellcheck source=test/test-functions
|
||||
. "${TEST_BASE_DIR:?}/test-functions"
|
||||
|
||||
test_append_files() {
|
||||
instmods qemu_fw_cfg
|
||||
generate_module_dependencies
|
||||
}
|
||||
|
||||
do_test "$@"
|
||||
|
@ -23,17 +23,34 @@ rm /tmp/ts54-fallback
|
||||
[ "$(systemd-run -p LoadCredential=paff:/tmp/ts54-fallback -p SetCredential=paff:poff --pipe --wait systemd-creds cat paff)" = "poff" ]
|
||||
|
||||
if systemd-detect-virt -q -c ; then
|
||||
expected_credential=mynspawncredential
|
||||
expected_value=strangevalue
|
||||
elif [ -d /sys/firmware/qemu_fw_cfg/by_name ]; then
|
||||
# Verify that passing creds through kernel cmdline works
|
||||
[ "$(systemd-creds --system cat kernelcmdlinecred)" = "uff" ]
|
||||
|
||||
# If we aren't run in nspawn, we are run in qemu
|
||||
systemd-detect-virt -q -v
|
||||
expected_credential=myqemucredential
|
||||
expected_value=othervalue
|
||||
else
|
||||
echo "qemu_fw_cfg support missing in kernel. Sniff!"
|
||||
expected_credential=""
|
||||
expected_value=""
|
||||
fi
|
||||
|
||||
if [ "$expected_credential" != "" ] ; then
|
||||
# If this test is run in nspawn a credential should have been passed to us. See test/TEST-54-CREDS/test.sh
|
||||
[ "$(systemd-creds --system cat mynspawncredential)" = "strangevalue" ]
|
||||
[ "$(systemd-creds --system cat "$expected_credential")" = "$expected_value" ]
|
||||
|
||||
# Test that propagation from system credential to service credential works
|
||||
[ "$(systemd-run -p LoadCredential=mynspawncredential --pipe --wait systemd-creds cat mynspawncredential)" = "strangevalue" ]
|
||||
[ "$(systemd-run -p LoadCredential="$expected_credential" --pipe --wait systemd-creds cat "$expected_credential")" = "$expected_value" ]
|
||||
|
||||
# Check it also works, if we rename it while propagating it
|
||||
[ "$(systemd-run -p LoadCredential=miau:mynspawncredential --pipe --wait systemd-creds cat miau)" = "strangevalue" ]
|
||||
[ "$(systemd-run -p LoadCredential=miau:"$expected_credential" --pipe --wait systemd-creds cat miau)" = "$expected_value" ]
|
||||
|
||||
# Combine it with a fallback (which should have no effect, given the cred should be passed down)
|
||||
[ "$(systemd-run -p LoadCredential=mynspawncredential -p SetCredential=mynspawncredential:zzz --pipe --wait systemd-creds cat mynspawncredential)" = "strangevalue" ]
|
||||
[ "$(systemd-run -p LoadCredential="$expected_credential" -p SetCredential="$expected_credential":zzz --pipe --wait systemd-creds cat "$expected_credential")" = "$expected_value" ]
|
||||
fi
|
||||
|
||||
# Verify that the creds are immutable
|
||||
|
Loading…
Reference in New Issue
Block a user