core/service: support credentials refreshing on reload (#40093)

Closes https://github.com/systemd/systemd/issues/21099
Closes https://github.com/systemd/systemd/issues/35227
This commit is contained in:
Daan De Meyer
2026-02-13 10:21:44 +01:00
committed by GitHub
28 changed files with 1120 additions and 181 deletions

View File

@@ -2833,6 +2833,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
readonly as ExtraFileDescriptorNames = ['...', ...];
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly i ReloadSignal = ...;
@org.freedesktop.DBus.Property.EmitsChangedSignal("const")
readonly as RefreshOnReload = ['...', ...];
readonly t ExecMainStartTimestamp = ...;
readonly t ExecMainStartTimestampMonotonic = ...;
readonly t ExecMainExitTimestamp = ...;
@@ -3534,6 +3536,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<!--property ReloadSignal is not documented!-->
<!--property RefreshOnReload is not documented!-->
<!--property ExecCondition is not documented!-->
<!--property ExecConditionEx is not documented!-->
@@ -4180,6 +4184,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice {
<variablelist class="dbus-property" generated="True" extra-ref="ReloadSignal"/>
<variablelist class="dbus-property" generated="True" extra-ref="RefreshOnReload"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestamp"/>
<variablelist class="dbus-property" generated="True" extra-ref="ExecMainStartTimestampMonotonic"/>
@@ -12514,8 +12520,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
<varname>ManagedOOMKills</varname>,
<varname>ExecReloadPost</varname>, and
<varname>ExecReloadPostEx</varname> were added in version 259.</para>
<para><varname>BindNetworkInterface</varname>, and
<varname>MemoryTHP</varname> were added in version 260.</para>
<para><varname>BindNetworkInterface</varname>,
<varname>MemoryTHP</varname>, and
<varname>RefreshOnReload</varname> were added in version 260.</para>
</refsect2>
<refsect2>
<title>Socket Unit Objects</title>

View File

@@ -572,14 +572,13 @@
To disable the safety check that the extension-release file name matches the image file name, the
<varname>x-systemd.relax-extension-release-check</varname> mount option may be appended.</para>
<para>This option can be used together with a <option>notify-reload</option> service type and
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>
to manage configuration updates. When such a service carrying confext images is reloaded via
<command>systemctl reload foo.service</command> or equivalent D-Bus method, the confext itself will
be reloaded to pick up any changes. This only applies to confext extensions. Note that in case a
service has this configuration enabled at first, and then it is subsequently removed in an update
followed by a daemon-reload operation, reloading the confexts will be a no-op, and a full service
restart is required instead. See
<para>If a service employs this option with
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
and has <option>RefreshOnReload=extensions</option> enabled (the default), the confexts will
be refreshed to pick up any changes on service reload. This only applies to confext extensions.
Note that in case a service has this configuration enabled at first, and then it is subsequently
removed in an update followed by a daemon-reload operation, reloading the confexts will be a no-op,
and a full service restart is required instead. See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
also for details.</para>
@@ -630,14 +629,13 @@
or the host. See:
<citerefentry><refentrytitle>os-release</refentrytitle><manvolnum>5</manvolnum></citerefentry>.</para>
<para>This option can be used together with a <option>notify-reload</option> service type and
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>
to manage configuration updates. When such a service carrying confext directories is reloaded via
<command>systemctl reload foo.service</command> or equivalent D-Bus method, the confext itself will
be reloaded to pick up any changes. This only applies to confext extensions. Note that in case a
service has this configuration enabled at first, and then it is subsequently removed in an update
followed by a daemon-reload operation, reloading the confexts will be a no-op, and a full service
restart is required instead. See
<para>If a service employs this option with
<citerefentry><refentrytitle>systemd.v</refentrytitle><manvolnum>7</manvolnum></citerefentry>,
and has <option>RefreshOnReload=extensions</option> enabled (the default), the confexts will
be refreshed to pick up any changes on service reload. This only applies to confext extensions.
Note that in case a service has this configuration enabled at first, and then it is subsequently
removed in an update followed by a daemon-reload operation, reloading the confexts will be a no-op,
and a full service restart is required instead. See
<citerefentry><refentrytitle>systemd.service</refentrytitle><manvolnum>5</manvolnum></citerefentry>
also for details.</para>

View File

@@ -1349,6 +1349,28 @@
<xi:include href="version-info.xml" xpointer="v253"/></listitem>
</varlistentry>
<varlistentry>
<term><varname>RefreshOnReload=</varname></term>
<listitem><para>Takes a boolean argument, or a list of resources defined in
<citerefentry><refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
Possible values are <option>extensions</option> and <option>credentials</option>, separated by space.
Prepending the list with a single tilde character (<literal>~</literal>) inverts the effect.
Defaults to <option>extensions</option>. An empty assignment resets the list to default. If enabled,
the corresponding resources (<option>ExtensionImages=</option>/<option>ExtensionDirectories=</option>
for <option>extensions</option> and <option>LoadCredential=</option>/<option>ImportCredential=</option>/
<option>SetCredential=</option> (along with their <option>Encrypted</option> counterparts)
for <option>credentials</option>) will be refreshed on service reload. If <option>yes</option>,
all resources listed above that are used by the service shall be refreshed.</para>
<para>Specially, if this option is set explicitly, and the respective resources are in use,
the service may be reloaded without any actual reload mechanism (<option>ExecReload=</option>
or <option>Type=notify-reload</option>) for notifying the main process, in which case the reload
is considered complete immediately after refreshing.</para>
<xi:include href="version-info.xml" xpointer="v260"/></listitem>
</varlistentry>
</variablelist>
<para id='shared-unit-options'>Check

View File

@@ -262,6 +262,71 @@ int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int
return 0;
}
static int namespace_enter_one_idempotent(int nsfd, NamespaceType type) {
int r;
/* Join a namespace, but only if we're not part of it already. This is important if we don't necessarily
* own the namespace in question, as kernel would unconditionally return EPERM otherwise. */
assert(nsfd >= 0);
assert(type >= 0 && type < _NAMESPACE_TYPE_MAX);
r = is_our_namespace(nsfd, type);
if (r < 0)
return r;
if (r > 0)
return 0;
if (setns(nsfd, namespace_info[type].clone_flag) < 0)
return -errno;
return 1;
}
int namespace_enter_delegated(int userns_fd, int pidns_fd, int mntns_fd, int netns_fd, int root_fd) {
int r;
/* Similar to namespace_enter(), but operates on a set of namespaces that are potentially owned
* by the userns ("delegated"), in which case we'll need to gain CAP_SYS_ADMIN by joining
* the userns first, and the rest later. */
assert(userns_fd >= 0);
/* Block dlopen() now, to avoid us inadvertently loading shared library from another namespace */
block_dlopen();
if (setns(userns_fd, CLONE_NEWUSER) < 0)
return -errno;
if (pidns_fd >= 0) {
r = namespace_enter_one_idempotent(pidns_fd, NAMESPACE_PID);
if (r < 0)
return r;
}
if (mntns_fd >= 0) {
r = namespace_enter_one_idempotent(mntns_fd, NAMESPACE_MOUNT);
if (r < 0)
return r;
}
if (netns_fd >= 0) {
r = namespace_enter_one_idempotent(netns_fd, NAMESPACE_NET);
if (r < 0)
return r;
}
if (root_fd >= 0) {
if (fchdir(root_fd) < 0)
return -errno;
if (chroot(".") < 0)
return -errno;
}
return maybe_setgroups(/* size = */ 0, NULL);
}
int fd_is_namespace(int fd, NamespaceType type) {
int r;

View File

@@ -47,6 +47,7 @@ int namespace_open(
int *ret_root_fd);
int namespace_enter(int pidns_fd, int mntns_fd, int netns_fd, int userns_fd, int root_fd);
int namespace_enter_delegated(int userns_fd, int pidns_fd, int mntns_fd, int netns_fd, int root_fd);
int fd_is_namespace(int fd, NamespaceType type);
int is_our_namespace(int fd, NamespaceType type);

View File

@@ -1778,6 +1778,7 @@ int namespace_fork_full(
int netns_fd,
int userns_fd,
int root_fd,
bool delegated,
PidRef *ret) {
_cleanup_(pidref_done_sigkill_wait) PidRef pidref_outer = PIDREF_NULL;
@@ -1823,7 +1824,10 @@ int namespace_fork_full(
errno_pipe_fd[0] = safe_close(errno_pipe_fd[0]);
r = namespace_enter(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd);
if (delegated)
r = namespace_enter_delegated(userns_fd, pidns_fd, mntns_fd, netns_fd, root_fd);
else
r = namespace_enter(pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd);
if (r < 0) {
log_full_errno(prio, r, "Failed to join namespace: %m");
report_errno_and_exit(errno_pipe_fd[1], r);

View File

@@ -201,6 +201,7 @@ int namespace_fork_full(
int netns_fd,
int userns_fd,
int root_fd,
bool delegated,
PidRef *ret);
static inline int namespace_fork(
@@ -215,7 +216,7 @@ static inline int namespace_fork(
PidRef *ret) {
return namespace_fork_full(outer_name, inner_name, NULL, 0, flags,
pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd,
pidns_fd, mntns_fd, netns_fd, userns_fd, root_fd, false,
ret);
}

View File

@@ -223,6 +223,7 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = {
[SERVICE_RUNNING] = "running",
[SERVICE_EXITED] = "exited",
[SERVICE_REFRESH_EXTENSIONS] = "refresh-extensions",
[SERVICE_REFRESH_CREDENTIALS] = "refresh-credentials",
[SERVICE_RELOAD] = "reload",
[SERVICE_RELOAD_SIGNAL] = "reload-signal",
[SERVICE_RELOAD_NOTIFY] = "reload-notify",

View File

@@ -132,6 +132,7 @@ typedef enum ServiceState {
SERVICE_RUNNING,
SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */
SERVICE_REFRESH_EXTENSIONS, /* Refreshing extensions for a reload request */
SERVICE_REFRESH_CREDENTIALS, /* ditto, but for credentials */
SERVICE_RELOAD, /* Reloading via ExecReload= */
SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */
SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */

View File

@@ -30,6 +30,7 @@
#include "signal-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "unit.h"
static BUS_DEFINE_PROPERTY_GET_ENUM(property_get_type, service_type, ServiceType);
@@ -100,6 +101,29 @@ static int property_get_extra_file_descriptors(
return sd_bus_message_close_container(reply);
}
static int property_get_refresh_on_reload(
sd_bus *bus,
const char *path,
const char *interface,
const char *property,
sd_bus_message *reply,
void *userdata,
sd_bus_error *reterr_error) {
Service *s = ASSERT_PTR(userdata);
_cleanup_strv_free_ char **l = NULL;
int r;
assert(bus);
assert(reply);
r = service_refresh_on_reload_to_strv(s->refresh_on_reload_flags, &l);
if (r < 0)
return r;
return sd_bus_message_append_strv(reply, l);
}
static int property_get_exit_status_set(
sd_bus *bus,
const char *path,
@@ -373,6 +397,7 @@ const sd_bus_vtable bus_service_vtable[] = {
SD_BUS_PROPERTY("OpenFile", "a(sst)", property_get_open_files, offsetof(Service, open_files), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ExtraFileDescriptorNames", "as", property_get_extra_file_descriptors, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("ReloadSignal", "i", bus_property_get_int, offsetof(Service, reload_signal), SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("RefreshOnReload", "as", property_get_refresh_on_reload, 0, SD_BUS_VTABLE_PROPERTY_CONST),
BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
@@ -840,6 +865,47 @@ static int bus_service_set_transient_property(
return 1;
}
if (streq(name, "RefreshOnReload")) {
const char *t;
int invert;
r = sd_bus_message_enter_container(message, 'a', "(bs)");
if (r < 0)
return r;
while ((r = sd_bus_message_read(message, "(bs)", &invert, &t)) > 0) {
ServiceRefreshOnReload f;
f = service_refresh_on_reload_flag_from_string(t);
if (f < 0)
return sd_bus_error_setf(reterr_error, SD_BUS_ERROR_INVALID_ARGS, "Invalid RefreshOnReload= value: %s", t);
if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
if (!s->refresh_on_reload_set)
s->refresh_on_reload_flags = invert ? (SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~f) : f;
else
SET_FLAG(s->refresh_on_reload_flags, f, !invert);
s->refresh_on_reload_set = true;
unit_write_settingf(u, flags, name, "%s=%s%s", name, invert ? "~" : "", t);
}
}
if (r < 0)
return r;
r = sd_bus_message_exit_container(message);
if (r < 0)
return r;
if (!UNIT_WRITE_FLAGS_NOOP(flags) && !s->refresh_on_reload_set) { /* empty array? */
s->refresh_on_reload_flags = 0;
s->refresh_on_reload_set = true;
unit_write_settingf(u, flags, name, "%s=no", name);
}
return 1;
}
return 0;
}

View File

@@ -16,19 +16,25 @@
#include "iovec-util.h"
#include "label-util.h"
#include "log.h"
#include "manager.h"
#include "mkdir-label.h"
#include "mount-util.h"
#include "mountpoint-util.h"
#include "namespace-util.h"
#include "ordered-set.h"
#include "path-lookup.h"
#include "path-util.h"
#include "pidref.h"
#include "process-util.h"
#include "random-util.h"
#include "recurse-dir.h"
#include "rm-rf.h"
#include "siphash24.h"
#include "socket-util.h"
#include "stat-util.h"
#include "string-util.h"
#include "strv.h"
#include "unit.h"
#include "user-util.h"
ExecSetCredential* exec_set_credential_free(ExecSetCredential *sc) {
@@ -310,6 +316,34 @@ int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_p
return 0;
}
typedef struct SetupCredentialsContext {
RuntimeScope scope;
const ExecContext *exec_context;
const char *unit;
const char *runtime_prefix;
const char *received_credentials_directory;
const char *received_encrypted_credentials_directory;
bool always_ipc;
uid_t uid;
gid_t gid;
} SetupCredentialsContext;
typedef struct LoadCredentialArguments {
const SetupCredentialsContext *context;
bool encrypted;
int write_dfd;
bool ownership_ok;
uint64_t left;
} LoadCredentialArguments;
typedef enum CredentialSearchPath {
CREDENTIAL_SEARCH_PATH_TRUSTED,
CREDENTIAL_SEARCH_PATH_ENCRYPTED,
@@ -318,11 +352,15 @@ typedef enum CredentialSearchPath {
_CREDENTIAL_SEARCH_PATH_INVALID = -EINVAL,
} CredentialSearchPath;
static int credential_search_path(const ExecParameters *params, CredentialSearchPath path, char ***ret) {
static int credential_search_path(
const SetupCredentialsContext *context,
CredentialSearchPath path,
char ***ret) {
_cleanup_strv_free_ char **l = NULL;
int r;
assert(params);
assert(context);
assert(path >= 0 && path < _CREDENTIAL_SEARCH_PATH_MAX);
assert(ret);
@@ -331,12 +369,12 @@ static int credential_search_path(const ExecParameters *params, CredentialSearch
* credentials, we'll look in /etc/credstore.encrypted/ (and similar dirs). */
if (IN_SET(path, CREDENTIAL_SEARCH_PATH_ENCRYPTED, CREDENTIAL_SEARCH_PATH_ALL)) {
r = strv_extend(&l, params->received_encrypted_credentials_directory);
r = strv_extend(&l, context->received_encrypted_credentials_directory);
if (r < 0)
return r;
_cleanup_strv_free_ char **add = NULL;
r = credential_store_path_encrypted(params->runtime_scope, &add);
r = credential_store_path_encrypted(context->scope, &add);
if (r < 0)
return r;
@@ -346,12 +384,12 @@ static int credential_search_path(const ExecParameters *params, CredentialSearch
}
if (IN_SET(path, CREDENTIAL_SEARCH_PATH_TRUSTED, CREDENTIAL_SEARCH_PATH_ALL)) {
r = strv_extend(&l, params->received_credentials_directory);
r = strv_extend(&l, context->received_credentials_directory);
if (r < 0)
return r;
_cleanup_strv_free_ char **add = NULL;
r = credential_store_path(params->runtime_scope, &add);
r = credential_store_path(context->scope, &add);
if (r < 0)
return r;
@@ -369,23 +407,6 @@ static int credential_search_path(const ExecParameters *params, CredentialSearch
return 0;
}
struct load_cred_args {
const ExecContext *context;
const ExecParameters *params;
const char *unit;
bool always_ipc;
bool encrypted;
int write_dfd;
uid_t uid;
gid_t gid;
bool ownership_ok;
uint64_t left;
};
static int write_credential(
int dfd,
const char *id,
@@ -431,7 +452,7 @@ static int write_credential(
}
static int maybe_decrypt_and_write_credential(
struct load_cred_args *args,
LoadCredentialArguments *args,
const char *id,
const char *data,
size_t size,
@@ -449,7 +470,7 @@ static int maybe_decrypt_and_write_credential(
if (args->encrypted) {
CredentialFlags flags = 0; /* only allow user creds in user scope */
switch (args->params->runtime_scope) {
switch (args->context->scope) {
case RUNTIME_SCOPE_SYSTEM:
/* In system mode talk directly to the TPM unless we live in a device sandbox
@@ -457,7 +478,7 @@ static int maybe_decrypt_and_write_credential(
flags |= CREDENTIAL_ANY_SCOPE;
if (!args->always_ipc) {
if (!args->context->always_ipc) {
r = decrypt_credential_and_warn(
id,
now(CLOCK_REALTIME),
@@ -506,7 +527,7 @@ static int maybe_decrypt_and_write_credential(
if (add > args->left)
return -E2BIG;
r = write_credential(args->write_dfd, id, data, size, args->uid, args->gid, args->ownership_ok);
r = write_credential(args->write_dfd, id, data, size, args->context->uid, args->context->gid, args->ownership_ok);
if (r < 0)
return log_debug_errno(r, "Failed to write credential '%s': %m", id);
@@ -516,7 +537,7 @@ static int maybe_decrypt_and_write_credential(
}
static int load_credential_glob(
struct load_cred_args *args,
LoadCredentialArguments *args,
const ExecImportCredential *ic,
char * const *search_path,
ReadFullFileFlags flags) {
@@ -595,7 +616,7 @@ static int load_credential_glob(
}
static int load_credential(
struct load_cred_args *args,
LoadCredentialArguments *args,
const char *id,
int read_dfd,
const char *path) {
@@ -611,9 +632,8 @@ static int load_credential(
assert(args);
assert(args->context);
assert(args->params);
assert(args->unit);
assert(args->write_dfd >= 0);
assert(args->context->exec_context);
assert(args->context->unit);
assert(id);
assert(read_dfd >= 0 || read_dfd == AT_FDCWD);
assert(path);
@@ -641,7 +661,7 @@ static int load_credential(
/* Pass some minimal info about the unit and the credential name we are looking to acquire
* via the source socket address in case we read off an AF_UNIX socket. */
if (asprintf(&bindname, "@%" PRIx64 "/unit/%s/%s", random_u64(), args->unit, id) < 0)
if (asprintf(&bindname, "@%" PRIx64 "/unit/%s/%s", random_u64(), args->context->unit, id) < 0)
return -ENOMEM;
missing_ok = false;
@@ -652,7 +672,7 @@ static int load_credential(
* directory we received ourselves. We don't support the AF_UNIX stuff in this mode, since we
* are operating on a credential store, i.e. this is guaranteed to be regular files. */
r = credential_search_path(args->params, CREDENTIAL_SEARCH_PATH_ALL, &search_path);
r = credential_search_path(args->context, CREDENTIAL_SEARCH_PATH_ALL, &search_path);
if (r < 0)
return r;
@@ -695,17 +715,20 @@ static int load_credential(
else
assert_not_reached();
if (r == -ENOENT && (missing_ok || hashmap_contains(args->context->set_credentials, id))) {
/* Make a missing inherited credential non-fatal, let's just continue. After all apps
* will get clear errors if we don't pass such a missing credential on as they
* themselves will get ENOENT when trying to read them, which should not be much
* worse than when we handle the error here and make it fatal.
*
* Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
* we are fine, too. */
log_full_errno(hashmap_contains(args->context->set_credentials, id) ? LOG_DEBUG : LOG_INFO,
r, "Couldn't read inherited credential '%s', skipping: %m", path);
return 0;
if (r == -ENOENT) {
bool in_set_credentials = hashmap_contains(args->context->exec_context->set_credentials, id);
if (missing_ok || in_set_credentials) {
/* Make a missing inherited credential non-fatal, let's just continue. After all apps
* will get clear errors if we don't pass such a missing credential on as they
* themselves will get ENOENT when trying to read them, which should not be much
* worse than when we handle the error here and make it fatal.
*
* Also, if the source file doesn't exist, but a fallback is set via SetCredentials=
* we are fine, too. */
log_full_errno(in_set_credentials ? LOG_DEBUG : LOG_INFO,
r, "Couldn't read inherited credential '%s', skipping: %m", path);
return 0;
}
}
if (r < 0)
return log_debug_errno(r, "Failed to read credential '%s': %m", path);
@@ -722,7 +745,7 @@ static int load_cred_recurse_dir_cb(
const struct statx *sx,
void *userdata) {
struct load_cred_args *args = ASSERT_PTR(userdata);
LoadCredentialArguments *args = ASSERT_PTR(userdata);
_cleanup_free_ char *sub_id = NULL;
int r;
@@ -779,38 +802,27 @@ static bool device_nodes_restricted(
}
static int acquire_credentials(
const ExecContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
const char *unit,
const SetupCredentialsContext *context,
int dfd,
uid_t uid,
gid_t gid,
bool ownership_ok) {
int r;
assert(context);
assert(cgroup_context);
assert(params);
assert(unit);
assert(context->exec_context);
assert(dfd >= 0);
struct load_cred_args args = {
LoadCredentialArguments args = {
.context = context,
.params = params,
.unit = unit,
.always_ipc = device_nodes_restricted(context, cgroup_context),
.write_dfd = dfd,
.uid = uid,
.gid = gid,
.ownership_ok = ownership_ok,
.left = CREDENTIALS_TOTAL_SIZE_MAX,
};
/* First, load credentials off disk (or acquire via AF_UNIX socket) */
ExecLoadCredential *lc;
HASHMAP_FOREACH(lc, context->load_credentials) {
HASHMAP_FOREACH(lc, context->exec_context->load_credentials) {
_cleanup_close_ int sub_fd = -EBADF;
args.encrypted = lc->encrypted;
@@ -849,10 +861,10 @@ static int acquire_credentials(
/* Next, look for system credentials and credentials in the credentials store. Note that these do not
* override any credentials found earlier. */
ExecImportCredential *ic;
ORDERED_SET_FOREACH(ic, context->import_credentials) {
ORDERED_SET_FOREACH(ic, context->exec_context->import_credentials) {
_cleanup_free_ char **search_path = NULL;
r = credential_search_path(params, CREDENTIAL_SEARCH_PATH_TRUSTED, &search_path);
r = credential_search_path(context, CREDENTIAL_SEARCH_PATH_TRUSTED, &search_path);
if (r < 0)
return r;
@@ -868,7 +880,7 @@ static int acquire_credentials(
search_path = strv_free(search_path);
r = credential_search_path(params, CREDENTIAL_SEARCH_PATH_ENCRYPTED, &search_path);
r = credential_search_path(context, CREDENTIAL_SEARCH_PATH_ENCRYPTED, &search_path);
if (r < 0)
return r;
@@ -886,7 +898,7 @@ static int acquire_credentials(
/* Finally, we add in literally specified credentials. If the credentials already exist, we'll not
* add them, so that they can act as a "default" if the same credential is specified multiple times. */
ExecSetCredential *sc;
HASHMAP_FOREACH(sc, context->set_credentials) {
HASHMAP_FOREACH(sc, context->exec_context->set_credentials) {
args.encrypted = sc->encrypted;
if (faccessat(dfd, sc->id, F_OK, AT_SYMLINK_NOFOLLOW) >= 0) {
@@ -934,13 +946,8 @@ static int credentials_dir_finalize_permissions(int dfd, uid_t uid, gid_t gid, b
}
static int setup_credentials_plain_dir(
const ExecContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
const char *unit,
const char *cred_dir,
uid_t uid,
gid_t gid) {
const SetupCredentialsContext *context,
const char *cred_dir) {
_cleanup_free_ char *t = NULL, *workspace = NULL;
_cleanup_(rm_rf_safep) const char *workspace_rm = NULL;
@@ -948,13 +955,13 @@ static int setup_credentials_plain_dir(
int r;
assert(context);
assert(params);
assert(unit);
assert(context->unit);
assert(context->runtime_prefix);
assert(cred_dir);
/* 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");
t = path_join(context->runtime_prefix, "systemd/temporary-credentials");
if (!t)
return -ENOMEM;
@@ -962,7 +969,7 @@ static int setup_credentials_plain_dir(
if (r < 0 && r != -EEXIST)
return r;
workspace = path_join(t, unit);
workspace = path_join(t, context->unit);
if (!workspace)
return -ENOMEM;
@@ -973,7 +980,7 @@ static int setup_credentials_plain_dir(
(void) label_fix_full(dfd, /* inode_path= */ NULL, cred_dir, /* flags= */ 0);
r = acquire_credentials(context, cgroup_context, params, unit, dfd, uid, gid, /* ownership_ok= */ false);
r = acquire_credentials(context, dfd, /* ownership_ok= */ false);
if (r < 0)
return r;
@@ -995,7 +1002,7 @@ static int setup_credentials_plain_dir(
/* rename() requires both the source and target to be writable, hence lock down write permission
* as last step. */
r = credentials_dir_finalize_permissions(dfd, uid, gid, /* ownership_ok= */ false);
r = credentials_dir_finalize_permissions(dfd, context->uid, context->gid, /* ownership_ok= */ false);
if (r < 0)
return log_debug_errno(r, "Failed to adjust ACLs of credentials dir: %m");
@@ -1003,43 +1010,47 @@ static int setup_credentials_plain_dir(
}
static int setup_credentials_internal(
const ExecContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
const char *unit,
const char *cred_dir,
uid_t uid,
gid_t gid) {
const SetupCredentialsContext *context,
bool may_reuse,
const char *cred_dir) {
_cleanup_close_ int fs_fd = -EBADF, mfd = -EBADF, dfd = -EBADF;
bool dir_mounted;
int r;
assert(context);
assert(params);
assert(unit);
assert(cred_dir);
if (!FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH)) {
/* We may reuse the previous credential dir */
r = dir_is_empty(cred_dir, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return r;
if (r == 0) {
log_debug("Credential dir for unit '%s' already set up, skipping.", unit);
return 0;
}
}
r = path_is_mount_point(cred_dir);
if (r < 0)
return log_debug_errno(r, "Failed to determine if '%s' is a mountpoint: %m", cred_dir);
dir_mounted = r > 0;
if (may_reuse) {
bool populated;
/* If the cred dir is a mount, let's treat it as populated, and only look at the contents
* if it's a plain dir, where we can't reasonably differentiate populated yet empty vs
* not set up. */
if (dir_mounted)
populated = true;
else {
r = dir_is_empty(cred_dir, /* ignore_hidden_or_backup= */ false);
if (r < 0)
return r;
populated = r == 0;
}
if (populated) {
log_debug("Credential dir for unit '%s' already set up, skipping.", context->unit);
return 0;
}
}
mfd = fsmount_credentials_fs(&fs_fd);
if (ERRNO_IS_NEG_PRIVILEGE(mfd) && !dir_mounted) {
log_debug_errno(mfd, "Lacking privilege to mount credentials fs, falling back to plain directory.");
return setup_credentials_plain_dir(context, cgroup_context, params, unit, cred_dir, uid, gid);
return setup_credentials_plain_dir(context, cred_dir);
}
if (mfd < 0)
return log_debug_errno(mfd, "Failed to mount credentials fs: %m");
@@ -1050,11 +1061,11 @@ static int setup_credentials_internal(
(void) label_fix_full(dfd, /* inode_path= */ NULL, cred_dir, /* flags= */ 0);
r = acquire_credentials(context, cgroup_context, params, unit, dfd, uid, gid, /* ownership_ok= */ true);
r = acquire_credentials(context, dfd, /* ownership_ok= */ true);
if (r < 0)
return r;
r = credentials_dir_finalize_permissions(dfd, uid, gid, /* ownership_ok= */ true);
r = credentials_dir_finalize_permissions(dfd, context->uid, context->gid, /* ownership_ok= */ true);
if (r < 0)
return log_debug_errno(r, "Failed to adjust ACLs of credentials dir: %m");
@@ -1099,7 +1110,6 @@ int exec_setup_credentials(
const ExecContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
const char *unit,
uid_t uid,
gid_t gid) {
@@ -1108,7 +1118,6 @@ int exec_setup_credentials(
assert(context);
assert(params);
assert(unit);
if (!exec_params_need_credentials(params) || !exec_context_has_credentials(context))
return 0;
@@ -1126,7 +1135,7 @@ int exec_setup_credentials(
if (r < 0 && r != -EEXIST)
return r;
p = path_join(q, unit);
p = path_join(q, params->unit_id);
if (!p)
return -ENOMEM;
@@ -1134,12 +1143,208 @@ int exec_setup_credentials(
if (r < 0 && r != -EEXIST)
return r;
r = setup_credentials_internal(context, cgroup_context, params, unit, p, uid, gid);
SetupCredentialsContext ctx = {
.scope = params->runtime_scope,
.exec_context = context,
.unit = params->unit_id,
.runtime_prefix = params->prefix[EXEC_DIRECTORY_RUNTIME],
.received_credentials_directory = params->received_credentials_directory,
.received_encrypted_credentials_directory = params->received_encrypted_credentials_directory,
.always_ipc = device_nodes_restricted(context, cgroup_context),
.uid = uid,
.gid = gid,
};
r = setup_credentials_internal(&ctx, /* may_reuse = */ !FLAGS_SET(params->flags, EXEC_SETUP_CREDENTIALS_FRESH), p);
if (r < 0)
(void) rmdir(p);
/* If the credentials dir is empty and not a mount point, then there's no point in having it. Let's
* try to remove it. This matters in particular if we created the dir as mount point but then didn't
* actually end up mounting anything on it. In that case we'd rather have ENOENT than EACCESS being
* seen by users when trying access this inode. */
(void) rmdir(p);
return r;
}
static int refresh_credentials_in_namespace_child(int cfd, const char *cred_dir) {
int r;
assert(cfd >= 0);
assert(cred_dir);
/* Paranoia: before doing anything, check if the credentials tree inside the mountns is available.
*
* Note that setup_namespace() always installs a mount for cred dir, hence path_is_mount_point()
* is the appropriate check here. */
r = path_is_mount_point(cred_dir);
if (IN_SET(r, 0, -ENOENT)) {
log_full_errno_zerook(LOG_WARNING, r,
"Credentials tree in the unit mount namespace is masked, skipping refresh.");
return 0;
}
if (r < 0)
return log_error_errno(r, "Failed to check whether '%s' is a mountpoint in unit mount namespace: %m",
cred_dir);
/* Inform the parent that we're good to go */
ssize_t n = write(cfd, &r, sizeof(r));
if (n < 0)
return log_error_errno(errno, "Failed to write to socket: %m");
_cleanup_close_ int mfd = receive_one_fd(cfd, /* flags = */ 0);
if (mfd < 0)
return log_error_errno(mfd, "Failed to receive credentials tree fd from socket: %m");
r = mount_exchange_graceful(mfd, cred_dir, /* mount_beneath = */ true);
if (r < 0)
return log_error_errno(r, "Failed to update credentials mount in namespace: %m");
return 1;
}
int unit_refresh_credentials(Unit *u) {
_cleanup_free_ char *cred_dir = NULL;
int r;
/* Refresh the credentials for a unit, potentially forking off a second process to join the mountns
* if needed. Returns > 0 on successful refresh, == 0 if the credentials tree is masked and the operation
* is skipped. */
assert(u);
assert(u->manager);
r = get_credential_directory(u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id, &cred_dir);
if (r < 0)
return log_oom();
assert(r > 0);
if (access(cred_dir, F_OK) < 0) {
if (errno == ENOENT) {
log_warning_errno(errno, "Requested to refresh credentials, but credentials aren't populated, skipping.");
return 0;
}
return log_error_errno(errno, "Failed to check if credentials dir '%s' exists: %m", cred_dir);
}
_cleanup_close_pair_ int tunnel_fds[2] = EBADF_PAIR;
_cleanup_(pidref_done) PidRef child = PIDREF_NULL;
_cleanup_close_ int userns_fd = -EBADF;
PidRef *main_pid = unit_main_pid(u);
if (pidref_is_set(main_pid)) {
_cleanup_close_ int mntns_fd = -EBADF, root_fd = -EBADF, pidns_fd = -EBADF;
r = pidref_namespace_open(main_pid,
&pidns_fd,
&mntns_fd,
/* ret_netns_fd = */ NULL,
MANAGER_IS_USER(u->manager) ? &userns_fd : NULL,
&root_fd);
if (r < 0)
return log_error_errno(r, "Failed to open namespace of unit main process '" PID_FMT "': %m",
main_pid->pid);
r = is_our_namespace(mntns_fd, NAMESPACE_MOUNT);
if (r < 0)
return log_error_errno(r, "Failed to check if main process resides in a separate mount namespace: %m");
if (r == 0) {
if (socketpair(AF_UNIX, SOCK_SEQPACKET|SOCK_CLOEXEC, 0, tunnel_fds) < 0)
return log_error_errno(errno, "Failed to allocate socket pair: %m");
r = namespace_fork_full("(sd-creds-ns)", "(sd-creds-ns-inner)",
(int[]) { tunnel_fds[1] }, 1,
FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGKILL|FORK_CLOSE_ALL_FDS|FORK_REOPEN_LOG,
pidns_fd, mntns_fd, /* netns_fd = */ -EBADF, userns_fd, root_fd,
/* delegated = */ MANAGER_IS_USER(u->manager),
&child);
if (r < 0)
return log_full_errno(ERRNO_IS_NEG_PRIVILEGE(r) ? LOG_WARNING : LOG_ERR, r,
"Failed to fork off process into unit namespace to refresh credentials: %m");
if (r == 0) {
r = refresh_credentials_in_namespace_child(tunnel_fds[1], cred_dir);
report_errno_and_exit(tunnel_fds[1], r);
}
tunnel_fds[1] = safe_close(tunnel_fds[1]);
/* Wait for the child to validate the creds tree in the unit namespace is populated. */
ssize_t n = read(tunnel_fds[0], &r, sizeof(r));
if (n < 0)
return log_error_errno(errno, "Failed to read from socket: %m");
if (!IN_SET(n, 0, sizeof(r)))
return log_error_errno(SYNTHETIC_ERRNO(EIO), "Received unexpected amount of bytes (%zi) while reading errno.", n);
if (n == 0 || r == 0) {
/* The child exited without sending anything or 0 is received signifying
* the credentials are masked? Check the exit status to be sure. */
r = pidref_wait_for_terminate_and_check("(sd-creds-ns)", &child, WAIT_LOG);
if (r < 0)
return r;
if (r != EXIT_SUCCESS)
return -EPROTO;
return 0; /* skipped */
}
if (r < 0)
return r;
/* Yay! Got > 0 from child indicating all good, proceed with refreshing. */
}
}
SetupCredentialsContext ctx = {
.scope = u->manager->runtime_scope,
.exec_context = ASSERT_PTR(unit_get_exec_context(u)),
.unit = u->id,
.runtime_prefix = u->manager->prefix[EXEC_DIRECTORY_RUNTIME],
.received_credentials_directory = u->manager->received_credentials_directory,
.received_encrypted_credentials_directory = u->manager->received_encrypted_credentials_directory,
.always_ipc = false, /* we don't migrate to unit cgroup, hence cannot be restricted by cgroup bpf */
.uid = u->ref_uid,
.gid = u->ref_gid,
};
r = setup_credentials_internal(&ctx, /* may_reuse = */ false, cred_dir);
if (r < 0)
return log_error_errno(r, "Failed to set up credentials: %m");
/* The main process doesn't run in a mountns hence nothing got forked off? Then we're all set. */
if (!pidref_is_set(&child))
return 1;
if (userns_fd >= 0) {
assert(MANAGER_IS_USER(u->manager));
/* Enter the unit userns now and unshare mountns, so that we have permissions to clone
* the mount tree using open_tree() */
if (setns(userns_fd, CLONE_NEWUSER) < 0)
return log_error_errno(errno, "Failed to enter user namespace: %m");
if (unshare(CLONE_NEWNS) < 0)
return log_error_errno(errno, "Failed to unshare mount namespace: %m");
}
_cleanup_close_ int tfd = open_tree(AT_FDCWD, cred_dir, OPEN_TREE_CLONE|OPEN_TREE_CLOEXEC|AT_SYMLINK_NOFOLLOW);
if (tfd < 0)
return log_error_errno(errno, "Failed to clone mount tree at '%s': %m", cred_dir);
r = send_one_fd(tunnel_fds[0], tfd, /* flags = */ 0);
if (r < 0)
return log_error_errno(r, "Failed to send mount fd to child: %m");
r = pidref_wait_for_terminate_and_check("(sd-creds-ns)", &child, WAIT_LOG_ABNORMAL);
if (r < 0)
return r;
if (r != EXIT_SUCCESS) {
r = read_errno(tunnel_fds[0]);
if (r < 0)
return r;
return -EPROTO;
}
return 1;
}

View File

@@ -57,8 +57,9 @@ int exec_setup_credentials(
const ExecContext *context,
const CGroupContext *cgroup_context,
const ExecParameters *params,
const char *unit,
uid_t uid,
gid_t gid);
int unit_refresh_credentials(Unit *u);
bool mount_point_is_credentials(const char *runtime_prefix, const char *path);

View File

@@ -5705,7 +5705,7 @@ int exec_invoke(
return log_error_errno(r, "Failed to set up special execution directory in %s: %m", params->prefix[dt]);
}
r = exec_setup_credentials(context, cgroup_context, params, params->unit_id, uid, gid);
r = exec_setup_credentials(context, cgroup_context, params, uid, gid);
if (r < 0) {
*exit_status = EXIT_CREDENTIALS;
return log_error_errno(r, "Failed to set up credentials: %m");

View File

@@ -479,6 +479,7 @@ Service.USBFunctionStrings, config_parse_unit_path_printf,
Service.OOMPolicy, config_parse_oom_policy, 0, offsetof(Service, oom_policy)
Service.OpenFile, config_parse_open_file, 0, offsetof(Service, open_files)
Service.ReloadSignal, config_parse_signal, 0, offsetof(Service, reload_signal)
Service.RefreshOnReload, config_parse_service_refresh_on_reload, 0, 0
{{ EXEC_CONTEXT_CONFIG_ITEMS('Service') }}
{{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }}
{{ KILL_CONTEXT_CONFIG_ITEMS('Service') }}

View File

@@ -4816,6 +4816,55 @@ int config_parse_import_credential(
return 0;
}
int config_parse_service_refresh_on_reload(
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) {
Service *s = ASSERT_PTR(userdata);
int r;
if (isempty(rvalue)) {
s->refresh_on_reload_set = false;
return 0;
}
r = parse_boolean(rvalue);
if (r >= 0) {
s->refresh_on_reload_flags = r > 0 ? _SERVICE_REFRESH_ON_RELOAD_ALL : 0;
s->refresh_on_reload_set = true;
return 0;
}
ServiceRefreshOnReload f;
bool invert = false;
if (rvalue[0] == '~') {
invert = true;
rvalue++;
}
r = service_refresh_on_reload_from_string_many(rvalue, &f);
if (r < 0)
return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
/* If the first entry is negated, mask off from default; otherwise assign "positive" values directly */
if (!s->refresh_on_reload_set)
s->refresh_on_reload_flags = invert ? (SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~f) : f;
else
SET_FLAG(s->refresh_on_reload_flags, f, !invert);
s->refresh_on_reload_set = true;
return 0;
}
int config_parse_set_status(
const char *unit,
const char *filename,

View File

@@ -105,6 +105,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_exec_quota);
CONFIG_PARSER_PROTOTYPE(config_parse_set_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_load_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_import_credential);
CONFIG_PARSER_PROTOTYPE(config_parse_service_refresh_on_reload);
CONFIG_PARSER_PROTOTYPE(config_parse_set_status);
CONFIG_PARSER_PROTOTYPE(config_parse_namespace_path_strv);
CONFIG_PARSER_PROTOTYPE(config_parse_temporary_filesystems);

View File

@@ -1888,13 +1888,15 @@ static bool manager_dbus_is_running(Manager *m, bool deserialized) {
u = manager_get_unit(m, SPECIAL_DBUS_SERVICE);
if (!u)
return false;
if (!IN_SET((deserialized ? SERVICE(u)->deserialized_state : SERVICE(u)->state),
if (!IN_SET(deserialized ? SERVICE(u)->deserialized_state : SERVICE(u)->state,
SERVICE_RUNNING,
SERVICE_MOUNTING,
SERVICE_RELOAD,
SERVICE_RELOAD_NOTIFY,
SERVICE_REFRESH_EXTENSIONS,
SERVICE_RELOAD_SIGNAL))
SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY,
SERVICE_RELOAD_POST,
SERVICE_MOUNTING))
return false;
return true;

View File

@@ -2845,7 +2845,6 @@ int setup_namespace(const NamespaceParameters *p, char **reterr_path) {
.mode = MOUNT_BIND,
.read_only = true,
.source_const = p->creds_path,
.ignore = true,
};
}

View File

@@ -69,6 +69,7 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = {
[SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_REFRESH_CREDENTIALS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING,
[SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING,
@@ -102,6 +103,7 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] =
[SERVICE_RUNNING] = UNIT_ACTIVE,
[SERVICE_EXITED] = UNIT_ACTIVE,
[SERVICE_REFRESH_EXTENSIONS] = UNIT_REFRESHING,
[SERVICE_REFRESH_CREDENTIALS] = UNIT_REFRESHING,
[SERVICE_RELOAD] = UNIT_RELOADING,
[SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING,
[SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING,
@@ -134,11 +136,14 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f
static void service_reload_finish(Service *s, ServiceResult f);
static void service_enter_reload_by_notify(Service *s);
static bool service_can_reload_extensions(Service *s, bool warn);
static bool SERVICE_STATE_WITH_MAIN_PROCESS(ServiceState state) {
return IN_SET(state,
SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL);
@@ -148,7 +153,7 @@ static bool SERVICE_STATE_WITH_CONTROL_PROCESS(ServiceState state) {
return IN_SET(state,
SERVICE_CONDITION,
SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_POST,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS, SERVICE_RELOAD, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
@@ -159,7 +164,8 @@ static bool SERVICE_STATE_WITH_WATCHDOG(ServiceState state) {
return IN_SET(state,
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING);
}
@@ -754,6 +760,15 @@ static int service_verify(Service *s) {
s->restart_usec = s->restart_max_delay_usec;
}
if (s->refresh_on_reload_set && s->refresh_on_reload_flags != _SERVICE_REFRESH_ON_RELOAD_ALL) {
if (FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_EXTENSIONS))
service_can_reload_extensions(s, /* warn = */ true);
if (FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_CREDENTIALS) &&
!exec_context_has_credentials(&s->exec_context))
log_unit_warning(UNIT(s), "Service has RefreshOnReload=credentials, but no credentials are in use. The credentials tree will be masked which blocks further refreshing. Continuing.");
}
return 0;
}
@@ -891,6 +906,11 @@ static int service_add_extras(Service *s) {
(IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) || s->watchdog_usec > 0 || s->n_fd_store_max > 0))
s->notify_access = NOTIFY_MAIN;
if (!s->refresh_on_reload_set) {
assert_cc(SERVICE_REFRESH_ON_RELOAD_DEFAULT == SERVICE_RELOAD_EXTENSIONS);
s->refresh_on_reload_flags = service_can_reload_extensions(s, /* warn = */ false) ? SERVICE_RELOAD_EXTENSIONS : 0;
}
/* If no OOM policy was explicitly set, then default to the configure default OOM policy. Except when
* delegation is on, in that case it we assume the payload knows better what to do and can process
* things in a more focused way. */
@@ -1296,7 +1316,8 @@ static void service_set_state(Service *s, ServiceState state) {
if (!IN_SET(state,
SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_MOUNTING,
SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST,
SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL,
@@ -1367,6 +1388,7 @@ static usec_t service_coldplug_timeout(Service *s) {
case SERVICE_START:
case SERVICE_START_POST:
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_REFRESH_CREDENTIALS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
@@ -1667,11 +1689,16 @@ static Service *service_get_triggering_service(Service *s) {
return NULL;
}
static ExecFlags service_exec_flags(ServiceExecCommand command_id, ExecFlags cred_flag) {
static ExecFlags service_exec_flags(
const Service *s,
ServiceExecCommand command_id,
ExecFlags cred_flag) {
/* All service main/control processes honor sandboxing and namespacing options (except those
explicitly excluded in service_spawn()) */
ExecFlags flags = EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT;
assert(s);
assert(command_id >= 0);
assert(command_id < _SERVICE_EXEC_COMMAND_MAX);
assert((cred_flag & ~(EXEC_SETUP_CREDENTIALS_FRESH|EXEC_SETUP_CREDENTIALS)) == 0);
@@ -1703,6 +1730,12 @@ static ExecFlags service_exec_flags(ServiceExecCommand command_id, ExecFlags cre
if (!IN_SET(command_id, SERVICE_EXEC_CONDITION, SERVICE_EXEC_START_PRE))
flags |= EXEC_CONTROL_CGROUP;
/* Pass credentials to ExecReload*= too, but only if the credentials are actually refreshed,
* to make sure they have the same understanding of the world as the main process. */
if (IN_SET(command_id, SERVICE_EXEC_RELOAD, SERVICE_EXEC_RELOAD_POST) &&
FLAGS_SET(s->refreshed_mask, SERVICE_RELOAD_CREDENTIALS))
flags |= EXEC_SETUP_CREDENTIALS;
if (IN_SET(command_id, SERVICE_EXEC_STOP, SERVICE_EXEC_STOP_POST))
flags |= EXEC_SETENV_RESULT;
@@ -2228,7 +2261,7 @@ static void service_enter_stop_post(Service *s, ServiceResult f) {
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0),
service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_stop_usec,
&s->control_pid);
if (r < 0) {
@@ -2340,7 +2373,7 @@ static void service_enter_stop(Service *s, ServiceResult f) {
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0),
service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_stop_usec,
&s->control_pid);
if (r < 0) {
@@ -2425,7 +2458,7 @@ static void service_enter_start_post(Service *s) {
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0),
service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec,
&s->control_pid);
if (r < 0) {
@@ -2535,7 +2568,7 @@ static void service_enter_start(Service *s) {
r = service_spawn(s,
c,
service_exec_flags(SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS_FRESH),
service_exec_flags(s, SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS_FRESH),
timeout,
&pidref);
if (r < 0) {
@@ -2595,7 +2628,7 @@ static void service_enter_start_pre(Service *s) {
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0),
service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec,
&s->control_pid);
if (r < 0) {
@@ -2631,7 +2664,7 @@ static void service_enter_condition(Service *s) {
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0),
service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec,
&s->control_pid);
if (r < 0) {
@@ -2735,7 +2768,7 @@ static void service_enter_reload_post(Service *s) {
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0),
service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec,
&s->control_pid);
if (r < 0) {
@@ -2810,7 +2843,7 @@ static void service_enter_reload(Service *s) {
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0),
service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
s->timeout_start_usec,
&s->control_pid);
if (r < 0) {
@@ -2823,28 +2856,114 @@ static void service_enter_reload(Service *s) {
service_enter_reload_signal(s);
}
static bool service_should_reload_extensions(Service *s) {
static bool service_get_effective_reload_credentials(Service *s) {
assert(s);
return FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_CREDENTIALS) &&
exec_context_has_credentials(&s->exec_context);
}
static void service_enter_refresh_credentials(Service *s) {
_cleanup_(pidref_done) PidRef worker = PIDREF_NULL;
int r;
assert(s);
if (!pidref_is_set(&s->main_pid)) {
log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID.");
return false;
if (!service_get_effective_reload_credentials(s))
return service_enter_reload(s);
service_unwatch_control_pid(s);
s->control_command = NULL;
s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID;
r = service_arm_timer(s, /* relative = */ true, s->timeout_start_usec);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to install timer: %m");
goto fail;
}
r = exec_context_has_vpicked_extensions(&s->exec_context);
if (r < 0)
log_unit_warning_errno(UNIT(s), r, "Failed to determine if service should reload extensions, assuming false: %m");
if (r == 0)
log_unit_debug(UNIT(s), "Service has no extensions to reload.");
if (r <= 0)
return false;
r = unit_fork_helper_process_full(UNIT(s), "(sd-refresh-creds)", /* into_cgroup = */ false,
FORK_ALLOW_DLOPEN, /* allow loading libacl to avoid doing so in pid1 */
&worker);
if (r < 0) {
log_unit_error_errno(UNIT(s), r, "Failed to fork process to refresh credentials in unit's namespace: %m");
goto fail;
}
if (r == 0) {
LOG_CONTEXT_PUSH_UNIT(UNIT(s));
r = unit_refresh_credentials(UNIT(s));
if (ERRNO_IS_NEG_PRIVILEGE(r))
_exit(EXIT_NOPERMISSION);
if (r < 0)
_exit(EXIT_FAILURE);
if (r == 0)
_exit(EXIT_NOTINSTALLED);
_exit(EXIT_SUCCESS);
}
r = unit_watch_pidref(UNIT(s), &worker, /* exclusive = */ true);
if (r < 0) {
log_unit_warning_errno(UNIT(s), r, "Failed to watch credentials refresh helper process: %m");
goto fail;
}
s->control_pid = TAKE_PIDREF(worker);
service_set_state(s, SERVICE_REFRESH_CREDENTIALS);
return;
fail:
service_reload_finish(s, SERVICE_FAILURE_RESOURCES);
}
static bool service_can_reload_extensions(Service *s, bool warn) {
assert(s);
// TODO: Add support for user services, which can use ExtensionDirectories= + notify-reload.
// For now, skip for user services.
if (exec_context_has_vpicked_extensions(&s->exec_context) <= 0) {
if (warn)
log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, but has no extensions using vpick. Ignoring.");
return false;
}
if (!s->exec_command[SERVICE_EXEC_START]) {
if (warn)
log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, but has no main process (ExecStart=). Ignoring.");
return false;
}
if (!MANAGER_IS_SYSTEM(UNIT(s)->manager)) {
log_once(LOG_WARNING, "Not reloading extensions for user services.");
if (warn)
log_unit_warning(UNIT(s), "Service uses RefreshOnReload=extensions, which is not supported in user mode. Ignoring.");
return false;
}
return true;
}
static bool service_get_effective_reload_extensions(Service *s) {
assert(s);
if (!FLAGS_SET(s->refresh_on_reload_flags, SERVICE_RELOAD_EXTENSIONS))
return false;
if (!service_can_reload_extensions(s, /* warn = */ false))
return false;
return true;
}
static bool service_should_reload_extensions(Service *s) {
assert(s);
if (!service_get_effective_reload_extensions(s))
return false;
if (!pidref_is_set(&s->main_pid)) {
log_unit_debug(UNIT(s), "Not reloading extensions for service without main PID.");
return false;
}
@@ -2857,9 +2976,9 @@ static void service_enter_refresh_extensions(Service *s) {
assert(s);
/* If we don't have extensions to refresh, immediately transition to reload state */
/* If we don't have extensions to refresh, immediately transition to next state */
if (!service_should_reload_extensions(s))
return service_enter_reload(s);
return service_enter_refresh_credentials(s);
service_unwatch_control_pid(s);
s->control_command = NULL;
@@ -2955,7 +3074,7 @@ static void service_run_next_control(Service *s) {
r = service_spawn(s,
s->control_command,
service_exec_flags(s->control_command_id, /* cred_flag= */ 0),
service_exec_flags(s, s->control_command_id, /* cred_flag = */ 0),
timeout,
&s->control_pid);
if (r < 0) {
@@ -2986,7 +3105,7 @@ static void service_run_next_main(Service *s) {
r = service_spawn(s,
s->main_command,
service_exec_flags(SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS),
service_exec_flags(s, SERVICE_EXEC_START, EXEC_SETUP_CREDENTIALS),
s->timeout_start_usec,
&pidref);
if (r < 0) {
@@ -3124,6 +3243,7 @@ static int service_stop(Unit *u) {
service_live_mount_finish(s, SERVICE_FAILURE_PROTOCOL, BUS_ERROR_UNIT_INACTIVE);
_fallthrough_;
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_REFRESH_CREDENTIALS:
service_kill_control_process(s);
_fallthrough_;
case SERVICE_CONDITION:
@@ -3166,6 +3286,7 @@ static int service_reload(Unit *u) {
assert(IN_SET(s->state, SERVICE_RUNNING, SERVICE_EXITED));
s->reload_result = SERVICE_SUCCESS;
s->refreshed_mask = 0;
service_enter_refresh_extensions(s);
@@ -3176,7 +3297,10 @@ static bool service_can_reload(Unit *u) {
Service *s = ASSERT_PTR(SERVICE(u));
return s->exec_command[SERVICE_EXEC_RELOAD] ||
s->type == SERVICE_NOTIFY_RELOAD;
s->type == SERVICE_NOTIFY_RELOAD ||
(s->refresh_on_reload_set &&
(service_get_effective_reload_extensions(s) ||
service_get_effective_reload_credentials(s)));
}
static unsigned service_exec_command_index(Unit *u, ServiceExecCommand id, const ExecCommand *current) {
@@ -3386,6 +3510,21 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) {
(void) serialize_usec(f, "reload-begin-usec", s->reload_begin_usec);
if (s->refreshed_mask > 0) {
_cleanup_strv_free_ char **l = NULL;
_cleanup_free_ char *t = NULL;
r = service_refresh_on_reload_to_strv(s->refreshed_mask, &l);
if (r < 0)
return log_oom();
t = strv_join(l, " ");
if (!t)
return log_oom();
(void) serialize_item(f, "refreshed-mask", t);
}
return 0;
}
@@ -3775,7 +3914,11 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value,
} else if (streq(key, "reload-begin-usec"))
(void) deserialize_usec(value, &s->reload_begin_usec);
else
else if (streq(key, "refreshed-mask")) {
r = service_refresh_on_reload_from_string_many(value, &s->refreshed_mask);
if (r < 0)
log_unit_debug_errno(u, r, "Failed to parse refresh-mask value: %s", value);
} else
log_unit_debug(u, "Unknown serialization key: %s", key);
return 0;
@@ -4188,6 +4331,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
case SERVICE_START_POST:
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_REFRESH_CREDENTIALS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
@@ -4307,7 +4451,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
success,
code, status);
if (!IN_SET(s->state, SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_POST, SERVICE_MOUNTING) &&
if (!IN_SET(s->state, SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS, SERVICE_RELOAD, SERVICE_RELOAD_POST, SERVICE_MOUNTING) &&
s->result == SERVICE_SUCCESS)
s->result = f;
@@ -4396,10 +4540,21 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) {
break;
case SERVICE_REFRESH_EXTENSIONS:
if (f == SERVICE_SUCCESS)
/* Remounting extensions asynchronously done, proceed to reload */
if (f == SERVICE_SUCCESS) {
s->refreshed_mask |= SERVICE_RELOAD_EXTENSIONS;
service_enter_refresh_credentials(s);
} else
service_reload_finish(s, f);
break;
case SERVICE_REFRESH_CREDENTIALS:
if (f == SERVICE_SUCCESS ||
(f == SERVICE_FAILURE_EXIT_CODE && IN_SET(status, EXIT_NOTINSTALLED, EXIT_NOPERMISSION))) {
/* Refreshing asynchronously done, proceed to reload */
s->refreshed_mask |= f == SERVICE_SUCCESS ? SERVICE_RELOAD_CREDENTIALS : 0;
service_enter_reload(s);
else
} else
service_reload_finish(s, f);
break;
@@ -4515,6 +4670,7 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us
break;
case SERVICE_REFRESH_EXTENSIONS:
case SERVICE_REFRESH_CREDENTIALS:
case SERVICE_RELOAD:
case SERVICE_RELOAD_SIGNAL:
case SERVICE_RELOAD_NOTIFY:
@@ -4838,7 +4994,9 @@ static void service_notify_message_process_state(Service *s, char * const *tags)
if (strv_contains(tags, "STOPPING=1")) {
s->notify_state = NOTIFY_STOPPING;
if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_REFRESH_EXTENSIONS))
if (IN_SET(s->state, SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY))
service_enter_stop_by_notify(s);
return;
@@ -4935,7 +5093,8 @@ static void service_notify_message(
r = service_notify_message_parse_new_pid(u, tags, fds, &new_main_pid);
if (r > 0 &&
IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_REFRESH_EXTENSIONS, SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_RELOAD_POST,
SERVICE_STOP, SERVICE_STOP_SIGTERM) &&
(!s->main_pid_known || !pidref_equal(&new_main_pid, &s->main_pid))) {
@@ -5204,6 +5363,7 @@ static bool pick_up_pid_from_bus_name(Service *s) {
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS,
SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY,
@@ -5390,6 +5550,7 @@ static bool service_needs_console(Unit *u) {
SERVICE_START_POST,
SERVICE_RUNNING,
SERVICE_REFRESH_EXTENSIONS,
SERVICE_REFRESH_CREDENTIALS,
SERVICE_RELOAD,
SERVICE_RELOAD_SIGNAL,
SERVICE_RELOAD_NOTIFY,
@@ -5888,6 +6049,73 @@ static const char* const service_timeout_failure_mode_table[_SERVICE_TIMEOUT_FAI
DEFINE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode);
static const struct {
ServiceRefreshOnReload flag;
const char *name;
} service_refresh_on_reload_table[] = {
{ SERVICE_RELOAD_EXTENSIONS, "extensions" },
{ SERVICE_RELOAD_CREDENTIALS, "credentials" },
};
ServiceRefreshOnReload service_refresh_on_reload_flag_from_string(const char *s) {
assert(s);
FOREACH_ELEMENT(i, service_refresh_on_reload_table)
if (streq(s, i->name))
return i->flag;
return _SERVICE_REFRESH_ON_RELOAD_INVALID;
}
int service_refresh_on_reload_from_string_many(const char *s, ServiceRefreshOnReload *ret) {
ServiceRefreshOnReload flags = 0;
int r;
assert(s);
assert(ret);
for (;;) {
_cleanup_free_ char *v = NULL;
ServiceRefreshOnReload f;
r = extract_first_word(&s, &v, NULL, 0);
if (r < 0)
return r;
if (r == 0)
break;
f = service_refresh_on_reload_flag_from_string(v);
if (f < 0)
return f;
assert(f > 0);
flags |= f;
}
*ret = flags;
return 0;
}
int service_refresh_on_reload_to_strv(ServiceRefreshOnReload flags, char ***ret) {
_cleanup_strv_free_ char **l = NULL;
int r;
assert(flags >= 0);
assert(ret);
FOREACH_ELEMENT(i, service_refresh_on_reload_table) {
if (!FLAGS_SET(flags, i->flag))
continue;
r = strv_extend(&l, i->name);
if (r < 0)
return r;
}
*ret = TAKE_PTR(l);
return 0;
}
const UnitVTable service_vtable = {
.object_size = sizeof(Service),
.exec_context_offset = offsetof(Service, exec_context),

View File

@@ -96,6 +96,15 @@ typedef enum ServiceRestartMode {
_SERVICE_RESTART_MODE_INVALID = -EINVAL,
} ServiceRestartMode;
typedef enum ServiceRefreshOnReload {
SERVICE_RELOAD_EXTENSIONS = 1 << 0,
SERVICE_RELOAD_CREDENTIALS = 1 << 1,
_SERVICE_REFRESH_ON_RELOAD_ALL = (1 << 2) - 1,
_SERVICE_REFRESH_ON_RELOAD_INVALID = -EINVAL,
} ServiceRefreshOnReload;
#define SERVICE_REFRESH_ON_RELOAD_DEFAULT SERVICE_RELOAD_EXTENSIONS
typedef struct ServiceFDStore {
Service *service;
@@ -237,6 +246,10 @@ typedef struct Service {
int reload_signal;
usec_t reload_begin_usec;
bool refresh_on_reload_set;
ServiceRefreshOnReload refresh_on_reload_flags;
ServiceRefreshOnReload refreshed_mask;
OOMPolicy oom_policy;
char *usb_function_descriptors;
@@ -288,6 +301,10 @@ DECLARE_STRING_TABLE_LOOKUP(service_result, ServiceResult);
DECLARE_STRING_TABLE_LOOKUP(service_timeout_failure_mode, ServiceTimeoutFailureMode);
ServiceRefreshOnReload service_refresh_on_reload_flag_from_string(const char *s) _pure_;
int service_refresh_on_reload_from_string_many(const char *s, ServiceRefreshOnReload *ret);
int service_refresh_on_reload_to_strv(ServiceRefreshOnReload flags, char ***ret);
DEFINE_CAST(SERVICE, Service);
/* Only exported for unit tests */

View File

@@ -351,10 +351,11 @@ assert_cc(sizeof(long long) == sizeof(intmax_t));
#define CASE_F_19(X, ...) case X: CASE_F_18( __VA_ARGS__)
#define CASE_F_20(X, ...) case X: CASE_F_19( __VA_ARGS__)
#define CASE_F_21(X, ...) case X: CASE_F_20( __VA_ARGS__)
#define CASE_F_22(X, ...) case X: CASE_F_21( __VA_ARGS__)
#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,NAME,...) NAME
#define GET_CASE_F(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,NAME,...) NAME
#define FOR_EACH_MAKE_CASE(...) \
GET_CASE_F(__VA_ARGS__,CASE_F_21,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12, \
GET_CASE_F(__VA_ARGS__,CASE_F_22,CASE_F_21,CASE_F_20,CASE_F_19,CASE_F_18,CASE_F_17,CASE_F_16,CASE_F_15,CASE_F_14,CASE_F_13,CASE_F_12, \
CASE_F_11,CASE_F_10,CASE_F_9,CASE_F_8,CASE_F_7,CASE_F_6,CASE_F_5,CASE_F_4,CASE_F_3,CASE_F_2,CASE_F_1) \
(__VA_ARGS__)
@@ -364,7 +365,7 @@ assert_cc(sizeof(long long) == sizeof(intmax_t));
/* If the build breaks in the line below, you need to extend the case macros. We use typeof(+x) \
* here to widen the type of x if it is a bit-field as this would otherwise be illegal. */ \
static const typeof(+x) __assert_in_set[] _unused_ = { first, __VA_ARGS__ }; \
assert_cc(ELEMENTSOF(__assert_in_set) <= 21); \
assert_cc(ELEMENTSOF(__assert_in_set) <= 22); \
switch (x) { \
FOR_EACH_MAKE_CASE(first, __VA_ARGS__) \
_found = true; \

View File

@@ -13,6 +13,7 @@
#include "alloc-util.h"
#include "errno-util.h"
#include "escape.h"
#include "extract-word.h"
#include "fd-util.h"
#include "fs-util.h"
@@ -633,7 +634,11 @@ static int pid_notify_with_fds_internal(
return log_debug_errno(SYNTHETIC_ERRNO(EPROTO), "Unexpectedly received data on notify socket.");
}
log_debug("Notify message sent to '%s': \"%s\"", e, state);
if (DEBUG_LOGGING) {
_cleanup_free_ char *escaped = xescape_full(state, "\"", /* console_width = */ SIZE_MAX, XESCAPE_8_BIT);
log_debug("Notify message sent to '%s': \"%s\"", e, escaped ?: state);
}
return 1;
}

View File

@@ -1172,6 +1172,56 @@ static int bus_append_import_credential(sd_bus_message *m, const char *field, co
return 1;
}
static int bus_append_refresh_on_reload(sd_bus_message *m, const char *field, const char *eq) {
int r;
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', field);
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'v', "a(bs)");
if (r < 0)
return bus_log_create_error(r);
r = sd_bus_message_open_container(m, 'a', "(bs)");
if (r < 0)
return bus_log_create_error(r);
bool invert = *eq == '~';
for (const char *p = eq + invert;;) {
_cleanup_free_ char *word = NULL;
r = extract_first_word(&p, &word, NULL, 0);
if (r < 0)
return parse_log_error(r, field, eq);
if (r == 0)
break;
r = sd_bus_message_append(m, "(bs)", invert, word);
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;
}
static int bus_append_log_extra_fields(sd_bus_message *m, const char *field, const char *eq) {
int r;
@@ -2665,6 +2715,7 @@ static const BusProperty service_properties[] = {
{ "SuccessExitStatus", bus_append_exit_status },
{ "OpenFile", bus_append_open_file },
{ "ReloadSignal", bus_append_signal_from_string },
{ "RefreshOnReload", bus_append_refresh_on_reload },
{}
};

View File

@@ -45,8 +45,8 @@ typedef enum UserRecordLoadFlags UserRecordLoadFlags;
typedef enum UserStorage UserStorage;
typedef struct Bitmap Bitmap;
typedef struct BPFProgram BPFProgram;
typedef struct BootConfig BootConfig;
typedef struct BPFProgram BPFProgram;
typedef struct BusObjectImplementation BusObjectImplementation;
typedef struct CalendarSpec CalendarSpec;
typedef struct Condition Condition;

View File

@@ -21,6 +21,7 @@
#include "open-file.h"
#include "pcre2-util.h"
#include "rm-rf.h"
#include "service.h"
#include "set.h"
#include "specifier.h"
#include "string-util.h"
@@ -1033,6 +1034,86 @@ TEST(config_parse_open_file) {
ASSERT_NULL(of);
}
TEST(config_parse_service_refresh_on_reload) {
ServiceRefreshOnReload flags;
_cleanup_strv_free_ char **l = NULL;
int r;
r = service_refresh_on_reload_from_string_many("extensions", &flags);
ASSERT_OK(r);
ASSERT_EQ(flags, SERVICE_RELOAD_EXTENSIONS);
ASSERT_OK(service_refresh_on_reload_to_strv(flags, &l));
ASSERT_TRUE(strv_equal(l, STRV_MAKE("extensions")));
l = strv_free(l);
r = service_refresh_on_reload_from_string_many("credentials extensions", &flags);
ASSERT_OK(r);
ASSERT_EQ(flags, SERVICE_RELOAD_EXTENSIONS|SERVICE_RELOAD_CREDENTIALS);
ASSERT_OK(service_refresh_on_reload_to_strv(flags, &l));
ASSERT_TRUE(strv_equal(l, STRV_MAKE("extensions", "credentials")));
ASSERT_ERROR(service_refresh_on_reload_from_string_many("hoge", &flags), EINVAL);
_cleanup_(manager_freep) Manager *m = NULL;
_cleanup_(unit_freep) Unit *u = NULL;
r = manager_new(RUNTIME_SCOPE_USER, MANAGER_TEST_RUN_MINIMAL, &m);
if (manager_errno_skip_test(r)) {
log_notice_errno(r, "Skipping test: manager_new: %m");
return;
}
ASSERT_OK(r);
ASSERT_OK(manager_startup(m, NULL, NULL, NULL));
ASSERT_NOT_NULL(u = unit_new(m, sizeof(Service)));
ASSERT_OK_ZERO(unit_add_name(u, "foobar.service"));
ASSERT_FALSE(SERVICE(u)->refresh_on_reload_set);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "no",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, 0);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "yes",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, _SERVICE_REFRESH_ON_RELOAD_ALL);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "~extensions",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, _SERVICE_REFRESH_ON_RELOAD_ALL & ~SERVICE_RELOAD_EXTENSIONS);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "~extensions credentials",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, 0);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "",
NULL, u));
ASSERT_FALSE(SERVICE(u)->refresh_on_reload_set);
ASSERT_OK(config_parse_service_refresh_on_reload(
NULL, "fake", 1, "section", 1,
"RefreshOnReload", 0, "~extensions",
NULL, u));
ASSERT_TRUE(SERVICE(u)->refresh_on_reload_set);
ASSERT_EQ(SERVICE(u)->refresh_on_reload_flags, SERVICE_REFRESH_ON_RELOAD_DEFAULT & ~SERVICE_RELOAD_EXTENSIONS);
}
static int intro(void) {
if (enter_cgroup_subroot(NULL) == -ENOMEDIUM)
return log_tests_skipped("cgroupfs not available");

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: LGPL-2.1-or-later
# shellcheck disable=SC2016
set -eux
set -o pipefail
OUTPUT_FILE="$1"
dump_creds_tree() {
grep . "$CREDENTIALS_DIRECTORY"/* >"$OUTPUT_FILE"
}
on_sighup() {
systemd-notify --reloading
dump_creds_tree
systemd-notify --ready
}
trap on_sighup SIGHUP
export SYSTEMD_LOG_LEVEL=debug
dump_creds_tree
systemd-notify --ready
sleep infinity &
while :; do
wait || :
done

View File

@@ -351,6 +351,7 @@ if install_tests
'integration-tests/TEST-30-ONCLOCKCHANGE/TEST-30-ONCLOCKCHANGE.units',
'integration-tests/TEST-38-FREEZER/TEST-38-FREEZER.units',
'integration-tests/TEST-52-HONORFIRSTSHUTDOWN/TEST-52-HONORFIRSTSHUTDOWN.units',
'integration-tests/TEST-54-CREDS/TEST-54-CREDS.units',
'integration-tests/TEST-55-OOMD/TEST-55-OOMD.units',
'integration-tests/TEST-62-RESTRICT-IFACES/TEST-62-RESTRICT-IFACES.units',
'integration-tests/TEST-63-PATH/TEST-63-PATH.units',

View File

@@ -571,4 +571,106 @@ systemd-run -M testuser@ --user --wait -p ImportCredential=brummbaer \
kill "$PID"
# Now test credential refreshing
UNIT_NAME="TEST-54-CREDS-refreshing-$RANDOM.service"
OUTPUT_FILE="/tmp/$UNIT_NAME.out"
POST_FLAG_FILE="/tmp/$UNIT_NAME.post-flag"
cat >/run/systemd/system/"$UNIT_NAME" <<EOF
[Service]
Type=notify-reload
ImportCredential=test.creds.*
ExecStart=/usr/lib/systemd/tests/testdata/TEST-54-CREDS.units/refresh.sh $OUTPUT_FILE
EOF
systemctl start "$UNIT_NAME"
ls -l /run/credentials/"$UNIT_NAME"/
echo "neu" >/run/credstore/test.creds.new-refresh-1
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-1 ]]
systemctl reload "$UNIT_NAME"
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-1 ]]
(! grep -q "test.creds.new-refresh-1" "$OUTPUT_FILE")
echo "RefreshOnReload=credentials" >>/run/systemd/system/"$UNIT_NAME"
systemctl daemon-reload
systemctl reload "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-1 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-1
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
systemctl stop "$UNIT_NAME"
cat >>/run/systemd/system/"$UNIT_NAME" <<EOF
ProtectSystem=strict
ReadWritePaths=/tmp
ExecReloadPost=bash -c 'diff $OUTPUT_FILE <(grep . %d/*) || rm -v $POST_FLAG_FILE'
EOF
systemctl daemon-reload
systemctl start "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-1 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-1
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
systemctl reload "$UNIT_NAME"
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
echo "2" >/run/credstore/test.creds.new-refresh-2
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-2 ]]
systemctl reload "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-2 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-2
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
echo "3" >/run/credstore/test.creds.new-refresh-3
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-3 ]]
rm "$OUTPUT_FILE"
systemctl edit --runtime --stdin "$UNIT_NAME" <<EOF
[Service]
Type=notify
ExecReloadPost=
EOF
systemctl reload "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-3 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-3
[[ ! -e "$OUTPUT_FILE" ]]
echo "RefreshOnReload=no" >>/run/systemd/system/"$UNIT_NAME"
systemctl daemon-reload
assert_eq "$(systemctl show "$UNIT_NAME" -P CanReload)" "no"
systemctl revert "$UNIT_NAME"
assert_eq "$(systemctl show "$UNIT_NAME" -P CanReload)" "yes"
echo "BOGUS" >/run/credstore/test.creds.refresh-bogus
touch "$POST_FLAG_FILE"
systemctl reload "$UNIT_NAME"
diff /run/credstore/test.creds.new-refresh-3 /run/credentials/"$UNIT_NAME"/test.creds.new-refresh-3
[[ ! -e /run/credentials/"$UNIT_NAME"/test.creds.refresh-bogus ]]
diff "$OUTPUT_FILE" <(grep . /run/credentials/"$UNIT_NAME"/*)
[[ ! -e "$POST_FLAG_FILE" ]]
OUTPUT_FILE_USER="/tmp/TEST-54-CREDS-refreshing-user.out"
systemd-notify --fork -- \
systemd-run -M testuser@ --user --wait \
--unit=brummbaer-refresh.service \
--service-type=notify-reload \
-p NotifyAccess=all \
-p 'ImportCredential=brummbaer*' \
-p RefreshOnReload=credentials \
-p ProtectSystem=strict \
-p ReadWritePaths=/tmp \
/usr/lib/systemd/tests/testdata/TEST-54-CREDS.units/refresh.sh "$OUTPUT_FILE_USER"
[[ -f "$TESTUSER_CRED_DIR/brummbaer-refresh.service/brummbaer" ]]
diff "$OUTPUT_FILE_USER" <(grep . "$TESTUSER_CRED_DIR"/brummbaer-refresh.service/*)
run0 -u testuser --pipe -i \
--property=EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \
'mkdir -p .config/credstore && echo "refreshed" >.config/credstore/brummbaer.refreshed'
systemctl -M testuser@ --user reload brummbaer-refresh.service
assert_eq "$(cat "$TESTUSER_CRED_DIR"/brummbaer-refresh.service/brummbaer.refreshed)" "refreshed"
diff "$OUTPUT_FILE_USER" <(grep . "$TESTUSER_CRED_DIR"/brummbaer-refresh.service/*)
touch /testok