mirror of
https://github.com/systemd/systemd
synced 2026-03-04 12:14:46 +01:00
Compare commits
13 Commits
d4739bc4d3
...
b6abc2acb4
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6abc2acb4 | ||
|
|
8b5cb69bc8 | ||
|
|
fabece9ccb | ||
|
|
30dd9f7391 | ||
|
|
b0d29bfdfd | ||
|
|
60cc90b959 | ||
|
|
3220cf394c | ||
|
|
3652872add | ||
|
|
bbb4e7f39f | ||
|
|
bb0c0d6f29 | ||
|
|
9db59d9283 | ||
|
|
71281a7655 | ||
|
|
2899fb024f |
20
TODO
20
TODO
@ -119,14 +119,18 @@ Features:
|
|||||||
|
|
||||||
* seccomp: maybe merge all filters we install into one with that libseccomp API that allows merging.
|
* seccomp: maybe merge all filters we install into one with that libseccomp API that allows merging.
|
||||||
|
|
||||||
* per-service credential system. Specifically: add LoadCredential= (for loading
|
* credentials system:
|
||||||
cred from file), AcquireCredential= (for asking user for cred, via
|
- maybe add AcquireCredential= for querying a cred via ask-password
|
||||||
ask-password), PassCredential= (for passing on credential systemd itself
|
- maybe try to acquire creds via keyring?
|
||||||
got). Then, place credentials in a per-service, immutable ramfs instance (so
|
- maybe try to pass creds via keyring?
|
||||||
that it cannot be swapped out), destroy after use. Also pass via keyring
|
- maybe optionally pass creds via memfd
|
||||||
(with graceful fallback to cover for containers). Define CredentialPath= for
|
- maybe add support for decrypting creds via TPM
|
||||||
defining subdir of /run/credentials/ where to place it. Set $CREDENTIAL_PATH
|
- maybe add support for decrypting/importing creds via pkcs11
|
||||||
env var for services to the result. Also pass via fd passing (optionally).
|
- make systemd-cryptsetup acquire pw via creds logic
|
||||||
|
- make PAMName= acquire pw via creds logic
|
||||||
|
- make macsec/wireguard code in networkd read key via creds logic
|
||||||
|
- make gatwayd/remote read key via creds logic
|
||||||
|
- add sd_notify() command for flushing out creds not needed anymore
|
||||||
|
|
||||||
* homed: during login resize fs automatically towards size goal. Specifically,
|
* homed: during login resize fs automatically towards size goal. Specifically,
|
||||||
resize to diskSize if possible, but leave a certain amount (configured by a
|
resize to diskSize if possible, but leave a certain amount (configured by a
|
||||||
|
|||||||
@ -131,6 +131,17 @@ manager, please consider supporting the following interfaces.
|
|||||||
`$container_host_variant_id=server`
|
`$container_host_variant_id=server`
|
||||||
`$container_host_version_id=10`
|
`$container_host_version_id=10`
|
||||||
|
|
||||||
|
5. systemd supports passing immutable binary data blobs with limited size and
|
||||||
|
restricted access to services via the `LoadCredential=` and `SetCredential=`
|
||||||
|
settings. The same protocol may be used to pass credentials from the
|
||||||
|
container manager to systemd itself. The credential data should be placed in
|
||||||
|
some location (ideally a read-only and non-swappable file system, like
|
||||||
|
'ramfs'), and the absolute path to this directory exported in the
|
||||||
|
`$CREDENTIALS_DIRECTORY` environment variable. If the container managers
|
||||||
|
does this, the credentials passed to the service manager can be propagated
|
||||||
|
to services via `LoadCredential=` (see ...). The container manager can
|
||||||
|
choose any path, but `/run/host/credentials` is recommended."
|
||||||
|
|
||||||
## Advanced Integration
|
## Advanced Integration
|
||||||
|
|
||||||
1. Consider syncing `/etc/localtime` from the host file system into the
|
1. Consider syncing `/etc/localtime` from the host file system into the
|
||||||
@ -228,7 +239,7 @@ care should be taken to avoid naming conflicts. `systemd` (and in particular
|
|||||||
inaccessible. Note that systemd when run as PID 1 in the container payload
|
inaccessible. Note that systemd when run as PID 1 in the container payload
|
||||||
will create these nodes on its own if not passed in by the container
|
will create these nodes on its own if not passed in by the container
|
||||||
manager. However, in that case it likely lacks the privileges to create the
|
manager. However, in that case it likely lacks the privileges to create the
|
||||||
character and block devices nodes (there all fallbacks for this case).
|
character and block devices nodes (there are fallbacks for this case).
|
||||||
|
|
||||||
3. The `/run/host/notify` path is a good choice to place the `sd_notify()`
|
3. The `/run/host/notify` path is a good choice to place the `sd_notify()`
|
||||||
socket in, that may be used for the container's PID 1 to report to the
|
socket in, that may be used for the container's PID 1 to report to the
|
||||||
@ -252,6 +263,9 @@ care should be taken to avoid naming conflicts. `systemd` (and in particular
|
|||||||
as the `$container_uuid` environment variable (see above). This file should
|
as the `$container_uuid` environment variable (see above). This file should
|
||||||
be newline terminated.
|
be newline terminated.
|
||||||
|
|
||||||
|
7. The `/run/host/credentials/` directory is a good place to pass credentials
|
||||||
|
into the container, using the `$CREDENTIALS_DIRECTORY` protocol, see above.
|
||||||
|
|
||||||
## What You Shouldn't Do
|
## What You Shouldn't Do
|
||||||
|
|
||||||
1. Do not drop `CAP_MKNOD` from the container. `PrivateDevices=` is a commonly
|
1. Do not drop `CAP_MKNOD` from the container. `PrivateDevices=` is a commonly
|
||||||
|
|||||||
@ -1402,7 +1402,51 @@
|
|||||||
|
|
||||||
<listitem><para>Equivalent to <option>--console=pipe</option>.</para></listitem>
|
<listitem><para>Equivalent to <option>--console=pipe</option>.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
</refsect2><refsect2>
|
||||||
|
<title>Credentials</title>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--load-credential=</option><replaceable>ID</replaceable>:<replaceable>PATH</replaceable></term>
|
||||||
|
<term><option>--set-credential=</option><replaceable>ID</replaceable>:<replaceable>VALUE</replaceable></term>
|
||||||
|
|
||||||
|
<para>Pass a credential to the container. These two options correspond to the
|
||||||
|
<varname>LoadCredential=</varname> and <varname>SetCredential=</varname> settings in unit files. See
|
||||||
|
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry> for
|
||||||
|
details about these concepts, as well as the syntax of the option's arguments.</para>
|
||||||
|
|
||||||
|
<para>Note:</para>
|
||||||
|
|
||||||
|
<orderedlist>
|
||||||
|
<listitem><para>When <command>systemd-nspawn</command> runs as systemd system service it can make
|
||||||
|
use and propagate credentials it received via
|
||||||
|
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname> to the container
|
||||||
|
payload.</para></listitem>
|
||||||
|
|
||||||
|
<listitem><para>A systemd service manager running as PID 1 in the container can make use of
|
||||||
|
credentials passed in this way, and propagate them further to services it itself
|
||||||
|
runs.</para></listitem>
|
||||||
|
</orderedlist>
|
||||||
|
|
||||||
|
<para>Thus it is possible to easily propagate credentials from a host service manager to a
|
||||||
|
<command>systemd-nspawn</command> service and from there into its payload and services running within
|
||||||
|
it.</para>
|
||||||
|
|
||||||
|
<para>In order to embed binary data into
|
||||||
|
the credential data for <option>--set-credential=</option> use C-style escaping
|
||||||
|
(i.e. <literal>\n</literal> to embed a newline, or <literal>\x00</literal> to embed a NUL byte. Note
|
||||||
|
that the invoking shell might already apply unescaping once, hence this might require double
|
||||||
|
escaping!).</para>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
</variablelist>
|
||||||
|
|
||||||
|
</refsect2><refsect2>
|
||||||
|
<title>Other</title>
|
||||||
|
|
||||||
|
<variablelist>
|
||||||
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
<xi:include href="standard-options.xml" xpointer="no-pager" />
|
||||||
<xi:include href="standard-options.xml" xpointer="help" />
|
<xi:include href="standard-options.xml" xpointer="help" />
|
||||||
<xi:include href="standard-options.xml" xpointer="version" />
|
<xi:include href="standard-options.xml" xpointer="version" />
|
||||||
|
|||||||
@ -2154,11 +2154,13 @@ SystemCallErrorNumber=EPERM</programlisting>
|
|||||||
project='man-pages'><refentrytitle>environ</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details
|
project='man-pages'><refentrytitle>environ</refentrytitle><manvolnum>7</manvolnum></citerefentry> for details
|
||||||
about environment variables.</para>
|
about environment variables.</para>
|
||||||
|
|
||||||
<para>Note that environment variables are not suitable for passing secrets (such as passwords, key material, …)
|
<para>Note that environment variables are not suitable for passing secrets (such as passwords, key
|
||||||
to service processes. Environment variables set for a unit are exposed to unprivileged clients via D-Bus IPC,
|
material, …) to service processes. Environment variables set for a unit are exposed to unprivileged
|
||||||
and generally not understood as being data that requires protection. Moreover, environment variables are
|
clients via D-Bus IPC, and generally not understood as being data that requires protection. Moreover,
|
||||||
propagated down the process tree, including across security boundaries (such as setuid/setgid executables), and
|
environment variables are propagated down the process tree, including across security boundaries
|
||||||
hence might leak to processes that should not have access to the secret data.</para></listitem>
|
(such as setuid/setgid executables), and hence might leak to processes that should not have access to
|
||||||
|
the secret data. Use <varname>LoadCredential=</varname> (see below) to pass data to unit processes
|
||||||
|
securely.</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
@ -2624,6 +2626,73 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
|||||||
</variablelist>
|
</variablelist>
|
||||||
</refsect1>
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1>
|
||||||
|
<title>Credentials</title>
|
||||||
|
|
||||||
|
<variablelist class='unit-directives'>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>LoadCredential=</varname><replaceable>ID</replaceable>:<replaceable>PATH</replaceable></term>
|
||||||
|
|
||||||
|
<listitem><para>Pass a credential to the unit. Credentials are limited-size binary or textual objects
|
||||||
|
that may be passed to unit processes. They are primarily used for passing cryptographic keys (both
|
||||||
|
public and private) or certificates, user account information or identity information from host to
|
||||||
|
services. The data is accessible from the unit's processes via the file system, at a read-only
|
||||||
|
location that (if possible and permitted) is backed by non-swappable memory. The data is only
|
||||||
|
accessible to the user associated with the unit, via the
|
||||||
|
<varname>User=</varname>/<varname>DynamicUser=</varname> settings (as well as the superuser). When
|
||||||
|
available, the location of credentials is exported as the <varname>$CREDENTIALS_DIRECTORY</varname>
|
||||||
|
environment variable to the unit's processes.</para>
|
||||||
|
|
||||||
|
<para>The <varname>LoadCredential=</varname> setting takes a textual ID to use as name for a
|
||||||
|
credential plus a file system path. The ID must be a short ASCII string suitable as filename in the
|
||||||
|
filesystem, and may be chosen freely by the user. If the specified path is absolute it is opened as
|
||||||
|
regular file and the credential data is read from it. If the absolute path refers to an
|
||||||
|
<constant>AF_UNIX</constant> stream socket in the file system a connection is made to it 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 NUL bytes. This option may be used multiple times, each time
|
||||||
|
defining an additional credential to pass to the unit.</para>
|
||||||
|
|
||||||
|
<para>The credential files/IPC sockets must be accessible to the service manager, but don't have to
|
||||||
|
be directly accessible to the unit's processes: the credential data is read and copied into separate,
|
||||||
|
read-only copies for the unit that are accessible to appropriately privileged processes. This is
|
||||||
|
particularly useful in combination with <varname>DynamicUser=</varname> as this way privileged data
|
||||||
|
can be made available to processes running under a dynamic UID (i.e. not a previously known one)
|
||||||
|
without having to open up access to all users.</para>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<para>Currently, an accumulated credential size limit of 1M bytes per unit is
|
||||||
|
enforced.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>SetCredential=</varname><replaceable>ID</replaceable>:<replaceable>VALUE</replaceable></term>
|
||||||
|
|
||||||
|
<listitem><para>The <varname>SetCredential=</varname> setting is similar to
|
||||||
|
<varname>LoadCredential=</varname> but accepts a literal value to use as data for the credential,
|
||||||
|
instead of a file system path to read the data from. Do not use this option for data that is supposed
|
||||||
|
to be secret, as it is accessible to unprivileged processes via IPC. It's only safe to use this for
|
||||||
|
user IDs, public key material and similar non-sensitive data. For everything else use
|
||||||
|
<varname>LoadCredential=</varname>. In order to embed binary data into the credential data use
|
||||||
|
C-style escaping (i.e. <literal>\n</literal> to embed a newline, or <literal>\x00</literal> to embed
|
||||||
|
a NUL byte).</para>
|
||||||
|
|
||||||
|
<para>If a credential of the same ID is listed in both <varname>LoadCredential=</varname> and
|
||||||
|
<varname>SetCredential=</varname>, the latter will act as default if the former cannot be
|
||||||
|
retrieved. In this case not being able to retrieve the credential from the path specified in
|
||||||
|
<varname>LoadCredential=</varname> is not considered fatal.</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
<refsect1>
|
<refsect1>
|
||||||
<title>System V Compatibility</title>
|
<title>System V Compatibility</title>
|
||||||
<variablelist class='unit-directives'>
|
<variablelist class='unit-directives'>
|
||||||
@ -2779,6 +2848,16 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><varname>$CREDENTIALS_DIRECTORY</varname></term>
|
||||||
|
|
||||||
|
<listitem><para>An absolute path to the per-unit directory with credentials configured via
|
||||||
|
<varname>LoadCredential=</varname>/<varname>SetCredential=</varname>. The directory is marked
|
||||||
|
read-only and is placed in unswappable memory (if supported and permitted), and is only accessible to
|
||||||
|
the UID associated with the unit via <varname>User=</varname> or <varname>DynamicUser=</varname> (and
|
||||||
|
the superuser).</para></listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><varname>$MAINPID</varname></term>
|
<term><varname>$MAINPID</varname></term>
|
||||||
|
|
||||||
@ -3380,7 +3459,11 @@ StandardInputData=SWNrIHNpdHplIGRhIHVuJyBlc3NlIEtsb3BzLAp1ZmYgZWVtYWwga2xvcHAncy
|
|||||||
<entry><constant>EXIT_NUMA_POLICY</constant></entry>
|
<entry><constant>EXIT_NUMA_POLICY</constant></entry>
|
||||||
<entry>Failed to set up unit's NUMA memory policy. See <varname>NUMAPolicy=</varname> and <varname>NUMAMask=</varname> above.</entry>
|
<entry>Failed to set up unit's NUMA memory policy. See <varname>NUMAPolicy=</varname> and <varname>NUMAMask=</varname> above.</entry>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<entry>243</entry>
|
||||||
|
<entry><constant>EXIT_CREDENTIALS</constant></entry>
|
||||||
|
<entry>Failed to set up unit's credentials. See <varname>LoadCredential=</varname> and <varname>SetCredential=</varname> above.</entry>
|
||||||
|
</row>
|
||||||
</tbody>
|
</tbody>
|
||||||
</tgroup>
|
</tgroup>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
#include "alloc-util.h"
|
#include "alloc-util.h"
|
||||||
#include "extract-word.h"
|
#include "extract-word.h"
|
||||||
|
#include "fd-util.h"
|
||||||
#include "fs-util.h"
|
#include "fs-util.h"
|
||||||
#include "glob-util.h"
|
#include "glob-util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@ -637,6 +638,9 @@ int find_binary(const char *name, char **ret) {
|
|||||||
if (!j)
|
if (!j)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (is_dir(j, true))
|
||||||
|
continue;
|
||||||
|
|
||||||
if (access(j, X_OK) >= 0) {
|
if (access(j, X_OK) >= 0) {
|
||||||
/* Found it! */
|
/* Found it! */
|
||||||
|
|
||||||
@ -1127,3 +1131,9 @@ bool prefixed_path_strv_contains(char **l, const char *path) {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool credential_name_valid(const char *s) {
|
||||||
|
/* We want that credential names are both valid in filenames (since that's our primary way to pass
|
||||||
|
* them around) and as fdnames (which is how we might want to pass them around eventually) */
|
||||||
|
return filename_is_valid(s) && fdname_is_valid(s);
|
||||||
|
}
|
||||||
|
|||||||
@ -173,3 +173,5 @@ static inline const char *empty_to_root(const char *path) {
|
|||||||
|
|
||||||
bool path_strv_contains(char **l, const char *path);
|
bool path_strv_contains(char **l, const char *path);
|
||||||
bool prefixed_path_strv_contains(char **l, const char *path);
|
bool prefixed_path_strv_contains(char **l, const char *path);
|
||||||
|
|
||||||
|
bool credential_name_valid(const char *s);
|
||||||
|
|||||||
@ -23,6 +23,46 @@ static bool is_physical_fs(const struct statfs *sfs) {
|
|||||||
return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
|
return !is_temporary_fs(sfs) && !is_cgroup_fs(sfs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int unlinkat_harder(
|
||||||
|
int dfd,
|
||||||
|
const char *filename,
|
||||||
|
int unlink_flags,
|
||||||
|
RemoveFlags remove_flags) {
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
/* Like unlinkat(), but tries harder: if we get EACCESS we'll try to set the r/w/x bits on the
|
||||||
|
* directory. This is useful if we run unprivileged and have some files where the w bit is
|
||||||
|
* missing. */
|
||||||
|
|
||||||
|
if (unlinkat(dfd, filename, unlink_flags) >= 0)
|
||||||
|
return 0;
|
||||||
|
if (errno != EACCES || !FLAGS_SET(remove_flags, REMOVE_CHMOD))
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (fstat(dfd, &st) < 0)
|
||||||
|
return -errno;
|
||||||
|
if (!S_ISDIR(st.st_mode))
|
||||||
|
return -ENOTDIR;
|
||||||
|
if ((st.st_mode & 0700) == 0700) /* Already set? */
|
||||||
|
return -EACCES; /* original error */
|
||||||
|
if (st.st_uid != geteuid()) /* this only works if the UID matches ours */
|
||||||
|
return -EACCES;
|
||||||
|
|
||||||
|
if (fchmod(dfd, (st.st_mode | 0700) & 07777) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (unlinkat(dfd, filename, unlink_flags) < 0) {
|
||||||
|
r = -errno;
|
||||||
|
/* Try to restore the original access mode if this didn't work */
|
||||||
|
(void) fchmod(dfd, st.st_mode & 07777);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
|
int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
|
||||||
_cleanup_closedir_ DIR *d = NULL;
|
_cleanup_closedir_ DIR *d = NULL;
|
||||||
struct dirent *de;
|
struct dirent *de;
|
||||||
@ -132,17 +172,15 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) {
|
|||||||
if (r < 0 && ret == 0)
|
if (r < 0 && ret == 0)
|
||||||
ret = r;
|
ret = r;
|
||||||
|
|
||||||
if (unlinkat(fd, de->d_name, AT_REMOVEDIR) < 0) {
|
r = unlinkat_harder(fd, de->d_name, AT_REMOVEDIR, flags);
|
||||||
if (ret == 0 && errno != ENOENT)
|
if (r < 0 && r != -ENOENT && ret == 0)
|
||||||
ret = -errno;
|
ret = r;
|
||||||
}
|
|
||||||
|
|
||||||
} else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
|
} else if (!(flags & REMOVE_ONLY_DIRECTORIES)) {
|
||||||
|
|
||||||
if (unlinkat(fd, de->d_name, 0) < 0) {
|
r = unlinkat_harder(fd, de->d_name, 0, flags);
|
||||||
if (ret == 0 && errno != ENOENT)
|
if (r < 0 && r != -ENOENT && ret == 0)
|
||||||
ret = -errno;
|
ret = r;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret;
|
return ret;
|
||||||
|
|||||||
@ -11,6 +11,7 @@ typedef enum RemoveFlags {
|
|||||||
REMOVE_PHYSICAL = 1 << 2, /* If not set, only removes files on tmpfs, never physical file systems */
|
REMOVE_PHYSICAL = 1 << 2, /* If not set, only removes files on tmpfs, never physical file systems */
|
||||||
REMOVE_SUBVOLUME = 1 << 3, /* Drop btrfs subvolumes in the tree too */
|
REMOVE_SUBVOLUME = 1 << 3, /* Drop btrfs subvolumes in the tree too */
|
||||||
REMOVE_MISSING_OK = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */
|
REMOVE_MISSING_OK = 1 << 4, /* If the top-level directory is missing, ignore the ENOENT for it */
|
||||||
|
REMOVE_CHMOD = 1 << 5, /* chmod() for write access if we cannot delete something */
|
||||||
} RemoveFlags;
|
} RemoveFlags;
|
||||||
|
|
||||||
int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);
|
int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev);
|
||||||
|
|||||||
@ -748,6 +748,82 @@ static int property_get_log_extra_fields(
|
|||||||
return sd_bus_message_close_container(reply);
|
return sd_bus_message_close_container(reply);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int property_get_set_credential(
|
||||||
|
sd_bus *bus,
|
||||||
|
const char *path,
|
||||||
|
const char *interface,
|
||||||
|
const char *property,
|
||||||
|
sd_bus_message *reply,
|
||||||
|
void *userdata,
|
||||||
|
sd_bus_error *error) {
|
||||||
|
|
||||||
|
ExecContext *c = userdata;
|
||||||
|
ExecSetCredential *sc;
|
||||||
|
Iterator iterator;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(bus);
|
||||||
|
assert(c);
|
||||||
|
assert(property);
|
||||||
|
assert(reply);
|
||||||
|
|
||||||
|
r = sd_bus_message_open_container(reply, 'a', "(say)");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
HASHMAP_FOREACH(sc, c->set_credentials, iterator) {
|
||||||
|
|
||||||
|
r = sd_bus_message_open_container(reply, 'r', "say");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = sd_bus_message_append(reply, "s", sc->id);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = sd_bus_message_append_array(reply, 'y', sc->data, sc->size);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = sd_bus_message_close_container(reply);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sd_bus_message_close_container(reply);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int property_get_load_credential(
|
||||||
|
sd_bus *bus,
|
||||||
|
const char *path,
|
||||||
|
const char *interface,
|
||||||
|
const char *property,
|
||||||
|
sd_bus_message *reply,
|
||||||
|
void *userdata,
|
||||||
|
sd_bus_error *error) {
|
||||||
|
|
||||||
|
ExecContext *c = userdata;
|
||||||
|
char **i, **j;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(bus);
|
||||||
|
assert(c);
|
||||||
|
assert(property);
|
||||||
|
assert(reply);
|
||||||
|
|
||||||
|
r = sd_bus_message_open_container(reply, 'a', "(ss)");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
STRV_FOREACH_PAIR(i, j, c->load_credentials) {
|
||||||
|
r = sd_bus_message_append(reply, "(ss)", *i, *j);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sd_bus_message_close_container(reply);
|
||||||
|
}
|
||||||
|
|
||||||
static int property_get_root_hash(
|
static int property_get_root_hash(
|
||||||
sd_bus *bus,
|
sd_bus *bus,
|
||||||
const char *path,
|
const char *path,
|
||||||
@ -965,6 +1041,8 @@ const sd_bus_vtable bus_exec_vtable[] = {
|
|||||||
SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
|
SD_BUS_PROPERTY("Group", "s", NULL, offsetof(ExecContext, group), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||||
SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
|
SD_BUS_PROPERTY("DynamicUser", "b", bus_property_get_bool, offsetof(ExecContext, dynamic_user), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||||
SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(ExecContext, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
|
SD_BUS_PROPERTY("RemoveIPC", "b", bus_property_get_bool, offsetof(ExecContext, remove_ipc), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||||
|
SD_BUS_PROPERTY("SetCredential", "a(say)", property_get_set_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||||
|
SD_BUS_PROPERTY("LoadCredential", "a(ss)", property_get_load_credential, 0, SD_BUS_VTABLE_PROPERTY_CONST),
|
||||||
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
|
SD_BUS_PROPERTY("SupplementaryGroups", "as", NULL, offsetof(ExecContext, supplementary_groups), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||||
SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
|
SD_BUS_PROPERTY("PAMName", "s", NULL, offsetof(ExecContext, pam_name), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||||
SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
|
SD_BUS_PROPERTY("ReadWritePaths", "as", NULL, offsetof(ExecContext, read_write_paths), SD_BUS_VTABLE_PROPERTY_CONST),
|
||||||
@ -1794,6 +1872,146 @@ int bus_exec_context_set_transient_property(
|
|||||||
|
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
|
} else if (streq(name, "SetCredential")) {
|
||||||
|
bool isempty = true;
|
||||||
|
|
||||||
|
r = sd_bus_message_enter_container(message, 'a', "(say)");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
const char *id;
|
||||||
|
const void *p;
|
||||||
|
size_t sz;
|
||||||
|
|
||||||
|
r = sd_bus_message_enter_container(message, 'r', "say");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
r = sd_bus_message_read(message, "s", &id);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = sd_bus_message_read_array(message, 'y', &p, &sz);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = sd_bus_message_exit_container(message);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (!credential_name_valid(id))
|
||||||
|
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential ID is invalid: %s", id);
|
||||||
|
|
||||||
|
isempty = false;
|
||||||
|
|
||||||
|
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
|
||||||
|
_cleanup_free_ char *a = NULL, *b = NULL;
|
||||||
|
_cleanup_free_ void *copy = NULL;
|
||||||
|
ExecSetCredential *old;
|
||||||
|
|
||||||
|
copy = memdup(p, sz);
|
||||||
|
if (!copy)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
old = hashmap_get(c->set_credentials, id);
|
||||||
|
if (old) {
|
||||||
|
free_and_replace(old->data, copy);
|
||||||
|
old->size = sz;
|
||||||
|
} else {
|
||||||
|
_cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
|
||||||
|
|
||||||
|
sc = new0(ExecSetCredential, 1);
|
||||||
|
if (!sc)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
sc->id = strdup(id);
|
||||||
|
if (!sc->id)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
sc->data = TAKE_PTR(copy);
|
||||||
|
sc->size = sz;
|
||||||
|
|
||||||
|
r = hashmap_ensure_allocated(&c->set_credentials, &exec_set_credential_hash_ops);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = hashmap_put(c->set_credentials, sc->id, sc);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
TAKE_PTR(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
a = specifier_escape(id);
|
||||||
|
if (!a)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
b = cescape_length(p, sz);
|
||||||
|
if (!b)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
(void) unit_write_settingf(u, flags, name, "%s=%s:%s", name, a, b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = sd_bus_message_exit_container(message);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) {
|
||||||
|
c->set_credentials = hashmap_free(c->set_credentials);
|
||||||
|
(void) unit_write_settingf(u, flags, name, "%s=", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
} else if (streq(name, "LoadCredential")) {
|
||||||
|
bool isempty = true;
|
||||||
|
|
||||||
|
r = sd_bus_message_enter_container(message, 'a', "(ss)");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
const char *id, *source;
|
||||||
|
|
||||||
|
r = sd_bus_message_read(message, "(ss)", &id, &source);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!credential_name_valid(id))
|
||||||
|
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential ID is invalid: %s", id);
|
||||||
|
|
||||||
|
if (!(path_is_absolute(source) ? path_is_normalized(source) : credential_name_valid(source)))
|
||||||
|
return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Credential source is invalid: %s", source);
|
||||||
|
|
||||||
|
isempty = false;
|
||||||
|
|
||||||
|
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
|
||||||
|
r = strv_extend_strv(&c->load_credentials, STRV_MAKE(id, source), /* filter_duplicates = */ false);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
(void) unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "%s=%s:%s", name, id, source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
r = sd_bus_message_exit_container(message);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (!UNIT_WRITE_FLAGS_NOOP(flags) && isempty) {
|
||||||
|
c->load_credentials = strv_free(c->load_credentials);
|
||||||
|
(void) unit_write_settingf(u, flags, name, "%s=", name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
|
||||||
} else if (streq(name, "SyslogLevel")) {
|
} else if (streq(name, "SyslogLevel")) {
|
||||||
int32_t level;
|
int32_t level;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
#include <sys/eventfd.h>
|
#include <sys/eventfd.h>
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/mman.h>
|
#include <sys/mman.h>
|
||||||
|
#include <sys/mount.h>
|
||||||
#include <sys/personality.h>
|
#include <sys/personality.h>
|
||||||
#include <sys/prctl.h>
|
#include <sys/prctl.h>
|
||||||
#include <sys/shm.h>
|
#include <sys/shm.h>
|
||||||
@ -32,6 +33,7 @@
|
|||||||
|
|
||||||
#include "sd-messages.h"
|
#include "sd-messages.h"
|
||||||
|
|
||||||
|
#include "acl-util.h"
|
||||||
#include "af-list.h"
|
#include "af-list.h"
|
||||||
#include "alloc-util.h"
|
#include "alloc-util.h"
|
||||||
#if HAVE_APPARMOR
|
#if HAVE_APPARMOR
|
||||||
@ -41,8 +43,8 @@
|
|||||||
#include "barrier.h"
|
#include "barrier.h"
|
||||||
#include "cap-list.h"
|
#include "cap-list.h"
|
||||||
#include "capability-util.h"
|
#include "capability-util.h"
|
||||||
#include "chown-recursive.h"
|
|
||||||
#include "cgroup-setup.h"
|
#include "cgroup-setup.h"
|
||||||
|
#include "chown-recursive.h"
|
||||||
#include "cpu-set-util.h"
|
#include "cpu-set-util.h"
|
||||||
#include "def.h"
|
#include "def.h"
|
||||||
#include "env-file.h"
|
#include "env-file.h"
|
||||||
@ -51,6 +53,7 @@
|
|||||||
#include "execute.h"
|
#include "execute.h"
|
||||||
#include "exit-status.h"
|
#include "exit-status.h"
|
||||||
#include "fd-util.h"
|
#include "fd-util.h"
|
||||||
|
#include "fileio.h"
|
||||||
#include "format-util.h"
|
#include "format-util.h"
|
||||||
#include "fs-util.h"
|
#include "fs-util.h"
|
||||||
#include "glob-util.h"
|
#include "glob-util.h"
|
||||||
@ -64,6 +67,7 @@
|
|||||||
#include "memory-util.h"
|
#include "memory-util.h"
|
||||||
#include "missing_fs.h"
|
#include "missing_fs.h"
|
||||||
#include "mkdir.h"
|
#include "mkdir.h"
|
||||||
|
#include "mountpoint-util.h"
|
||||||
#include "namespace.h"
|
#include "namespace.h"
|
||||||
#include "parse-util.h"
|
#include "parse-util.h"
|
||||||
#include "path-util.h"
|
#include "path-util.h"
|
||||||
@ -85,6 +89,7 @@
|
|||||||
#include "strv.h"
|
#include "strv.h"
|
||||||
#include "syslog-util.h"
|
#include "syslog-util.h"
|
||||||
#include "terminal-util.h"
|
#include "terminal-util.h"
|
||||||
|
#include "tmpfile-util.h"
|
||||||
#include "umask-util.h"
|
#include "umask-util.h"
|
||||||
#include "unit.h"
|
#include "unit.h"
|
||||||
#include "user-util.h"
|
#include "user-util.h"
|
||||||
@ -1417,6 +1422,14 @@ static bool context_has_no_new_privileges(const ExecContext *c) {
|
|||||||
c->protect_hostname;
|
c->protect_hostname;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool exec_context_has_credentials(const ExecContext *context) {
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
return !hashmap_isempty(context->set_credentials) ||
|
||||||
|
context->load_credentials;
|
||||||
|
}
|
||||||
|
|
||||||
#if HAVE_SECCOMP
|
#if HAVE_SECCOMP
|
||||||
|
|
||||||
static bool skip_seccomp_unavailable(const Unit* u, const char* msg) {
|
static bool skip_seccomp_unavailable(const Unit* u, const char* msg) {
|
||||||
@ -1725,7 +1738,7 @@ static int build_environment(
|
|||||||
assert(p);
|
assert(p);
|
||||||
assert(ret);
|
assert(ret);
|
||||||
|
|
||||||
#define N_ENV_VARS 15
|
#define N_ENV_VARS 16
|
||||||
our_env = new0(char*, N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
|
our_env = new0(char*, N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
|
||||||
if (!our_env)
|
if (!our_env)
|
||||||
return -ENOMEM;
|
return -ENOMEM;
|
||||||
@ -1873,6 +1886,14 @@ static int build_environment(
|
|||||||
our_env[n_env++] = x;
|
our_env[n_env++] = x;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (exec_context_has_credentials(c) && p->prefix[EXEC_DIRECTORY_RUNTIME]) {
|
||||||
|
x = strjoin("CREDENTIALS_DIRECTORY=", p->prefix[EXEC_DIRECTORY_RUNTIME], "/credentials/", u->id);
|
||||||
|
if (!x)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
our_env[n_env++] = x;
|
||||||
|
}
|
||||||
|
|
||||||
our_env[n_env++] = NULL;
|
our_env[n_env++] = NULL;
|
||||||
assert(n_env <= N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
|
assert(n_env <= N_ENV_VARS + _EXEC_DIRECTORY_TYPE_MAX);
|
||||||
#undef N_ENV_VARS
|
#undef N_ENV_VARS
|
||||||
@ -2378,6 +2399,437 @@ fail:
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int write_credential(
|
||||||
|
int dfd,
|
||||||
|
const char *id,
|
||||||
|
const void *data,
|
||||||
|
size_t size,
|
||||||
|
uid_t uid,
|
||||||
|
bool ownership_ok) {
|
||||||
|
|
||||||
|
_cleanup_(unlink_and_freep) char *tmp = NULL;
|
||||||
|
_cleanup_close_ int fd = -1;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
r = tempfn_random_child("", "cred", &tmp);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
fd = openat(dfd, tmp, O_CREAT|O_RDWR|O_CLOEXEC|O_EXCL|O_NOFOLLOW|O_NOCTTY, 0600);
|
||||||
|
if (fd < 0) {
|
||||||
|
tmp = mfree(tmp);
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = loop_write(fd, data, size, /* do_pool = */ false);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (fchmod(fd, 0400) < 0) /* Take away "w" bit */
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (uid_is_valid(uid) && uid != getuid()) {
|
||||||
|
#if HAVE_ACL
|
||||||
|
r = fd_add_uid_acl_permission(fd, uid, /* read = */ true, /* write = */ false, /* execute = */ false);
|
||||||
|
#else
|
||||||
|
r = -EOPNOTSUPP;
|
||||||
|
#endif
|
||||||
|
if (r < 0) {
|
||||||
|
if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (!ownership_ok) /* Ideally we use ACLs, since we can neatly express what we want
|
||||||
|
* to express: that the user gets read access and nothing
|
||||||
|
* else. But if the backing fs can't support that (e.g. ramfs)
|
||||||
|
* then we can use file ownership instead. But that's only safe if
|
||||||
|
* we can then re-mount the whole thing read-only, so that the
|
||||||
|
* user can no longer chmod() the file to gain write access. */
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (fchown(fd, uid, (gid_t) -1) < 0)
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (renameat(dfd, tmp, dfd, id) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
tmp = mfree(tmp);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CREDENTIALS_BYTES_MAX (1024LU * 1024LU) /* Refuse to pass more than 1M, after all this is unswappable memory */
|
||||||
|
|
||||||
|
static int acquire_credentials(
|
||||||
|
const ExecContext *context,
|
||||||
|
const ExecParameters *params,
|
||||||
|
const char *p,
|
||||||
|
uid_t uid,
|
||||||
|
bool ownership_ok) {
|
||||||
|
|
||||||
|
uint64_t left = CREDENTIALS_BYTES_MAX;
|
||||||
|
_cleanup_close_ int dfd = -1;
|
||||||
|
ExecSetCredential *sc;
|
||||||
|
char **id, **fn;
|
||||||
|
Iterator iterator;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
assert(p);
|
||||||
|
|
||||||
|
dfd = open(p, O_DIRECTORY|O_CLOEXEC);
|
||||||
|
if (dfd < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
/* First we use the literally specified credentials. Note that they might be overriden again below,
|
||||||
|
* and thus act as a "default" if the same credential is specified multiple times */
|
||||||
|
HASHMAP_FOREACH(sc, context->set_credentials, iterator) {
|
||||||
|
size_t add;
|
||||||
|
|
||||||
|
add = strlen(sc->id) + sc->size;
|
||||||
|
if (add > left)
|
||||||
|
return -E2BIG;
|
||||||
|
|
||||||
|
r = write_credential(dfd, sc->id, sc->data, sc->size, uid, ownership_ok);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
left -= add;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Then, load credential off disk (or acquire via AF_UNIX socket) */
|
||||||
|
STRV_FOREACH_PAIR(id, fn, context->load_credentials) {
|
||||||
|
ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
|
||||||
|
_cleanup_(erase_and_freep) char *data = NULL;
|
||||||
|
_cleanup_free_ char *j = NULL;
|
||||||
|
const char *source;
|
||||||
|
size_t size, add;
|
||||||
|
|
||||||
|
if (path_is_absolute(*fn)) {
|
||||||
|
/* If this is an absolute path, read the data directly from it, and support AF_UNIX sockets */
|
||||||
|
source = *fn;
|
||||||
|
flags |= READ_FULL_FILE_CONNECT_SOCKET;
|
||||||
|
} 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, *fn);
|
||||||
|
if (!j)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
source = j;
|
||||||
|
} else
|
||||||
|
source = NULL;
|
||||||
|
|
||||||
|
if (source)
|
||||||
|
r = read_full_file_full(AT_FDCWD, source, flags, &data, &size);
|
||||||
|
else
|
||||||
|
r = -ENOENT;
|
||||||
|
if (r == -ENOENT &&
|
||||||
|
faccessat(dfd, *id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) /* If the source file doesn't exist, but we already acquired the key otherwise, then don't fail */
|
||||||
|
continue;
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
add = strlen(*id) + size;
|
||||||
|
if (add > left)
|
||||||
|
return -E2BIG;
|
||||||
|
|
||||||
|
r = write_credential(dfd, *id, data, size, uid, ownership_ok);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
left -= add;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fchmod(dfd, 0500) < 0) /* Now take away the "w" bit */
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
/* After we created all keys with the right perms, also make sure the credential store as a whole is
|
||||||
|
* accessible */
|
||||||
|
|
||||||
|
if (uid_is_valid(uid) && uid != getuid()) {
|
||||||
|
#if HAVE_ACL
|
||||||
|
r = fd_add_uid_acl_permission(dfd, uid, /* read = */ true, /* write = */ false, /* execute = */ true);
|
||||||
|
#else
|
||||||
|
r = -EOPNOTSUPP;
|
||||||
|
#endif
|
||||||
|
if (r < 0) {
|
||||||
|
if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (!ownership_ok)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (fchown(dfd, uid, (gid_t) -1) < 0)
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int setup_credentials_internal(
|
||||||
|
const ExecContext *context,
|
||||||
|
const ExecParameters *params,
|
||||||
|
const char *final, /* This is where the credential store shall eventually end up at */
|
||||||
|
const char *workspace, /* This is where we can prepare it before moving it to the final place */
|
||||||
|
bool reuse_workspace, /* Whether to reuse any existing workspace mount if it already is a mount */
|
||||||
|
bool must_mount, /* Whether to require that we mount something, it's not OK to use the plain directory fall back */
|
||||||
|
uid_t uid) {
|
||||||
|
|
||||||
|
int r, workspace_mounted; /* negative if we don't know yet whether we have/can mount something; true
|
||||||
|
* if we mounted something; false if we definitely can't mount anything */
|
||||||
|
bool final_mounted;
|
||||||
|
const char *where;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
assert(final);
|
||||||
|
assert(workspace);
|
||||||
|
|
||||||
|
if (reuse_workspace) {
|
||||||
|
r = path_is_mount_point(workspace, NULL, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r > 0)
|
||||||
|
workspace_mounted = true; /* If this is already a mount, and we are supposed to reuse it, let's keep this in mind */
|
||||||
|
else
|
||||||
|
workspace_mounted = -1; /* We need to figure out if we can mount something to the workspace */
|
||||||
|
} else
|
||||||
|
workspace_mounted = -1; /* ditto */
|
||||||
|
|
||||||
|
r = path_is_mount_point(final, NULL, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
if (r > 0) {
|
||||||
|
/* If the final place already has something mounted, we use that. If the workspace also has
|
||||||
|
* something mounted we assume it's actually the same mount (but with MS_RDONLY
|
||||||
|
* different). */
|
||||||
|
final_mounted = true;
|
||||||
|
|
||||||
|
if (workspace_mounted < 0) {
|
||||||
|
/* If the final place is mounted, but the workspace we isn't, then let's bind mount
|
||||||
|
* the final version to the workspace, and make it writable, so that we can make
|
||||||
|
* changes */
|
||||||
|
|
||||||
|
if (mount(final, workspace, NULL, MS_BIND|MS_REC, NULL) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (mount(NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
workspace_mounted = true;
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
final_mounted = false;
|
||||||
|
|
||||||
|
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 */
|
||||||
|
if (mount("ramfs", workspace, "ramfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, "mode=0700") >= 0) {
|
||||||
|
workspace_mounted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (try == 1) {
|
||||||
|
_cleanup_free_ char *opts = NULL;
|
||||||
|
|
||||||
|
if (asprintf(&opts, "mode=0700,nr_inodes=1024,size=%lu", CREDENTIALS_BYTES_MAX) < 0)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* Fall back to "tmpfs" otherwise */
|
||||||
|
if (mount("tmpfs", workspace, "tmpfs", MS_NODEV|MS_NOEXEC|MS_NOSUID, opts) >= 0) {
|
||||||
|
workspace_mounted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
} 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. */
|
||||||
|
if (mount(final, workspace, NULL, MS_BIND|MS_REC, NULL) < 0) {
|
||||||
|
if (!ERRNO_IS_PRIVILEGE(errno)) /* Propagate anything that isn't a permission problem */
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
if (must_mount) /* If we it's not OK to use the plain directory
|
||||||
|
* fallback, propagate all errors too */
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
/* 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) */
|
||||||
|
if (mount(NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
workspace_mounted = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(!must_mount || workspace_mounted > 0);
|
||||||
|
where = workspace_mounted ? workspace : final;
|
||||||
|
|
||||||
|
r = acquire_credentials(context, params, where, uid, workspace_mounted);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
if (workspace_mounted) {
|
||||||
|
/* Make workspace read-only now, so that any bind mount we make from it defaults to read-only too */
|
||||||
|
if (mount(NULL, workspace, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NODEV|MS_NOEXEC|MS_NOSUID, NULL) < 0)
|
||||||
|
return -errno;
|
||||||
|
|
||||||
|
/* And mount it to the final place, read-only */
|
||||||
|
if (final_mounted) {
|
||||||
|
if (umount2(workspace, MNT_DETACH|UMOUNT_NOFOLLOW) < 0)
|
||||||
|
return -errno;
|
||||||
|
} else {
|
||||||
|
if (mount(workspace, final, NULL, MS_MOVE, NULL) < 0)
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_cleanup_free_ char *parent = NULL;
|
||||||
|
|
||||||
|
/* If we do not have our own mount put used the plain directory fallback, then we need to
|
||||||
|
* open access to the top-level credential directory and the per-service directory now */
|
||||||
|
|
||||||
|
parent = dirname_malloc(final);
|
||||||
|
if (!parent)
|
||||||
|
return -ENOMEM;
|
||||||
|
if (chmod(parent, 0755) < 0)
|
||||||
|
return -errno;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int setup_credentials(
|
||||||
|
const ExecContext *context,
|
||||||
|
const ExecParameters *params,
|
||||||
|
const char *unit,
|
||||||
|
uid_t uid) {
|
||||||
|
|
||||||
|
_cleanup_free_ char *p = NULL, *q = NULL;
|
||||||
|
const char *i;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(context);
|
||||||
|
assert(params);
|
||||||
|
|
||||||
|
if (!exec_context_has_credentials(context))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!params->prefix[EXEC_DIRECTORY_RUNTIME])
|
||||||
|
return -EINVAL;
|
||||||
|
|
||||||
|
/* This where we'll place stuff when we are done; this main credentials directory is world-readable,
|
||||||
|
* and the subdir we mount over with a read-only file system readable by the service's user */
|
||||||
|
q = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials");
|
||||||
|
if (!q)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
r = mkdir_label(q, 0755); /* top-level dir: world readable/searchable */
|
||||||
|
if (r < 0 && r != -EEXIST)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
p = path_join(q, unit);
|
||||||
|
if (!p)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
r = mkdir_label(p, 0700); /* per-unit dir: private to user */
|
||||||
|
if (r < 0 && r != -EEXIST)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = safe_fork("(sd-mkdcreds)", FORK_DEATHSIG|FORK_WAIT|FORK_NEW_MOUNTNS, NULL);
|
||||||
|
if (r < 0) {
|
||||||
|
_cleanup_free_ char *t = NULL, *u = NULL;
|
||||||
|
|
||||||
|
/* If this is not a privilege or support issue then propagate the error */
|
||||||
|
if (!ERRNO_IS_NOT_SUPPORTED(r) && !ERRNO_IS_PRIVILEGE(r))
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Temporary workspace, that remains inaccessible all the time. We prepare stuff there before moving
|
||||||
|
* it into place, so that users can't access half-initialized credential stores. */
|
||||||
|
t = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "systemd/temporary-credentials");
|
||||||
|
if (!t)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* We can't set up a mount namespace. In that case operate on a fixed, inaccessible per-unit
|
||||||
|
* directory outside of /run/credentials/ first, and then move it over to /run/credentials/
|
||||||
|
* after it is fully set up */
|
||||||
|
u = path_join(t, unit);
|
||||||
|
if (!u)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
FOREACH_STRING(i, t, u) {
|
||||||
|
r = mkdir_label(i, 0700);
|
||||||
|
if (r < 0 && r != -EEXIST)
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = setup_credentials_internal(
|
||||||
|
context,
|
||||||
|
params,
|
||||||
|
p, /* final mount point */
|
||||||
|
u, /* temporary workspace to overmount */
|
||||||
|
true, /* reuse the workspace if it is already a mount */
|
||||||
|
false, /* it's OK to fall back to a plain directory if we can't mount anything */
|
||||||
|
uid);
|
||||||
|
|
||||||
|
(void) rmdir(u); /* remove the workspace again if we can. */
|
||||||
|
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
} else if (r == 0) {
|
||||||
|
|
||||||
|
/* We managed to set up a mount namespace, and are now in a child. That's great. In this case
|
||||||
|
* we can use the same directory for all cases, after turning off propagation. Question
|
||||||
|
* though is: where do we turn off propagation exactly, and where do we place the workspace
|
||||||
|
* directory? We need some place that is guaranteed to be a mount point in the host, and
|
||||||
|
* which is guaranteed to have a subdir we can mount over. /run/ is not suitable for this,
|
||||||
|
* since we ultimately want to move the resulting file system there, i.e. we need propagation
|
||||||
|
* for /run/ eventually. We could use our own /run/systemd/bind mount on itself, but that
|
||||||
|
* would be visible in the host mount table all the time, which we want to avoid. Hence, what
|
||||||
|
* we do here instead we use /dev/ and /dev/shm/ for our purposes. We know for sure that
|
||||||
|
* /dev/ is a mount point and we now for sure that /dev/shm/ exists. Hence we can turn off
|
||||||
|
* propagation on the former, and then overmount the latter.
|
||||||
|
*
|
||||||
|
* Yes it's nasty playing games with /dev/ and /dev/shm/ like this, since it does not exist
|
||||||
|
* for this purpose, but there are few other candidates that work equally well for us, and
|
||||||
|
* given that the we do this in a privately namespaced short-lived single-threaded process
|
||||||
|
* that noone else sees this should be OK to do.*/
|
||||||
|
|
||||||
|
if (mount(NULL, "/dev", NULL, MS_SLAVE|MS_REC, NULL) < 0) /* Turn off propagation from our namespace to host */
|
||||||
|
goto child_fail;
|
||||||
|
|
||||||
|
r = setup_credentials_internal(
|
||||||
|
context,
|
||||||
|
params,
|
||||||
|
p, /* final mount point */
|
||||||
|
"/dev/shm", /* temporary workspace to overmount */
|
||||||
|
false, /* do not reuse /dev/shm if it is already a mount, under no circumstances */
|
||||||
|
true, /* insist that something is mounted, do not allow fallback to plain directory */
|
||||||
|
uid);
|
||||||
|
if (r < 0)
|
||||||
|
goto child_fail;
|
||||||
|
|
||||||
|
_exit(EXIT_SUCCESS);
|
||||||
|
|
||||||
|
child_fail:
|
||||||
|
_exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
#if ENABLE_SMACK
|
#if ENABLE_SMACK
|
||||||
static int setup_smack(
|
static int setup_smack(
|
||||||
const ExecContext *context,
|
const ExecContext *context,
|
||||||
@ -2604,6 +3056,7 @@ static int apply_mount_namespace(
|
|||||||
_cleanup_strv_free_ char **empty_directories = NULL;
|
_cleanup_strv_free_ char **empty_directories = NULL;
|
||||||
const char *tmp_dir = NULL, *var_tmp_dir = NULL;
|
const char *tmp_dir = NULL, *var_tmp_dir = NULL;
|
||||||
const char *root_dir = NULL, *root_image = NULL;
|
const char *root_dir = NULL, *root_image = NULL;
|
||||||
|
_cleanup_free_ char *creds_path = NULL;
|
||||||
NamespaceInfo ns_info;
|
NamespaceInfo ns_info;
|
||||||
bool needs_sandboxing;
|
bool needs_sandboxing;
|
||||||
BindMount *bind_mounts = NULL;
|
BindMount *bind_mounts = NULL;
|
||||||
@ -2672,6 +3125,12 @@ static int apply_mount_namespace(
|
|||||||
if (context->mount_flags == MS_SHARED)
|
if (context->mount_flags == MS_SHARED)
|
||||||
log_unit_debug(u, "shared mount propagation hidden by other fs namespacing unit settings: ignoring");
|
log_unit_debug(u, "shared mount propagation hidden by other fs namespacing unit settings: ignoring");
|
||||||
|
|
||||||
|
if (exec_context_has_credentials(context) && params->prefix[EXEC_DIRECTORY_RUNTIME]) {
|
||||||
|
creds_path = path_join(params->prefix[EXEC_DIRECTORY_RUNTIME], "credentials", u->id);
|
||||||
|
if (!creds_path)
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
r = setup_namespace(root_dir, root_image, context->root_image_options,
|
r = setup_namespace(root_dir, root_image, context->root_image_options,
|
||||||
&ns_info, context->read_write_paths,
|
&ns_info, context->read_write_paths,
|
||||||
needs_sandboxing ? context->read_only_paths : NULL,
|
needs_sandboxing ? context->read_only_paths : NULL,
|
||||||
@ -2685,6 +3144,7 @@ static int apply_mount_namespace(
|
|||||||
context->n_mount_images,
|
context->n_mount_images,
|
||||||
tmp_dir,
|
tmp_dir,
|
||||||
var_tmp_dir,
|
var_tmp_dir,
|
||||||
|
creds_path,
|
||||||
context->log_namespace,
|
context->log_namespace,
|
||||||
context->mount_flags,
|
context->mount_flags,
|
||||||
context->root_hash, context->root_hash_size, context->root_hash_path,
|
context->root_hash, context->root_hash_size, context->root_hash_path,
|
||||||
@ -3489,6 +3949,14 @@ static int exec_child(
|
|||||||
return log_unit_error_errno(unit, r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]);
|
return log_unit_error_errno(unit, r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (FLAGS_SET(params->flags, EXEC_WRITE_CREDENTIALS)) {
|
||||||
|
r = setup_credentials(context, params, unit->id, uid);
|
||||||
|
if (r < 0) {
|
||||||
|
*exit_status = EXIT_CREDENTIALS;
|
||||||
|
return log_unit_error_errno(unit, r, "Failed to set up credentials: %m");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r = build_environment(
|
r = build_environment(
|
||||||
unit,
|
unit,
|
||||||
context,
|
context,
|
||||||
@ -4276,6 +4744,9 @@ void exec_context_done(ExecContext *c) {
|
|||||||
c->network_namespace_path = mfree(c->network_namespace_path);
|
c->network_namespace_path = mfree(c->network_namespace_path);
|
||||||
|
|
||||||
c->log_namespace = mfree(c->log_namespace);
|
c->log_namespace = mfree(c->log_namespace);
|
||||||
|
|
||||||
|
c->load_credentials = strv_free(c->load_credentials);
|
||||||
|
c->set_credentials = hashmap_free(c->set_credentials);
|
||||||
}
|
}
|
||||||
|
|
||||||
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
|
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_prefix) {
|
||||||
@ -4304,6 +4775,26 @@ int exec_context_destroy_runtime_directory(const ExecContext *c, const char *run
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_prefix, const char *unit) {
|
||||||
|
_cleanup_free_ char *p = NULL;
|
||||||
|
|
||||||
|
assert(c);
|
||||||
|
|
||||||
|
if (!runtime_prefix || !unit)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
p = path_join(runtime_prefix, "credentials", unit);
|
||||||
|
if (!p)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
/* This is either a tmpfs/ramfs of its own, or a plain directory. Either way, let's first try to
|
||||||
|
* unmount it, and afterwards remove the mount point */
|
||||||
|
(void) umount2(p, MNT_DETACH|UMOUNT_NOFOLLOW);
|
||||||
|
(void) rm_rf(p, REMOVE_ROOT|REMOVE_CHMOD);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void exec_command_done(ExecCommand *c) {
|
static void exec_command_done(ExecCommand *c) {
|
||||||
assert(c);
|
assert(c);
|
||||||
|
|
||||||
@ -5812,6 +6303,17 @@ void exec_params_clear(ExecParameters *p) {
|
|||||||
p->exec_fd = safe_close(p->exec_fd);
|
p->exec_fd = safe_close(p->exec_fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ExecSetCredential *exec_set_credential_free(ExecSetCredential *sc) {
|
||||||
|
if (!sc)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
free(sc->id);
|
||||||
|
free(sc->data);
|
||||||
|
return mfree(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
DEFINE_HASH_OPS_WITH_VALUE_DESTRUCTOR(exec_set_credential_hash_ops, char, string_hash_func, string_compare_func, ExecSetCredential, exec_set_credential_free);
|
||||||
|
|
||||||
static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
|
static const char* const exec_input_table[_EXEC_INPUT_MAX] = {
|
||||||
[EXEC_INPUT_NULL] = "null",
|
[EXEC_INPUT_NULL] = "null",
|
||||||
[EXEC_INPUT_TTY] = "tty",
|
[EXEC_INPUT_TTY] = "tty",
|
||||||
|
|||||||
@ -145,6 +145,13 @@ typedef enum ExecCleanMask {
|
|||||||
_EXEC_CLEAN_MASK_INVALID = -1,
|
_EXEC_CLEAN_MASK_INVALID = -1,
|
||||||
} ExecCleanMask;
|
} ExecCleanMask;
|
||||||
|
|
||||||
|
/* A credential configured with SetCredential= */
|
||||||
|
typedef struct ExecSetCredential {
|
||||||
|
char *id;
|
||||||
|
void *data;
|
||||||
|
size_t size;
|
||||||
|
} ExecSetCredential;
|
||||||
|
|
||||||
/* Encodes configuration parameters applied to invoked commands. Does not carry runtime data, but only configuration
|
/* Encodes configuration parameters applied to invoked commands. Does not carry runtime data, but only configuration
|
||||||
* changes sourced from unit files and suchlike. ExecContext objects are usually embedded into Unit objects, and do not
|
* changes sourced from unit files and suchlike. ExecContext objects are usually embedded into Unit objects, and do not
|
||||||
* change after being loaded. */
|
* change after being loaded. */
|
||||||
@ -303,6 +310,9 @@ struct ExecContext {
|
|||||||
ExecDirectory directories[_EXEC_DIRECTORY_TYPE_MAX];
|
ExecDirectory directories[_EXEC_DIRECTORY_TYPE_MAX];
|
||||||
ExecPreserveMode runtime_directory_preserve_mode;
|
ExecPreserveMode runtime_directory_preserve_mode;
|
||||||
usec_t timeout_clean_usec;
|
usec_t timeout_clean_usec;
|
||||||
|
|
||||||
|
Hashmap *set_credentials; /* output id → ExecSetCredential */
|
||||||
|
char **load_credentials; /* pairs of output id, path/input id */
|
||||||
};
|
};
|
||||||
|
|
||||||
static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {
|
static inline bool exec_context_restrict_namespaces_set(const ExecContext *c) {
|
||||||
@ -321,11 +331,12 @@ typedef enum ExecFlags {
|
|||||||
EXEC_CGROUP_DELEGATE = 1 << 6,
|
EXEC_CGROUP_DELEGATE = 1 << 6,
|
||||||
EXEC_IS_CONTROL = 1 << 7,
|
EXEC_IS_CONTROL = 1 << 7,
|
||||||
EXEC_CONTROL_CGROUP = 1 << 8, /* Place the process not in the indicated cgroup but in a subcgroup '/.control', but only EXEC_CGROUP_DELEGATE and EXEC_IS_CONTROL is set, too */
|
EXEC_CONTROL_CGROUP = 1 << 8, /* Place the process not in the indicated cgroup but in a subcgroup '/.control', but only EXEC_CGROUP_DELEGATE and EXEC_IS_CONTROL is set, too */
|
||||||
|
EXEC_WRITE_CREDENTIALS = 1 << 9, /* Set up the credential store logic */
|
||||||
|
|
||||||
/* The following are not used by execute.c, but by consumers internally */
|
/* The following are not used by execute.c, but by consumers internally */
|
||||||
EXEC_PASS_FDS = 1 << 9,
|
EXEC_PASS_FDS = 1 << 10,
|
||||||
EXEC_SETENV_RESULT = 1 << 10,
|
EXEC_SETENV_RESULT = 1 << 11,
|
||||||
EXEC_SET_WATCHDOG = 1 << 11,
|
EXEC_SET_WATCHDOG = 1 << 12,
|
||||||
} ExecFlags;
|
} ExecFlags;
|
||||||
|
|
||||||
/* Parameters for a specific invocation of a command. This structure is put together right before a command is
|
/* Parameters for a specific invocation of a command. This structure is put together right before a command is
|
||||||
@ -345,6 +356,7 @@ struct ExecParameters {
|
|||||||
const char *cgroup_path;
|
const char *cgroup_path;
|
||||||
|
|
||||||
char **prefix;
|
char **prefix;
|
||||||
|
const char *received_credentials;
|
||||||
|
|
||||||
const char *confirm_spawn;
|
const char *confirm_spawn;
|
||||||
|
|
||||||
@ -386,6 +398,7 @@ void exec_context_done(ExecContext *c);
|
|||||||
void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix);
|
void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix);
|
||||||
|
|
||||||
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_root);
|
int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_root);
|
||||||
|
int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_root, const char *unit);
|
||||||
|
|
||||||
const char* exec_context_fdname(const ExecContext *c, int fd_index);
|
const char* exec_context_fdname(const ExecContext *c, int fd_index);
|
||||||
|
|
||||||
@ -418,6 +431,11 @@ void exec_params_clear(ExecParameters *p);
|
|||||||
|
|
||||||
bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c);
|
bool exec_context_get_cpu_affinity_from_numa(const ExecContext *c);
|
||||||
|
|
||||||
|
ExecSetCredential *exec_set_credential_free(ExecSetCredential *sc);
|
||||||
|
DEFINE_TRIVIAL_CLEANUP_FUNC(ExecSetCredential*, exec_set_credential_free);
|
||||||
|
|
||||||
|
extern const struct hash_ops exec_set_credential_hash_ops;
|
||||||
|
|
||||||
const char* exec_output_to_string(ExecOutput i) _const_;
|
const char* exec_output_to_string(ExecOutput i) _const_;
|
||||||
ExecOutput exec_output_from_string(const char *s) _pure_;
|
ExecOutput exec_output_from_string(const char *s) _pure_;
|
||||||
|
|
||||||
|
|||||||
@ -147,6 +147,8 @@ $1.LogsDirectoryMode, config_parse_mode, 0,
|
|||||||
$1.LogsDirectory, config_parse_exec_directories, 0, offsetof($1, exec_context.directories[EXEC_DIRECTORY_LOGS].paths)
|
$1.LogsDirectory, config_parse_exec_directories, 0, offsetof($1, exec_context.directories[EXEC_DIRECTORY_LOGS].paths)
|
||||||
$1.ConfigurationDirectoryMode, config_parse_mode, 0, offsetof($1, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].mode)
|
$1.ConfigurationDirectoryMode, config_parse_mode, 0, offsetof($1, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].mode)
|
||||||
$1.ConfigurationDirectory, config_parse_exec_directories, 0, offsetof($1, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].paths)
|
$1.ConfigurationDirectory, config_parse_exec_directories, 0, offsetof($1, exec_context.directories[EXEC_DIRECTORY_CONFIGURATION].paths)
|
||||||
|
$1.SetCredential, config_parse_set_credential, 0, offsetof($1, exec_context)
|
||||||
|
$1.LoadCredential, config_parse_load_credential, 0, offsetof($1, exec_context)
|
||||||
$1.TimeoutCleanSec, config_parse_sec, 0, offsetof($1, exec_context.timeout_clean_usec)
|
$1.TimeoutCleanSec, config_parse_sec, 0, offsetof($1, exec_context.timeout_clean_usec)
|
||||||
$1.ProtectHostname, config_parse_bool, 0, offsetof($1, exec_context.protect_hostname)
|
$1.ProtectHostname, config_parse_bool, 0, offsetof($1, exec_context.protect_hostname)
|
||||||
m4_ifdef(`HAVE_PAM',
|
m4_ifdef(`HAVE_PAM',
|
||||||
|
|||||||
@ -60,6 +60,7 @@
|
|||||||
#include "unit-name.h"
|
#include "unit-name.h"
|
||||||
#include "unit-printf.h"
|
#include "unit-printf.h"
|
||||||
#include "user-util.h"
|
#include "user-util.h"
|
||||||
|
#include "utf8.h"
|
||||||
#include "web-util.h"
|
#include "web-util.h"
|
||||||
|
|
||||||
static int parse_socket_protocol(const char *s) {
|
static int parse_socket_protocol(const char *s) {
|
||||||
@ -4268,6 +4269,155 @@ int config_parse_exec_directories(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int config_parse_set_credential(
|
||||||
|
const char *unit,
|
||||||
|
const char *filename,
|
||||||
|
unsigned line,
|
||||||
|
const char *section,
|
||||||
|
unsigned section_line,
|
||||||
|
const char *lvalue,
|
||||||
|
int ltype,
|
||||||
|
const char *rvalue,
|
||||||
|
void *data,
|
||||||
|
void *userdata) {
|
||||||
|
|
||||||
|
_cleanup_free_ char *word = NULL, *k = NULL, *unescaped = NULL;
|
||||||
|
ExecContext *context = data;
|
||||||
|
ExecSetCredential *old;
|
||||||
|
Unit *u = userdata;
|
||||||
|
const char *p;
|
||||||
|
int r, l;
|
||||||
|
|
||||||
|
assert(filename);
|
||||||
|
assert(lvalue);
|
||||||
|
assert(rvalue);
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
if (isempty(rvalue)) {
|
||||||
|
/* Empty assignment resets the list */
|
||||||
|
context->set_credentials = hashmap_free(context->set_credentials);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = rvalue;
|
||||||
|
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||||
|
if (r == -ENOMEM)
|
||||||
|
return log_oom();
|
||||||
|
if (r <= 0 || !p) {
|
||||||
|
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = unit_full_printf(u, word, &k);
|
||||||
|
if (r < 0) {
|
||||||
|
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", word);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!credential_name_valid(k)) {
|
||||||
|
log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", k);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* We support escape codes here, so that users can insert trailing \n if they like */
|
||||||
|
l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
|
||||||
|
if (l < 0) {
|
||||||
|
log_syntax(unit, LOG_WARNING, filename, line, l, "Can't unescape \"%s\", ignoring: %m", p);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
old = hashmap_get(context->set_credentials, k);
|
||||||
|
if (old) {
|
||||||
|
free_and_replace(old->data, unescaped);
|
||||||
|
old->size = l;
|
||||||
|
} else {
|
||||||
|
_cleanup_(exec_set_credential_freep) ExecSetCredential *sc = NULL;
|
||||||
|
|
||||||
|
sc = new0(ExecSetCredential, 1);
|
||||||
|
if (!sc)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
sc->id = TAKE_PTR(k);
|
||||||
|
sc->data = TAKE_PTR(unescaped);
|
||||||
|
sc->size = l;
|
||||||
|
|
||||||
|
r = hashmap_ensure_allocated(&context->set_credentials, &exec_set_credential_hash_ops);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
r = hashmap_put(context->set_credentials, sc->id, sc);
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
TAKE_PTR(sc);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int config_parse_load_credential(
|
||||||
|
const char *unit,
|
||||||
|
const char *filename,
|
||||||
|
unsigned line,
|
||||||
|
const char *section,
|
||||||
|
unsigned section_line,
|
||||||
|
const char *lvalue,
|
||||||
|
int ltype,
|
||||||
|
const char *rvalue,
|
||||||
|
void *data,
|
||||||
|
void *userdata) {
|
||||||
|
|
||||||
|
_cleanup_free_ char *word = NULL, *k = NULL, *q = NULL;
|
||||||
|
ExecContext *context = data;
|
||||||
|
Unit *u = userdata;
|
||||||
|
const char *p;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
assert(filename);
|
||||||
|
assert(lvalue);
|
||||||
|
assert(rvalue);
|
||||||
|
assert(context);
|
||||||
|
|
||||||
|
if (isempty(rvalue)) {
|
||||||
|
/* Empty assignment resets the list */
|
||||||
|
context->load_credentials = strv_free(context->load_credentials);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = rvalue;
|
||||||
|
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||||
|
if (r == -ENOMEM)
|
||||||
|
return log_oom();
|
||||||
|
if (r <= 0) {
|
||||||
|
log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = unit_full_printf(u, word, &k);
|
||||||
|
if (r < 0) {
|
||||||
|
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", word);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (!credential_name_valid(k)) {
|
||||||
|
log_syntax(unit, LOG_WARNING, filename, line, 0, "Credential name \"%s\" not valid, ignoring.", k);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
r = unit_full_printf(u, p, &q);
|
||||||
|
if (r < 0) {
|
||||||
|
log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in \"%s\", ignoring: %m", p);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (path_is_absolute(q) ? !path_is_normalized(q) : !credential_name_valid(q)) {
|
||||||
|
log_syntax(unit, LOG_WARNING, filename, line, r, "Credential source \"%s\" not valid, ignoring.", q);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
r = strv_consume_pair(&context->load_credentials, TAKE_PTR(k), TAKE_PTR(q));
|
||||||
|
if (r < 0)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int config_parse_set_status(
|
int config_parse_set_status(
|
||||||
const char *unit,
|
const char *unit,
|
||||||
const char *filename,
|
const char *filename,
|
||||||
|
|||||||
@ -90,6 +90,8 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_smack_process_label);
|
|||||||
CONFIG_PARSER_PROTOTYPE(config_parse_address_families);
|
CONFIG_PARSER_PROTOTYPE(config_parse_address_families);
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_runtime_preserve_mode);
|
CONFIG_PARSER_PROTOTYPE(config_parse_runtime_preserve_mode);
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
|
CONFIG_PARSER_PROTOTYPE(config_parse_exec_directories);
|
||||||
|
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
|
||||||
|
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
|
CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
|
CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
|
||||||
CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);
|
CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);
|
||||||
|
|||||||
@ -591,6 +591,7 @@ static char** sanitize_environment(char **l) {
|
|||||||
l,
|
l,
|
||||||
"CACHE_DIRECTORY",
|
"CACHE_DIRECTORY",
|
||||||
"CONFIGURATION_DIRECTORY",
|
"CONFIGURATION_DIRECTORY",
|
||||||
|
"CREDENTIALS_DIRECTORY",
|
||||||
"EXIT_CODE",
|
"EXIT_CODE",
|
||||||
"EXIT_STATUS",
|
"EXIT_STATUS",
|
||||||
"INVOCATION_ID",
|
"INVOCATION_ID",
|
||||||
@ -754,6 +755,7 @@ static int manager_setup_sigchld_event_source(Manager *m) {
|
|||||||
|
|
||||||
int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager **_m) {
|
int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager **_m) {
|
||||||
_cleanup_(manager_freep) Manager *m = NULL;
|
_cleanup_(manager_freep) Manager *m = NULL;
|
||||||
|
const char *e;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(_m);
|
assert(_m);
|
||||||
@ -857,6 +859,13 @@ int manager_new(UnitFileScope scope, ManagerTestRunFlags test_run_flags, Manager
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
e = secure_getenv("CREDENTIALS_DIRECTORY");
|
||||||
|
if (e) {
|
||||||
|
m->received_credentials = strdup(e);
|
||||||
|
if (!m->received_credentials)
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
r = sd_event_default(&m->event);
|
r = sd_event_default(&m->event);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
@ -1420,6 +1429,7 @@ Manager* manager_free(Manager *m) {
|
|||||||
|
|
||||||
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++)
|
for (ExecDirectoryType dt = 0; dt < _EXEC_DIRECTORY_TYPE_MAX; dt++)
|
||||||
m->prefix[dt] = mfree(m->prefix[dt]);
|
m->prefix[dt] = mfree(m->prefix[dt]);
|
||||||
|
free(m->received_credentials);
|
||||||
|
|
||||||
return mfree(m);
|
return mfree(m);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -424,6 +424,7 @@ struct Manager {
|
|||||||
|
|
||||||
/* Prefixes of e.g. RuntimeDirectory= */
|
/* Prefixes of e.g. RuntimeDirectory= */
|
||||||
char *prefix[_EXEC_DIRECTORY_TYPE_MAX];
|
char *prefix[_EXEC_DIRECTORY_TYPE_MAX];
|
||||||
|
char *received_credentials;
|
||||||
|
|
||||||
/* Used in the SIGCHLD and sd_notify() message invocation logic to avoid that we dispatch the same event
|
/* Used in the SIGCHLD and sd_notify() message invocation logic to avoid that we dispatch the same event
|
||||||
* multiple times on the same unit. */
|
* multiple times on the same unit. */
|
||||||
|
|||||||
@ -178,7 +178,8 @@ libcore = static_library(
|
|||||||
libkmod,
|
libkmod,
|
||||||
libapparmor,
|
libapparmor,
|
||||||
libselinux,
|
libselinux,
|
||||||
libmount])
|
libmount,
|
||||||
|
libacl])
|
||||||
|
|
||||||
systemd_sources = files('main.c')
|
systemd_sources = files('main.c')
|
||||||
|
|
||||||
|
|||||||
@ -537,6 +537,9 @@ int mount_setup(bool loaded_policy, bool leave_propagation) {
|
|||||||
(void) mkdir_label("/run/systemd", 0755);
|
(void) mkdir_label("/run/systemd", 0755);
|
||||||
(void) mkdir_label("/run/systemd/system", 0755);
|
(void) mkdir_label("/run/systemd/system", 0755);
|
||||||
|
|
||||||
|
/* Make sure we have a mount point to hide in sandboxes */
|
||||||
|
(void) mkdir_label("/run/credentials", 0755);
|
||||||
|
|
||||||
/* Also create /run/systemd/inaccessible nodes, so that we always have something to mount
|
/* Also create /run/systemd/inaccessible nodes, so that we always have something to mount
|
||||||
* inaccessible nodes from. If we run in a container the host might have created these for us already
|
* inaccessible nodes from. If we run in a container the host might have created these for us already
|
||||||
* in /run/host/inaccessible/. Use those if we can, since tht way we likely get access to block/char
|
* in /run/host/inaccessible/. Use those if we can, since tht way we likely get access to block/char
|
||||||
|
|||||||
@ -869,7 +869,7 @@ static void mount_enter_dead(Mount *m, MountResult f) {
|
|||||||
|
|
||||||
m->exec_runtime = exec_runtime_unref(m->exec_runtime, true);
|
m->exec_runtime = exec_runtime_unref(m->exec_runtime, true);
|
||||||
|
|
||||||
unit_destroy_runtime_directory(UNIT(m), &m->exec_context);
|
unit_destroy_runtime_data(UNIT(m), &m->exec_context);
|
||||||
|
|
||||||
unit_unref_uid_gid(UNIT(m), true);
|
unit_unref_uid_gid(UNIT(m), true);
|
||||||
|
|
||||||
|
|||||||
@ -1270,6 +1270,7 @@ static size_t namespace_calculate_mounts(
|
|||||||
size_t n_mount_images,
|
size_t n_mount_images,
|
||||||
const char* tmp_dir,
|
const char* tmp_dir,
|
||||||
const char* var_tmp_dir,
|
const char* var_tmp_dir,
|
||||||
|
const char *creds_path,
|
||||||
const char* log_namespace) {
|
const char* log_namespace) {
|
||||||
|
|
||||||
size_t protect_home_cnt;
|
size_t protect_home_cnt;
|
||||||
@ -1305,6 +1306,7 @@ static size_t namespace_calculate_mounts(
|
|||||||
protect_home_cnt + protect_system_cnt +
|
protect_home_cnt + protect_system_cnt +
|
||||||
(ns_info->protect_hostname ? 2 : 0) +
|
(ns_info->protect_hostname ? 2 : 0) +
|
||||||
(namespace_info_mount_apivfs(ns_info) ? ELEMENTSOF(apivfs_table) : 0) +
|
(namespace_info_mount_apivfs(ns_info) ? ELEMENTSOF(apivfs_table) : 0) +
|
||||||
|
(creds_path ? 2 : 1) +
|
||||||
!!log_namespace;
|
!!log_namespace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1389,6 +1391,7 @@ int setup_namespace(
|
|||||||
size_t n_mount_images,
|
size_t n_mount_images,
|
||||||
const char* tmp_dir,
|
const char* tmp_dir,
|
||||||
const char* var_tmp_dir,
|
const char* var_tmp_dir,
|
||||||
|
const char *creds_path,
|
||||||
const char *log_namespace,
|
const char *log_namespace,
|
||||||
unsigned long mount_flags,
|
unsigned long mount_flags,
|
||||||
const void *root_hash,
|
const void *root_hash,
|
||||||
@ -1494,6 +1497,7 @@ int setup_namespace(
|
|||||||
n_temporary_filesystems,
|
n_temporary_filesystems,
|
||||||
n_mount_images,
|
n_mount_images,
|
||||||
tmp_dir, var_tmp_dir,
|
tmp_dir, var_tmp_dir,
|
||||||
|
creds_path,
|
||||||
log_namespace);
|
log_namespace);
|
||||||
|
|
||||||
if (n_mounts > 0) {
|
if (n_mounts > 0) {
|
||||||
@ -1619,6 +1623,35 @@ int setup_namespace(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (creds_path) {
|
||||||
|
/* If our service has a credentials store configured, then bind that one in, but hide
|
||||||
|
* everything else. */
|
||||||
|
|
||||||
|
*(m++) = (MountEntry) {
|
||||||
|
.path_const = "/run/credentials",
|
||||||
|
.mode = TMPFS,
|
||||||
|
.read_only = true,
|
||||||
|
.options_const = "mode=0755" TMPFS_LIMITS_EMPTY_OR_ALMOST,
|
||||||
|
.flags = MS_NODEV|MS_STRICTATIME|MS_NOSUID|MS_NOEXEC,
|
||||||
|
};
|
||||||
|
|
||||||
|
*(m++) = (MountEntry) {
|
||||||
|
.path_const = creds_path,
|
||||||
|
.mode = BIND_MOUNT,
|
||||||
|
.read_only = true,
|
||||||
|
.source_const = creds_path,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
/* If our service has no credentials store configured, then make the whole
|
||||||
|
* credentials tree inaccessible wholesale. */
|
||||||
|
|
||||||
|
*(m++) = (MountEntry) {
|
||||||
|
.path_const = "/run/credentials",
|
||||||
|
.mode = INACCESSIBLE,
|
||||||
|
.ignore = true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
if (log_namespace) {
|
if (log_namespace) {
|
||||||
_cleanup_free_ char *q;
|
_cleanup_free_ char *q;
|
||||||
|
|
||||||
|
|||||||
@ -117,6 +117,7 @@ int setup_namespace(
|
|||||||
size_t n_mount_images,
|
size_t n_mount_images,
|
||||||
const char *tmp_dir,
|
const char *tmp_dir,
|
||||||
const char *var_tmp_dir,
|
const char *var_tmp_dir,
|
||||||
|
const char *creds_path,
|
||||||
const char *log_namespace,
|
const char *log_namespace,
|
||||||
unsigned long mount_flags,
|
unsigned long mount_flags,
|
||||||
const void *root_hash,
|
const void *root_hash,
|
||||||
|
|||||||
@ -1801,7 +1801,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart)
|
|||||||
s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
|
s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
|
||||||
|
|
||||||
/* Also, remove the runtime directory */
|
/* Also, remove the runtime directory */
|
||||||
unit_destroy_runtime_directory(UNIT(s), &s->exec_context);
|
unit_destroy_runtime_data(UNIT(s), &s->exec_context);
|
||||||
|
|
||||||
/* Get rid of the IPC bits of the user */
|
/* Get rid of the IPC bits of the user */
|
||||||
unit_unref_uid_gid(UNIT(s), true);
|
unit_unref_uid_gid(UNIT(s), true);
|
||||||
@ -2156,7 +2156,7 @@ static void service_enter_start(Service *s) {
|
|||||||
r = service_spawn(s,
|
r = service_spawn(s,
|
||||||
c,
|
c,
|
||||||
timeout,
|
timeout,
|
||||||
EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG,
|
EXEC_PASS_FDS|EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_APPLY_TTY_STDIN|EXEC_SET_WATCHDOG|EXEC_WRITE_CREDENTIALS,
|
||||||
&pid);
|
&pid);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|||||||
@ -2080,7 +2080,7 @@ static void socket_enter_dead(Socket *s, SocketResult f) {
|
|||||||
|
|
||||||
s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
|
s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
|
||||||
|
|
||||||
unit_destroy_runtime_directory(UNIT(s), &s->exec_context);
|
unit_destroy_runtime_data(UNIT(s), &s->exec_context);
|
||||||
|
|
||||||
unit_unref_uid_gid(UNIT(s), true);
|
unit_unref_uid_gid(UNIT(s), true);
|
||||||
|
|
||||||
|
|||||||
@ -706,7 +706,7 @@ static void swap_enter_dead(Swap *s, SwapResult f) {
|
|||||||
|
|
||||||
s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
|
s->exec_runtime = exec_runtime_unref(s->exec_runtime, true);
|
||||||
|
|
||||||
unit_destroy_runtime_directory(UNIT(s), &s->exec_context);
|
unit_destroy_runtime_data(UNIT(s), &s->exec_context);
|
||||||
|
|
||||||
unit_unref_uid_gid(UNIT(s), true);
|
unit_unref_uid_gid(UNIT(s), true);
|
||||||
|
|
||||||
|
|||||||
@ -5429,6 +5429,8 @@ int unit_set_exec_params(Unit *u, ExecParameters *p) {
|
|||||||
p->cgroup_path = u->cgroup_path;
|
p->cgroup_path = u->cgroup_path;
|
||||||
SET_FLAG(p->flags, EXEC_CGROUP_DELEGATE, unit_cgroup_delegate(u));
|
SET_FLAG(p->flags, EXEC_CGROUP_DELEGATE, unit_cgroup_delegate(u));
|
||||||
|
|
||||||
|
p->received_credentials = u->manager->received_credentials;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -6123,10 +6125,15 @@ int unit_test_trigger_loaded(Unit *u) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void unit_destroy_runtime_directory(Unit *u, const ExecContext *context) {
|
void unit_destroy_runtime_data(Unit *u, const ExecContext *context) {
|
||||||
|
assert(u);
|
||||||
|
assert(context);
|
||||||
|
|
||||||
if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO ||
|
if (context->runtime_directory_preserve_mode == EXEC_PRESERVE_NO ||
|
||||||
(context->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART && !unit_will_restart(u)))
|
(context->runtime_directory_preserve_mode == EXEC_PRESERVE_RESTART && !unit_will_restart(u)))
|
||||||
exec_context_destroy_runtime_directory(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
|
exec_context_destroy_runtime_directory(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]);
|
||||||
|
|
||||||
|
exec_context_destroy_credentials(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
int unit_clean(Unit *u, ExecCleanMask mask) {
|
int unit_clean(Unit *u, ExecCleanMask mask) {
|
||||||
|
|||||||
@ -880,7 +880,7 @@ int unit_failure_action_exit_status(Unit *u);
|
|||||||
|
|
||||||
int unit_test_trigger_loaded(Unit *u);
|
int unit_test_trigger_loaded(Unit *u);
|
||||||
|
|
||||||
void unit_destroy_runtime_directory(Unit *u, const ExecContext *context);
|
void unit_destroy_runtime_data(Unit *u, const ExecContext *context);
|
||||||
int unit_clean(Unit *u, ExecCleanMask mask);
|
int unit_clean(Unit *u, ExecCleanMask mask);
|
||||||
int unit_can_clean(Unit *u, ExecCleanMask *ret_mask);
|
int unit_can_clean(Unit *u, ExecCleanMask *ret_mask);
|
||||||
|
|
||||||
|
|||||||
@ -186,7 +186,7 @@ static int fix_acl(int fd, uid_t uid) {
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Make sure normal users can read (but not write or delete) their own coredumps */
|
/* Make sure normal users can read (but not write or delete) their own coredumps */
|
||||||
r = add_acls_for_user(fd, uid);
|
r = fd_add_uid_acl_permission(fd, uid, /* read = */ true, /* write = */ false, /* execute = */ false);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return log_error_errno(r, "Failed to adjust ACL of coredump: %m");
|
return log_error_errno(r, "Failed to adjust ACL of coredump: %m");
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -256,7 +256,7 @@ static void server_add_acls(JournalFile *f, uid_t uid) {
|
|||||||
if (uid_for_system_journal(uid))
|
if (uid_for_system_journal(uid))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
r = add_acls_for_user(f->fd, uid);
|
r = fd_add_uid_acl_permission(f->fd, uid, /* read = */ true, /* write = */ false, /* execute = */ false);
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
log_warning_errno(r, "Failed to set ACL on %s, ignoring: %m", f->path);
|
log_warning_errno(r, "Failed to set ACL on %s, ignoring: %m", f->path);
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -3,6 +3,8 @@
|
|||||||
libnspawn_core_sources = files('''
|
libnspawn_core_sources = files('''
|
||||||
nspawn-cgroup.c
|
nspawn-cgroup.c
|
||||||
nspawn-cgroup.h
|
nspawn-cgroup.h
|
||||||
|
nspawn-creds.c
|
||||||
|
nspawn-creds.h
|
||||||
nspawn-def.h
|
nspawn-def.h
|
||||||
nspawn-expose-ports.c
|
nspawn-expose-ports.c
|
||||||
nspawn-expose-ports.h
|
nspawn-expose-ports.h
|
||||||
|
|||||||
25
src/nspawn/nspawn-creds.c
Normal file
25
src/nspawn/nspawn-creds.c
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||||
|
|
||||||
|
#include "alloc-util.h"
|
||||||
|
#include "macro.h"
|
||||||
|
#include "memory-util.h"
|
||||||
|
#include "nspawn-creds.h"
|
||||||
|
|
||||||
|
static void credential_free(Credential *cred) {
|
||||||
|
assert(cred);
|
||||||
|
|
||||||
|
cred->id = mfree(cred->id);
|
||||||
|
cred->data = erase_and_free(cred->data);
|
||||||
|
cred->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void credential_free_all(Credential *creds, size_t n) {
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
assert(creds || n == 0);
|
||||||
|
|
||||||
|
for (i = 0; i < n; i++)
|
||||||
|
credential_free(creds + i);
|
||||||
|
|
||||||
|
free(creds);
|
||||||
|
}
|
||||||
12
src/nspawn/nspawn-creds.h
Normal file
12
src/nspawn/nspawn-creds.h
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
typedef struct Credential {
|
||||||
|
char *id;
|
||||||
|
void *data;
|
||||||
|
size_t size;
|
||||||
|
} Credential;
|
||||||
|
|
||||||
|
void credential_free_all(Credential *creds, size_t n);
|
||||||
@ -116,9 +116,10 @@ typedef enum SettingsMask {
|
|||||||
SETTING_USE_CGNS = UINT64_C(1) << 27,
|
SETTING_USE_CGNS = UINT64_C(1) << 27,
|
||||||
SETTING_CLONE_NS_FLAGS = UINT64_C(1) << 28,
|
SETTING_CLONE_NS_FLAGS = UINT64_C(1) << 28,
|
||||||
SETTING_CONSOLE_MODE = UINT64_C(1) << 29,
|
SETTING_CONSOLE_MODE = UINT64_C(1) << 29,
|
||||||
SETTING_RLIMIT_FIRST = UINT64_C(1) << 30, /* we define one bit per resource limit here */
|
SETTING_CREDENTIALS = UINT64_C(1) << 30,
|
||||||
SETTING_RLIMIT_LAST = UINT64_C(1) << (30 + _RLIMIT_MAX - 1),
|
SETTING_RLIMIT_FIRST = UINT64_C(1) << 31, /* we define one bit per resource limit here */
|
||||||
_SETTINGS_MASK_ALL = (UINT64_C(1) << (30 + _RLIMIT_MAX)) -1,
|
SETTING_RLIMIT_LAST = UINT64_C(1) << (31 + _RLIMIT_MAX - 1),
|
||||||
|
_SETTINGS_MASK_ALL = (UINT64_C(1) << (31 + _RLIMIT_MAX)) -1,
|
||||||
_SETTING_FORCE_ENUM_WIDTH = UINT64_MAX
|
_SETTING_FORCE_ENUM_WIDTH = UINT64_MAX
|
||||||
} SettingsMask;
|
} SettingsMask;
|
||||||
|
|
||||||
|
|||||||
@ -36,6 +36,7 @@
|
|||||||
#include "dev-setup.h"
|
#include "dev-setup.h"
|
||||||
#include "dissect-image.h"
|
#include "dissect-image.h"
|
||||||
#include "env-util.h"
|
#include "env-util.h"
|
||||||
|
#include "escape.h"
|
||||||
#include "fd-util.h"
|
#include "fd-util.h"
|
||||||
#include "fdset.h"
|
#include "fdset.h"
|
||||||
#include "fileio.h"
|
#include "fileio.h"
|
||||||
@ -45,6 +46,7 @@
|
|||||||
#include "hexdecoct.h"
|
#include "hexdecoct.h"
|
||||||
#include "hostname-util.h"
|
#include "hostname-util.h"
|
||||||
#include "id128-util.h"
|
#include "id128-util.h"
|
||||||
|
#include "io-util.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "loop-util.h"
|
#include "loop-util.h"
|
||||||
#include "loopback-setup.h"
|
#include "loopback-setup.h"
|
||||||
@ -58,6 +60,7 @@
|
|||||||
#include "namespace-util.h"
|
#include "namespace-util.h"
|
||||||
#include "netlink-util.h"
|
#include "netlink-util.h"
|
||||||
#include "nspawn-cgroup.h"
|
#include "nspawn-cgroup.h"
|
||||||
|
#include "nspawn-creds.h"
|
||||||
#include "nspawn-def.h"
|
#include "nspawn-def.h"
|
||||||
#include "nspawn-expose-ports.h"
|
#include "nspawn-expose-ports.h"
|
||||||
#include "nspawn-mount.h"
|
#include "nspawn-mount.h"
|
||||||
@ -219,6 +222,8 @@ static DeviceNode* arg_extra_nodes = NULL;
|
|||||||
static size_t arg_n_extra_nodes = 0;
|
static size_t arg_n_extra_nodes = 0;
|
||||||
static char **arg_sysctl = NULL;
|
static char **arg_sysctl = NULL;
|
||||||
static ConsoleMode arg_console_mode = _CONSOLE_MODE_INVALID;
|
static ConsoleMode arg_console_mode = _CONSOLE_MODE_INVALID;
|
||||||
|
static Credential *arg_credentials = NULL;
|
||||||
|
static size_t arg_n_credentials = 0;
|
||||||
|
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_directory, freep);
|
||||||
STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
|
STATIC_DESTRUCTOR_REGISTER(arg_template, freep);
|
||||||
@ -406,7 +411,13 @@ static int help(void) {
|
|||||||
"%3$sInput/Output:%4$s\n"
|
"%3$sInput/Output:%4$s\n"
|
||||||
" --console=MODE Select how stdin/stdout/stderr and /dev/console are\n"
|
" --console=MODE Select how stdin/stdout/stderr and /dev/console are\n"
|
||||||
" set up for the container.\n"
|
" set up for the container.\n"
|
||||||
" -P --pipe Equivalent to --console=pipe\n"
|
" -P --pipe Equivalent to --console=pipe\n\n"
|
||||||
|
"%3$sCredentials:%4$s\n"
|
||||||
|
" --set-credential=ID:VALUE\n"
|
||||||
|
" Pass a credential with literal value to container.\n"
|
||||||
|
" --load-credential=ID:PATH\n"
|
||||||
|
" Load credential to pass to container from file or\n"
|
||||||
|
" AF_UNIX stream socket.\n"
|
||||||
"\nSee the %2$s for details.\n"
|
"\nSee the %2$s for details.\n"
|
||||||
, program_invocation_short_name
|
, program_invocation_short_name
|
||||||
, link
|
, link
|
||||||
@ -675,6 +686,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
ARG_NO_PAGER,
|
ARG_NO_PAGER,
|
||||||
ARG_VERITY_DATA,
|
ARG_VERITY_DATA,
|
||||||
ARG_ROOT_HASH_SIG,
|
ARG_ROOT_HASH_SIG,
|
||||||
|
ARG_SET_CREDENTIAL,
|
||||||
|
ARG_LOAD_CREDENTIAL,
|
||||||
};
|
};
|
||||||
|
|
||||||
static const struct option options[] = {
|
static const struct option options[] = {
|
||||||
@ -742,6 +755,8 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||||
{ "verity-data", required_argument, NULL, ARG_VERITY_DATA },
|
{ "verity-data", required_argument, NULL, ARG_VERITY_DATA },
|
||||||
{ "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
|
{ "root-hash-sig", required_argument, NULL, ARG_ROOT_HASH_SIG },
|
||||||
|
{ "set-credential", required_argument, NULL, ARG_SET_CREDENTIAL },
|
||||||
|
{ "load-credential", required_argument, NULL, ARG_LOAD_CREDENTIAL },
|
||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1496,6 +1511,105 @@ static int parse_argv(int argc, char *argv[]) {
|
|||||||
arg_pager_flags |= PAGER_DISABLE;
|
arg_pager_flags |= PAGER_DISABLE;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case ARG_SET_CREDENTIAL: {
|
||||||
|
_cleanup_free_ char *word = NULL, *data = NULL;
|
||||||
|
const char *p = optarg;
|
||||||
|
Credential *a;
|
||||||
|
size_t i;
|
||||||
|
int l;
|
||||||
|
|
||||||
|
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||||
|
if (r == -ENOMEM)
|
||||||
|
return log_oom();
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse --set-credential= parameter: %m");
|
||||||
|
if (r == 0 || !p)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --set-credential=: %s", optarg);
|
||||||
|
|
||||||
|
if (!credential_name_valid(word))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word);
|
||||||
|
|
||||||
|
for (i = 0; i < arg_n_credentials; i++)
|
||||||
|
if (streq(arg_credentials[i].id, word))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word);
|
||||||
|
|
||||||
|
l = cunescape(p, UNESCAPE_ACCEPT_NUL, &data);
|
||||||
|
if (l < 0)
|
||||||
|
return log_error_errno(l, "Failed to unescape credential data: %s", p);
|
||||||
|
|
||||||
|
a = reallocarray(arg_credentials, arg_n_credentials + 1, sizeof(Credential));
|
||||||
|
if (!a)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
a[arg_n_credentials++] = (Credential) {
|
||||||
|
.id = TAKE_PTR(word),
|
||||||
|
.data = TAKE_PTR(data),
|
||||||
|
.size = l,
|
||||||
|
};
|
||||||
|
|
||||||
|
arg_credentials = a;
|
||||||
|
|
||||||
|
arg_settings_mask |= SETTING_CREDENTIALS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case ARG_LOAD_CREDENTIAL: {
|
||||||
|
ReadFullFileFlags flags = READ_FULL_FILE_SECURE;
|
||||||
|
_cleanup_(erase_and_freep) char *data = NULL;
|
||||||
|
_cleanup_free_ char *word = NULL, *j = NULL;
|
||||||
|
const char *p = optarg;
|
||||||
|
Credential *a;
|
||||||
|
size_t size, i;
|
||||||
|
|
||||||
|
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||||
|
if (r == -ENOMEM)
|
||||||
|
return log_oom();
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse --set-credential= parameter: %m");
|
||||||
|
if (r == 0 || !p)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing value for --set-credential=: %s", optarg);
|
||||||
|
|
||||||
|
if (!credential_name_valid(word))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential name is not valid: %s", word);
|
||||||
|
|
||||||
|
for (i = 0; i < arg_n_credentials; i++)
|
||||||
|
if (streq(arg_credentials[i].id, word))
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EEXIST), "Duplicate credential '%s', refusing.", word);
|
||||||
|
|
||||||
|
if (path_is_absolute(p))
|
||||||
|
flags |= READ_FULL_FILE_CONNECT_SOCKET;
|
||||||
|
else {
|
||||||
|
const char *e;
|
||||||
|
|
||||||
|
e = getenv("CREDENTIALS_DIRECTORY");
|
||||||
|
if (!e)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Credential not available (no credentials passed at all): %s", word);
|
||||||
|
|
||||||
|
j = path_join(e, p);
|
||||||
|
if (!j)
|
||||||
|
return log_oom();
|
||||||
|
}
|
||||||
|
|
||||||
|
r = read_full_file_full(AT_FDCWD, j ?: p, flags, &data, &size);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to read credential '%s': %m", j ?: p);
|
||||||
|
|
||||||
|
a = reallocarray(arg_credentials, arg_n_credentials + 1, sizeof(Credential));
|
||||||
|
if (!a)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
a[arg_n_credentials++] = (Credential) {
|
||||||
|
.id = TAKE_PTR(word),
|
||||||
|
.data = TAKE_PTR(data),
|
||||||
|
.size = size,
|
||||||
|
};
|
||||||
|
|
||||||
|
arg_credentials = a;
|
||||||
|
|
||||||
|
arg_settings_mask |= SETTING_CREDENTIALS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case '?':
|
case '?':
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
||||||
@ -2228,6 +2342,66 @@ static int setup_keyring(void) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int setup_credentials(const char *root) {
|
||||||
|
const char *q;
|
||||||
|
int r;
|
||||||
|
|
||||||
|
if (arg_n_credentials <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
r = userns_mkdir(root, "/run/host", 0755, 0, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to create /run/host: %m");
|
||||||
|
|
||||||
|
r = userns_mkdir(root, "/run/host/credentials", 0700, 0, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to create /run/host/credentials: %m");
|
||||||
|
|
||||||
|
q = prefix_roota(root, "/run/host/credentials");
|
||||||
|
r = mount_verbose(LOG_ERR, NULL, q, "ramfs", MS_NOSUID|MS_NOEXEC|MS_NODEV, "mode=0700");
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < arg_n_credentials; i++) {
|
||||||
|
_cleanup_free_ char *j = NULL;
|
||||||
|
_cleanup_close_ int fd = -1;
|
||||||
|
|
||||||
|
j = path_join(q, arg_credentials[i].id);
|
||||||
|
if (!j)
|
||||||
|
return log_oom();
|
||||||
|
|
||||||
|
fd = open(j, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC|O_NOFOLLOW, 0600);
|
||||||
|
if (fd < 0)
|
||||||
|
return log_error_errno(errno, "Failed to create credential file %s: %m", j);
|
||||||
|
|
||||||
|
r = loop_write(fd, arg_credentials[i].data, arg_credentials[i].size, /* do_poll= */ false);
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to write credential to file %s: %m", j);
|
||||||
|
|
||||||
|
if (fchmod(fd, 0400) < 0)
|
||||||
|
return log_error_errno(errno, "Failed to adjust access mode of %s: %m", j);
|
||||||
|
|
||||||
|
if (arg_userns_mode != USER_NAMESPACE_NO) {
|
||||||
|
if (fchown(fd, arg_uid_shift, arg_uid_shift) < 0)
|
||||||
|
return log_error_errno(errno, "Failed to adjust ownership of %s: %m", j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (chmod(q, 0500) < 0)
|
||||||
|
return log_error_errno(errno, "Failed to adjust access mode of %s: %m", q);
|
||||||
|
|
||||||
|
r = userns_lchown(q, 0, 0);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
/* Make both mount and superblock read-only now */
|
||||||
|
r = mount_verbose(LOG_ERR, NULL, q, NULL, MS_REMOUNT|MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
|
return mount_verbose(LOG_ERR, NULL, q, NULL, MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV, "mode=0500");
|
||||||
|
}
|
||||||
|
|
||||||
static int setup_kmsg(int kmsg_socket) {
|
static int setup_kmsg(int kmsg_socket) {
|
||||||
_cleanup_(unlink_and_freep) char *from = NULL;
|
_cleanup_(unlink_and_freep) char *from = NULL;
|
||||||
_cleanup_free_ char *fifo = NULL;
|
_cleanup_free_ char *fifo = NULL;
|
||||||
@ -2941,6 +3115,7 @@ static int inner_child(
|
|||||||
NULL, /* LISTEN_FDS */
|
NULL, /* LISTEN_FDS */
|
||||||
NULL, /* LISTEN_PID */
|
NULL, /* LISTEN_PID */
|
||||||
NULL, /* NOTIFY_SOCKET */
|
NULL, /* NOTIFY_SOCKET */
|
||||||
|
NULL, /* CREDENTIALS_DIRECTORY */
|
||||||
NULL
|
NULL
|
||||||
};
|
};
|
||||||
const char *exec_target;
|
const char *exec_target;
|
||||||
@ -3191,6 +3366,13 @@ static int inner_child(
|
|||||||
if (asprintf((char **)(envp + n_env++), "NOTIFY_SOCKET=%s", NSPAWN_NOTIFY_SOCKET_PATH) < 0)
|
if (asprintf((char **)(envp + n_env++), "NOTIFY_SOCKET=%s", NSPAWN_NOTIFY_SOCKET_PATH) < 0)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
|
|
||||||
|
if (arg_n_credentials > 0) {
|
||||||
|
envp[n_env] = strdup("CREDENTIALS_DIRECTORY=/run/host/credentials");
|
||||||
|
if (!envp[n_env])
|
||||||
|
return log_oom();
|
||||||
|
n_env++;
|
||||||
|
}
|
||||||
|
|
||||||
env_use = strv_env_merge(3, envp, os_release_pairs, arg_setenv);
|
env_use = strv_env_merge(3, envp, os_release_pairs, arg_setenv);
|
||||||
if (!env_use)
|
if (!env_use)
|
||||||
return log_oom();
|
return log_oom();
|
||||||
@ -3538,6 +3720,10 @@ static int outer_child(
|
|||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|
||||||
|
r = setup_credentials(directory);
|
||||||
|
if (r < 0)
|
||||||
|
return r;
|
||||||
|
|
||||||
r = mount_custom(
|
r = mount_custom(
|
||||||
directory,
|
directory,
|
||||||
arg_custom_mounts,
|
arg_custom_mounts,
|
||||||
@ -5339,6 +5525,7 @@ finish:
|
|||||||
expose_port_free_all(arg_expose_ports);
|
expose_port_free_all(arg_expose_ports);
|
||||||
rlimit_free_all(arg_rlimit);
|
rlimit_free_all(arg_rlimit);
|
||||||
device_node_array_free(arg_extra_nodes, arg_n_extra_nodes);
|
device_node_array_free(arg_extra_nodes, arg_n_extra_nodes);
|
||||||
|
credential_free_all(arg_credentials, arg_n_credentials);
|
||||||
|
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return r;
|
return r;
|
||||||
|
|||||||
@ -12,12 +12,13 @@
|
|||||||
#include "user-util.h"
|
#include "user-util.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) {
|
int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *ret_entry) {
|
||||||
acl_entry_t i;
|
acl_entry_t i;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
assert(acl);
|
assert(acl);
|
||||||
assert(entry);
|
assert(uid_is_valid(uid));
|
||||||
|
assert(ret_entry);
|
||||||
|
|
||||||
for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
|
for (r = acl_get_entry(acl, ACL_FIRST_ENTRY, &i);
|
||||||
r > 0;
|
r > 0;
|
||||||
@ -41,13 +42,14 @@ int acl_find_uid(acl_t acl, uid_t uid, acl_entry_t *entry) {
|
|||||||
acl_free(u);
|
acl_free(u);
|
||||||
|
|
||||||
if (b) {
|
if (b) {
|
||||||
*entry = i;
|
*ret_entry = i;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
return -errno;
|
return -errno;
|
||||||
|
|
||||||
|
*ret_entry = NULL;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -376,12 +378,21 @@ int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int add_acls_for_user(int fd, uid_t uid) {
|
int fd_add_uid_acl_permission(
|
||||||
|
int fd,
|
||||||
|
uid_t uid,
|
||||||
|
bool rd,
|
||||||
|
bool wr,
|
||||||
|
bool ex) {
|
||||||
|
|
||||||
_cleanup_(acl_freep) acl_t acl = NULL;
|
_cleanup_(acl_freep) acl_t acl = NULL;
|
||||||
acl_permset_t permset;
|
acl_permset_t permset;
|
||||||
acl_entry_t entry;
|
acl_entry_t entry;
|
||||||
int r;
|
int r;
|
||||||
|
|
||||||
|
/* Adds an ACL entry for the specified file to allow the indicated access to the specified
|
||||||
|
* user. Operates purely incrementally. */
|
||||||
|
|
||||||
assert(fd >= 0);
|
assert(fd >= 0);
|
||||||
assert(uid_is_valid(uid));
|
assert(uid_is_valid(uid));
|
||||||
|
|
||||||
@ -397,10 +408,14 @@ int add_acls_for_user(int fd, uid_t uid) {
|
|||||||
return -errno;
|
return -errno;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* We do not recalculate the mask unconditionally here, so that the fchmod() mask above stays
|
if (acl_get_permset(entry, &permset) < 0)
|
||||||
* intact. */
|
return -errno;
|
||||||
if (acl_get_permset(entry, &permset) < 0 ||
|
|
||||||
acl_add_perm(permset, ACL_READ) < 0)
|
if (rd && acl_add_perm(permset, ACL_READ) < 0)
|
||||||
|
return -errno;
|
||||||
|
if (wr && acl_add_perm(permset, ACL_WRITE) < 0)
|
||||||
|
return -errno;
|
||||||
|
if (ex && acl_add_perm(permset, ACL_EXECUTE) < 0)
|
||||||
return -errno;
|
return -errno;
|
||||||
|
|
||||||
r = calc_acl_mask_if_needed(&acl);
|
r = calc_acl_mask_if_needed(&acl);
|
||||||
|
|||||||
@ -15,7 +15,7 @@ int add_base_acls_if_needed(acl_t *acl_p, const char *path);
|
|||||||
int acl_search_groups(const char* path, char ***ret_groups);
|
int acl_search_groups(const char* path, char ***ret_groups);
|
||||||
int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask);
|
int parse_acl(const char *text, acl_t *acl_access, acl_t *acl_default, bool want_mask);
|
||||||
int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl);
|
int acls_for_file(const char *path, acl_type_t type, acl_t new, acl_t *acl);
|
||||||
int add_acls_for_user(int fd, uid_t uid);
|
int fd_add_uid_acl_permission(int fd, uid_t uid, bool rd, bool wr, bool ex);
|
||||||
|
|
||||||
/* acl_free takes multiple argument types.
|
/* acl_free takes multiple argument types.
|
||||||
* Multiple cleanup functions are necessary. */
|
* Multiple cleanup functions are necessary. */
|
||||||
|
|||||||
@ -973,6 +973,117 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (streq(field, "SetCredential")) {
|
||||||
|
r = sd_bus_message_open_container(m, 'r', "sv");
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_append_basic(m, 's', "SetCredential");
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_open_container(m, 'v', "a(say)");
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
if (isempty(eq))
|
||||||
|
r = sd_bus_message_append(m, "a(say)", 0);
|
||||||
|
else {
|
||||||
|
_cleanup_free_ char *word = NULL, *unescaped = NULL;
|
||||||
|
const char *p = eq;
|
||||||
|
int l;
|
||||||
|
|
||||||
|
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||||
|
if (r == -ENOMEM)
|
||||||
|
return log_oom();
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse SetCredential= parameter: %s", eq);
|
||||||
|
if (r == 0 || !p)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing argument to SetCredential=.");
|
||||||
|
|
||||||
|
l = cunescape(p, UNESCAPE_ACCEPT_NUL, &unescaped);
|
||||||
|
if (l < 0)
|
||||||
|
return log_error_errno(l, "Failed to unescape SetCredential= value: %s", p);
|
||||||
|
|
||||||
|
r = sd_bus_message_open_container(m, 'a', "(say)");
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_open_container(m, 'r', "say");
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_append(m, "s", word);
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_append_array(m, 'y', unescaped, l);
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_close_container(m);
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_close_container(m);
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_close_container(m);
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_close_container(m);
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (streq(field, "LoadCredential")) {
|
||||||
|
r = sd_bus_message_open_container(m, 'r', "sv");
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_append_basic(m, 's', "LoadCredential");
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_open_container(m, 'v', "a(ss)");
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
if (isempty(eq))
|
||||||
|
r = sd_bus_message_append(m, "a(ss)", 0);
|
||||||
|
else {
|
||||||
|
_cleanup_free_ char *word = NULL;
|
||||||
|
const char *p = eq;
|
||||||
|
|
||||||
|
r = extract_first_word(&p, &word, ":", EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||||
|
if (r == -ENOMEM)
|
||||||
|
return log_oom();
|
||||||
|
if (r < 0)
|
||||||
|
return log_error_errno(r, "Failed to parse LoadCredential= parameter: %s", eq);
|
||||||
|
if (r == 0 || !p)
|
||||||
|
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Missing argument to LoadCredential=.");
|
||||||
|
|
||||||
|
r = sd_bus_message_append(m, "a(ss)", 1, word, p);
|
||||||
|
}
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_close_container(m);
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
r = sd_bus_message_close_container(m);
|
||||||
|
if (r < 0)
|
||||||
|
return bus_log_create_error(r);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
if (streq(field, "LogExtraFields")) {
|
if (streq(field, "LogExtraFields")) {
|
||||||
r = sd_bus_message_open_container(m, 'r', "sv");
|
r = sd_bus_message_open_container(m, 'r', "sv");
|
||||||
if (r < 0)
|
if (r < 0)
|
||||||
|
|||||||
@ -70,6 +70,8 @@ const ExitStatusMapping exit_status_mappings[256] = {
|
|||||||
[EXIT_LOGS_DIRECTORY] = { "LOGS_DIRECTORY", EXIT_STATUS_SYSTEMD },
|
[EXIT_LOGS_DIRECTORY] = { "LOGS_DIRECTORY", EXIT_STATUS_SYSTEMD },
|
||||||
[EXIT_CONFIGURATION_DIRECTORY] = { "CONFIGURATION_DIRECTORY", EXIT_STATUS_SYSTEMD },
|
[EXIT_CONFIGURATION_DIRECTORY] = { "CONFIGURATION_DIRECTORY", EXIT_STATUS_SYSTEMD },
|
||||||
[EXIT_NUMA_POLICY] = { "NUMA_POLICY", EXIT_STATUS_SYSTEMD },
|
[EXIT_NUMA_POLICY] = { "NUMA_POLICY", EXIT_STATUS_SYSTEMD },
|
||||||
|
[EXIT_CREDENTIALS] = { "CREDENTIALS", EXIT_STATUS_SYSTEMD },
|
||||||
|
|
||||||
[EXIT_EXCEPTION] = { "EXCEPTION", EXIT_STATUS_SYSTEMD },
|
[EXIT_EXCEPTION] = { "EXCEPTION", EXIT_STATUS_SYSTEMD },
|
||||||
|
|
||||||
[EXIT_INVALIDARGUMENT] = { "INVALIDARGUMENT", EXIT_STATUS_LSB },
|
[EXIT_INVALIDARGUMENT] = { "INVALIDARGUMENT", EXIT_STATUS_LSB },
|
||||||
|
|||||||
@ -70,6 +70,7 @@ enum {
|
|||||||
EXIT_LOGS_DIRECTORY, /* 240 */
|
EXIT_LOGS_DIRECTORY, /* 240 */
|
||||||
EXIT_CONFIGURATION_DIRECTORY,
|
EXIT_CONFIGURATION_DIRECTORY,
|
||||||
EXIT_NUMA_POLICY,
|
EXIT_NUMA_POLICY,
|
||||||
|
EXIT_CREDENTIALS,
|
||||||
|
|
||||||
EXIT_EXCEPTION = 255, /* Whenever we want to propagate an abnormal/signal exit, in line with bash */
|
EXIT_EXCEPTION = 255, /* Whenever we want to propagate an abnormal/signal exit, in line with bash */
|
||||||
};
|
};
|
||||||
|
|||||||
@ -658,6 +658,10 @@ tests += [
|
|||||||
[],
|
[],
|
||||||
[]],
|
[]],
|
||||||
|
|
||||||
|
[['src/test/test-rm-rf.c'],
|
||||||
|
[],
|
||||||
|
[]],
|
||||||
|
|
||||||
[['src/test/test-chase-symlinks.c'],
|
[['src/test/test-chase-symlinks.c'],
|
||||||
[],
|
[],
|
||||||
[],
|
[],
|
||||||
|
|||||||
@ -41,8 +41,8 @@ static void test_add_acls_for_user(void) {
|
|||||||
} else
|
} else
|
||||||
uid = getuid();
|
uid = getuid();
|
||||||
|
|
||||||
r = add_acls_for_user(fd, uid);
|
r = fd_add_uid_acl_permission(fd, uid, true, false, false);
|
||||||
log_info_errno(r, "add_acls_for_user(%d, "UID_FMT"): %m", fd, uid);
|
log_info_errno(r, "fd_add_uid_acl_permission(%i, "UID_FMT", true, false, false): %m", fd, uid);
|
||||||
assert_se(r >= 0);
|
assert_se(r >= 0);
|
||||||
|
|
||||||
cmd = strjoina("ls -l ", fn);
|
cmd = strjoina("ls -l ", fn);
|
||||||
@ -53,7 +53,7 @@ static void test_add_acls_for_user(void) {
|
|||||||
|
|
||||||
/* set the acls again */
|
/* set the acls again */
|
||||||
|
|
||||||
r = add_acls_for_user(fd, uid);
|
r = fd_add_uid_acl_permission(fd, uid, true, false, false);
|
||||||
assert_se(r >= 0);
|
assert_se(r >= 0);
|
||||||
|
|
||||||
cmd = strjoina("ls -l ", fn);
|
cmd = strjoina("ls -l ", fn);
|
||||||
|
|||||||
@ -163,6 +163,7 @@ static void test_protect_kernel_logs(void) {
|
|||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
NULL,
|
NULL,
|
||||||
|
NULL,
|
||||||
0,
|
0,
|
||||||
NULL,
|
NULL,
|
||||||
0,
|
0,
|
||||||
|
|||||||
@ -78,6 +78,7 @@ int main(int argc, char *argv[]) {
|
|||||||
tmp_dir,
|
tmp_dir,
|
||||||
var_tmp_dir,
|
var_tmp_dir,
|
||||||
NULL,
|
NULL,
|
||||||
|
NULL,
|
||||||
0,
|
0,
|
||||||
NULL,
|
NULL,
|
||||||
0,
|
0,
|
||||||
|
|||||||
74
src/test/test-rm-rf.c
Normal file
74
src/test/test-rm-rf.c
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||||
|
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "alloc-util.h"
|
||||||
|
#include "process-util.h"
|
||||||
|
#include "rm-rf.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
#include "tests.h"
|
||||||
|
#include "tmpfile-util.h"
|
||||||
|
|
||||||
|
static void test_rm_rf_chmod_inner(void) {
|
||||||
|
_cleanup_free_ char *d = NULL;
|
||||||
|
const char *x, *y;
|
||||||
|
|
||||||
|
assert_se(getuid() != 0);
|
||||||
|
|
||||||
|
assert_se(mkdtemp_malloc(NULL, &d) >= 0);
|
||||||
|
|
||||||
|
x = strjoina(d, "/d");
|
||||||
|
assert_se(mkdir(x, 0700) >= 0);
|
||||||
|
y = strjoina(x, "/f");
|
||||||
|
assert_se(mknod(y, S_IFREG | 0600, 0) >= 0);
|
||||||
|
|
||||||
|
assert_se(chmod(y, 0400) >= 0);
|
||||||
|
assert_se(chmod(x, 0500) >= 0);
|
||||||
|
assert_se(chmod(d, 0500) >= 0);
|
||||||
|
|
||||||
|
assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_ROOT) == -EACCES);
|
||||||
|
|
||||||
|
assert_se(access(d, F_OK) >= 0);
|
||||||
|
assert_se(access(x, F_OK) >= 0);
|
||||||
|
assert_se(access(y, F_OK) >= 0);
|
||||||
|
|
||||||
|
assert_se(rm_rf(d, REMOVE_PHYSICAL|REMOVE_ROOT|REMOVE_CHMOD) >= 0);
|
||||||
|
|
||||||
|
errno = 0;
|
||||||
|
assert_se(access(d, F_OK) < 0 && errno == ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void test_rm_rf_chmod(void) {
|
||||||
|
int r;
|
||||||
|
|
||||||
|
log_info("/* %s */", __func__);
|
||||||
|
|
||||||
|
if (getuid() == 0) {
|
||||||
|
/* This test only works unpriv (as only then the access mask for the owning user matters),
|
||||||
|
* hence drop privs here */
|
||||||
|
|
||||||
|
r = safe_fork("(setresuid)", FORK_DEATHSIG|FORK_WAIT, NULL);
|
||||||
|
assert_se(r >= 0);
|
||||||
|
|
||||||
|
if (r == 0) {
|
||||||
|
/* child */
|
||||||
|
|
||||||
|
assert_se(setresuid(1, 1, 1) >= 0);
|
||||||
|
|
||||||
|
test_rm_rf_chmod_inner();
|
||||||
|
_exit(EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
test_rm_rf_chmod_inner();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv) {
|
||||||
|
test_setup_logging(LOG_DEBUG);
|
||||||
|
|
||||||
|
test_rm_rf_chmod();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
1
test/TEST-54-CREDS/Makefile
Symbolic link
1
test/TEST-54-CREDS/Makefile
Symbolic link
@ -0,0 +1 @@
|
|||||||
|
../TEST-01-BASIC/Makefile
|
||||||
7
test/TEST-54-CREDS/test.sh
Executable file
7
test/TEST-54-CREDS/test.sh
Executable file
@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
TEST_DESCRIPTION="test credentials"
|
||||||
|
|
||||||
|
. $TEST_BASE_DIR/test-functions
|
||||||
|
|
||||||
|
do_test "$@" 54
|
||||||
7
test/units/testsuite-54.service
Normal file
7
test/units/testsuite-54.service
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=TESTSUITE-54-CREDS
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
ExecStartPre=rm -f /failed /testok
|
||||||
|
ExecStart=/usr/lib/systemd/tests/testdata/units/%N.sh
|
||||||
|
Type=oneshot
|
||||||
31
test/units/testsuite-54.sh
Executable file
31
test/units/testsuite-54.sh
Executable file
@ -0,0 +1,31 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -ex
|
||||||
|
|
||||||
|
systemd-analyze log-level debug
|
||||||
|
|
||||||
|
# Verify that the creds are properly loaded and we can read them from the service's unpriv user
|
||||||
|
systemd-run -p LoadCredential=passwd:/etc/passwd \
|
||||||
|
-p LoadCredential=shadow:/etc/shadow \
|
||||||
|
-p SetCredential=dog:wuff \
|
||||||
|
-p DynamicUser=1 \
|
||||||
|
--wait \
|
||||||
|
--pipe \
|
||||||
|
cat '${CREDENTIALS_DIRECTORY}/passwd' '${CREDENTIALS_DIRECTORY}/shadow' '${CREDENTIALS_DIRECTORY}/dog' > /tmp/ts54-concat
|
||||||
|
( cat /etc/passwd /etc/shadow && echo -n wuff ) | cmp /tmp/ts54-concat
|
||||||
|
rm /tmp/ts54-concat
|
||||||
|
|
||||||
|
# Verify that the creds are immutable
|
||||||
|
! systemd-run -p LoadCredential=passwd:/etc/passwd \
|
||||||
|
-p DynamicUser=1 \
|
||||||
|
--wait \
|
||||||
|
touch '${CREDENTIALS_DIRECTORY}/passwd'
|
||||||
|
! systemd-run -p LoadCredential=passwd:/etc/passwd \
|
||||||
|
-p DynamicUser=1 \
|
||||||
|
--wait \
|
||||||
|
rm '${CREDENTIALS_DIRECTORY}/passwd'
|
||||||
|
|
||||||
|
systemd-analyze log-level info
|
||||||
|
|
||||||
|
echo OK > /testok
|
||||||
|
|
||||||
|
exit 0
|
||||||
Loading…
x
Reference in New Issue
Block a user