diff options
author | Luca Boccassi <luca.boccassi@microsoft.com> | 2020-07-30 19:37:10 +0100 |
---|---|---|
committer | Luca Boccassi <luca.boccassi@microsoft.com> | 2021-01-18 17:24:05 +0000 |
commit | 5e8deb94c6f05137942b10b5288a37d9b09fd43f (patch) | |
tree | 1cbed6e76c6398d4c183a6b71e0a9927386833d8 /src/core/dbus-service.c | |
parent | 94293d65cd4125347e21b3e423d0e245226b1be2 (diff) | |
download | systemd-5e8deb94c6f05137942b10b5288a37d9b09fd43f.tar.gz |
core: add DBUS method to bind mount new nodes without service restart
Allow to setup new bind mounts for a service at runtime (via either
DBUS or a new 'systemctl bind' verb) with a new helper that forks into
the unit's mount namespace.
Add a new integration test to cover this.
Useful for zero-downtime addition to services that are running inside
mount namespaces, especially when using RootImage/RootDirectory.
If a service runs with a read-only root, a tmpfs is added on /run
to ensure we can create the airlock directory for incoming mounts
under /run/host/incoming.
Diffstat (limited to 'src/core/dbus-service.c')
-rw-r--r-- | src/core/dbus-service.c | 87 |
1 files changed, 87 insertions, 0 deletions
diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c index 64f9d4ab36..6df93e44a4 100644 --- a/src/core/dbus-service.c +++ b/src/core/dbus-service.c @@ -11,11 +11,15 @@ #include "dbus-manager.h" #include "dbus-service.h" #include "dbus-util.h" +#include "execute.h" #include "exit-status.h" #include "fd-util.h" #include "fileio.h" +#include "locale-util.h" +#include "mount-util.h" #include "parse-util.h" #include "path-util.h" +#include "selinux-access.h" #include "service.h" #include "signal-util.h" #include "string-util.h" @@ -91,6 +95,79 @@ static int property_get_exit_status_set( return sd_bus_message_close_container(reply); } +int bus_service_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error) { + int read_only, make_file_or_directory; + const char *dest, *src, *propagate_directory; + Unit *u = userdata; + ExecContext *c; + pid_t unit_pid; + int r; + + assert(message); + assert(u); + + if (!MANAGER_IS_SYSTEM(u->manager)) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Adding bind mounts at runtime is only supported for system managers."); + + r = mac_selinux_unit_access_check(u, message, "start", error); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "ssbb", &src, &dest, &read_only, &make_file_or_directory); + if (r < 0) + return r; + + if (!path_is_absolute(src) || !path_is_normalized(src)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Source path must be absolute and normalized."); + + if (isempty(dest)) + dest = src; + else if (!path_is_absolute(dest) || !path_is_normalized(dest)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Destination path must be absolute and normalized."); + + r = bus_verify_manage_units_async_full( + u, + "bind-mount", + CAP_SYS_ADMIN, + N_("Authentication is required to bind mount on '$(unit)'."), + true, + message, + error); + if (r < 0) + return r; + if (r == 0) + return 1; /* No authorization for now, but the async polkit stuff will call us again when it has it */ + + if (u->type != UNIT_SERVICE) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not of type .service"); + + /* If it would be dropped at startup time, return an error. The context should always be available, but + * there's an assert in exec_needs_mount_namespace, so double-check just in case. */ + c = unit_get_exec_context(u); + if (!c) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Cannot access unit execution context"); + if (path_startswith_strv(dest, c->inaccessible_paths)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "%s is not accessible to this unit", dest); + + /* Ensure that the unit was started in a private mount namespace */ + if (!exec_needs_mount_namespace(c, NULL, unit_get_exec_runtime(u))) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit not running in private mount namespace, cannot activate bind mount"); + + unit_pid = unit_main_pid(u); + if (unit_pid == 0 || !UNIT_IS_ACTIVE_OR_RELOADING(unit_active_state(u))) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Unit is not running"); + + propagate_directory = strjoina("/run/systemd/propagate/", u->id); + r = bind_mount_in_namespace(unit_pid, + propagate_directory, + "/run/systemd/incoming/", + src, dest, read_only, make_file_or_directory); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to mount %s on %s in unit's namespace: %m", src, dest); + + return sd_bus_reply_method_return(message, NULL); +} + const sd_bus_vtable bus_service_vtable[] = { SD_BUS_VTABLE_START(0), SD_BUS_PROPERTY("Type", "s", property_get_type, offsetof(Service, type), SD_BUS_VTABLE_PROPERTY_CONST), @@ -146,6 +223,16 @@ const sd_bus_vtable bus_service_vtable[] = { BUS_EXEC_COMMAND_LIST_VTABLE("ExecStopPost", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), BUS_EXEC_EX_COMMAND_LIST_VTABLE("ExecStopPostEx", offsetof(Service, exec_command[SERVICE_EXEC_STOP_POST]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), + SD_BUS_METHOD_WITH_NAMES("BindMount", + "ssbb", + SD_BUS_PARAM(source) + SD_BUS_PARAM(destination) + SD_BUS_PARAM(read_only) + SD_BUS_PARAM(mkdir), + NULL,, + bus_service_method_bind_mount, + SD_BUS_VTABLE_UNPRIVILEGED), + /* The following four are obsolete, and thus marked hidden here. They moved into the Unit interface */ SD_BUS_PROPERTY("StartLimitInterval", "t", bus_property_get_usec, offsetof(Unit, start_ratelimit.interval), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), SD_BUS_PROPERTY("StartLimitBurst", "u", bus_property_get_unsigned, offsetof(Unit, start_ratelimit.burst), SD_BUS_VTABLE_PROPERTY_CONST|SD_BUS_VTABLE_HIDDEN), |