Commit a8f11321 authored by Lennart Poettering's avatar Lennart Poettering

systemctl: support remote and privileged systemctl access via SSH and pkexec

This adds support for executing systemctl operations remotely or as
privileged user while still running systemctl itself unprivileged and
locally.

This currently requires a D-Bus patch to work properly.

https://bugs.freedesktop.org/show_bug.cgi?id=35230
parent e75c0580
systemd-stdio-bridge
systemd-machine-id-setup
systemd-detect-virt
systemd-sysctl
......
......@@ -58,6 +58,7 @@ AM_CPPFLAGS = \
-DSYSTEMD_SHUTDOWN_BINARY_PATH=\"$(rootlibexecdir)/systemd-shutdown\" \
-DSYSTEMCTL_BINARY_PATH=\"$(rootbindir)/systemctl\" \
-DSYSTEMD_TTY_ASK_PASSWORD_AGENT_BINARY_PATH=\"$(rootbindir)/systemd-tty-ask-password-agent\" \
-DSYSTEMD_STDIO_BRIDGE_BINARY_PATH=\"$(bindir)/systemd-stdio-bridge\" \
-DRUNTIME_DIR=\"$(localstatedir)/run\" \
-DRANDOM_SEED=\"$(localstatedir)/lib/random-seed\" \
-DSYSTEMD_CRYPTSETUP_PATH=\"$(rootlibexecdir)/systemd-cryptsetup\" \
......@@ -111,7 +112,8 @@ rootsbin_PROGRAMS = \
systemd-machine-id-setup
bin_PROGRAMS = \
systemd-cgls
systemd-cgls \
systemd-stdio-bridge
if HAVE_GTK
bin_PROGRAMS += \
......@@ -980,6 +982,12 @@ systemd_cgls_CFLAGS = \
systemd_cgls_LDADD = \
libsystemd-basic.la
systemd_stdio_bridge_SOURCES = \
src/bridge.c
systemd_stdio_bridge_LDADD = \
libsystemd-basic.la
systemadm_SOURCES = \
src/systemadm.vala \
src/systemd-interfaces.vala
......
......@@ -26,6 +26,8 @@ Features:
* optionally create watched directories in .path units
* Support --test based on current system state
* consider services with no [Install] section and stored in /lib enabled by "systemctl is-enabled"
* consider services with any kind of link in /etc/systemd/system enabled
......
......@@ -382,6 +382,27 @@
file that shall be
disabled.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-H</option></term>
<term><option>--host</option></term>
<listitem><para>Execute operation
remotely. Specifiy a hostname, or
username and hostname seperated by @,
to connect to. This will use SSH to
talk to the remote systemd
instance.</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-P</option></term>
<term><option>--privileged</option></term>
<listitem><para>Acquire privileges via
PolicyKit before executing the
operation.</para></listitem>
</varlistentry>
</variablelist>
<para>The following commands are understood:</para>
......
This diff is collapsed.
......@@ -23,6 +23,8 @@
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <dbus/dbus.h>
#include "log.h"
......@@ -55,59 +57,63 @@ int bus_check_peercred(DBusConnection *c) {
return 1;
}
int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private, DBusError *error) {
DBusConnection *bus;
#define TIMEOUT_USEC (60*USEC_PER_SEC)
assert(_bus);
static int sync_auth(DBusConnection *bus, DBusError *error) {
usec_t begin, tstamp;
#define TIMEOUT_USEC (60*USEC_PER_SEC)
assert(bus);
/* If we are root, then let's not go via the bus */
if (geteuid() == 0 && t == DBUS_BUS_SYSTEM) {
usec_t begin, tstamp;
/* This complexity should probably move into D-Bus itself:
*
* https://bugs.freedesktop.org/show_bug.cgi?id=35189 */
if (!(bus = dbus_connection_open_private("unix:abstract=/org/freedesktop/systemd1/private", error)))
return -EIO;
begin = tstamp = now(CLOCK_MONOTONIC);
for (;;) {
if (bus_check_peercred(bus) < 0) {
dbus_connection_close(bus);
dbus_connection_unref(bus);
if (tstamp > begin + TIMEOUT_USEC)
break;
dbus_set_error_const(error, DBUS_ERROR_ACCESS_DENIED, "Failed to verify owner of bus.");
return -EACCES;
}
if (dbus_connection_get_is_authenticated(bus))
break;
/* This complexity should probably move into D-Bus itself:
*
* https://bugs.freedesktop.org/show_bug.cgi?id=35189 */
begin = tstamp = now(CLOCK_MONOTONIC);
for (;;) {
if (!dbus_connection_read_write_dispatch(bus, ((begin + TIMEOUT_USEC - tstamp) + USEC_PER_MSEC - 1) / USEC_PER_MSEC))
break;
if (tstamp > begin + TIMEOUT_USEC)
break;
tstamp = now(CLOCK_MONOTONIC);
}
if (dbus_connection_get_is_authenticated(bus))
break;
if (!dbus_connection_get_is_connected(bus)) {
dbus_set_error_const(error, DBUS_ERROR_NO_SERVER, "Connection terminated during authentication.");
return -ECONNREFUSED;
}
if (!dbus_connection_read_write_dispatch(bus, ((begin + TIMEOUT_USEC - tstamp) + USEC_PER_MSEC - 1) / USEC_PER_MSEC))
break;
if (!dbus_connection_get_is_authenticated(bus)) {
dbus_set_error_const(error, DBUS_ERROR_TIMEOUT, "Failed to authenticate in time.");
return -EACCES;
}
tstamp = now(CLOCK_MONOTONIC);
}
return 0;
}
if (!dbus_connection_get_is_connected(bus)) {
dbus_connection_close(bus);
dbus_connection_unref(bus);
int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private, DBusError *error) {
DBusConnection *bus;
int r;
dbus_set_error_const(error, DBUS_ERROR_NO_SERVER, "Connection terminated during authentication.");
return -ECONNREFUSED;
}
assert(_bus);
if (!dbus_connection_get_is_authenticated(bus)) {
/* If we are root, then let's not go via the bus */
if (geteuid() == 0 && t == DBUS_BUS_SYSTEM) {
if (!(bus = dbus_connection_open_private("unix:abstract=/org/freedesktop/systemd1/private", error)))
return -EIO;
dbus_connection_set_exit_on_disconnect(bus, FALSE);
if (bus_check_peercred(bus) < 0) {
dbus_connection_close(bus);
dbus_connection_unref(bus);
dbus_set_error_const(error, DBUS_ERROR_TIMEOUT, "Failed to authenticate in time.");
dbus_set_error_const(error, DBUS_ERROR_ACCESS_DENIED, "Failed to verify owner of bus.");
return -EACCES;
}
......@@ -118,12 +124,93 @@ int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private, DBusError *
if (!(bus = dbus_bus_get_private(t, error)))
return -EIO;
dbus_connection_set_exit_on_disconnect(bus, FALSE);
if (private)
*private = false;
}
if ((r = sync_auth(bus, error)) < 0) {
dbus_connection_close(bus);
dbus_connection_unref(bus);
return r;
}
*_bus = bus;
return 0;
}
int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error) {
DBusConnection *bus;
char *p = NULL;
int r;
assert(_bus);
assert(user || host);
if (user && host)
asprintf(&p, "exec:path=ssh,argv1=-xT,argv2=%s@%s,argv3=systemd-stdio-bridge", user, host);
else if (user)
asprintf(&p, "exec:path=ssh,argv1=-xT,argv2=%s@localhost,argv3=systemd-stdio-bridge", user);
else if (host)
asprintf(&p, "exec:path=ssh,argv1=-xT,argv2=%s,argv3=systemd-stdio-bridge", host);
if (!p) {
dbus_set_error_const(error, DBUS_ERROR_NO_MEMORY, NULL);
return -ENOMEM;
}
bus = dbus_connection_open_private(p, error);
free(p);
if (!bus)
return -EIO;
dbus_connection_set_exit_on_disconnect(bus, FALSE);
if ((r = sync_auth(bus, error)) < 0) {
dbus_connection_close(bus);
dbus_connection_unref(bus);
return r;
}
if (!dbus_bus_register(bus, error)) {
dbus_connection_close(bus);
dbus_connection_unref(bus);
return r;
}
*_bus = bus;
return 0;
}
int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error) {
DBusConnection *bus;
int r;
assert(_bus);
/* Don't bother with PolicyKit if we are root */
if (geteuid() == 0)
return bus_connect(DBUS_BUS_SYSTEM, _bus, NULL, error);
if (!(bus = dbus_connection_open_private("exec:path=pkexec,argv1=" SYSTEMD_STDIO_BRIDGE_BINARY_PATH, error)))
return -EIO;
dbus_connection_set_exit_on_disconnect(bus, FALSE);
if ((r = sync_auth(bus, error)) < 0) {
dbus_connection_close(bus);
dbus_connection_unref(bus);
return r;
}
if (!dbus_bus_register(bus, error)) {
dbus_connection_close(bus);
dbus_connection_unref(bus);
return r;
}
*_bus = bus;
return 0;
}
......
......@@ -28,6 +28,9 @@ int bus_check_peercred(DBusConnection *c);
int bus_connect(DBusBusType t, DBusConnection **_bus, bool *private_bus, DBusError *error);
int bus_connect_system_ssh(const char *user, const char *host, DBusConnection **_bus, DBusError *error);
int bus_connect_system_polkit(DBusConnection **_bus, DBusError *error);
const char *bus_error_message(const DBusError *error);
#endif
......@@ -27,4 +27,15 @@
<annotate key="org.freedesktop.policykit.exec.path">/lib/systemd/systemd-reply-password</annotate>
</action>
<action id="org.freedesktop.systemd1.BusAccess">
<description>Privileged system and service manager access</description>
<message>Authentication is required to access the system and service manager.</message>
<defaults>
<allow_any>no</allow_any>
<allow_inactive>no</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
<annotate key="org.freedesktop.policykit.exec.path">/usr/bin/systemd-stdio-bridge</annotate>
</action>
</policyconfig>
......@@ -108,6 +108,12 @@ static enum dot {
DOT_ORDER,
DOT_REQUIRE
} arg_dot = DOT_ALL;
static enum transport {
TRANSPORT_NORMAL,
TRANSPORT_SSH,
TRANSPORT_POLKIT
} arg_transport = TRANSPORT_NORMAL;
static const char *arg_host = NULL;
static bool private_bus = false;
......@@ -2061,12 +2067,14 @@ static void print_status_info(UnitStatusInfo *i) {
printf("\t CGroup: %s\n", i->default_control_group);
if ((c = columns()) > 18)
c -= 18;
else
c = 0;
if (arg_transport != TRANSPORT_SSH) {
if ((c = columns()) > 18)
c -= 18;
else
c = 0;
show_cgroup_by_path(i->default_control_group, "\t\t ", c);
show_cgroup_by_path(i->default_control_group, "\t\t ", c);
}
}
if (i->need_daemon_reload)
......@@ -4290,22 +4298,25 @@ static int systemctl_help(void) {
" pending\n"
" --ignore-dependencies\n"
" When queueing a new job, ignore all its dependencies\n"
" --kill-mode=MODE How to send signal\n"
" --kill-who=WHO Who to send signal to\n"
" -s --signal=SIGNAL Which signal to send\n"
" -H --host=[user@]host\n"
" Show information for remote host\n"
" -P --privileged Acquire privileges before execution\n"
" -q --quiet Suppress output\n"
" --no-block Do not wait until operation finished\n"
" --no-pager Do not pipe output into a pager.\n"
" --system Connect to system manager\n"
" --user Connect to user service manager\n"
" --order When generating graph for dot, show only order\n"
" --require When generating graph for dot, show only requirement\n"
" --no-wall Don't send wall message before halt/power-off/reboot\n"
" --global Enable/disable unit files globally\n"
" --no-reload When enabling/disabling unit files, don't reload daemon\n"
" configuration\n"
" --no-pager Do not pipe output into a pager.\n"
" --no-ask-password\n"
" Do not ask for system passwords\n"
" --kill-mode=MODE How to send signal\n"
" --kill-who=WHO Who to send signal to\n"
" -s --signal=SIGNAL Which signal to send\n"
" --order When generating graph for dot, show only order\n"
" --require When generating graph for dot, show only requirement\n"
" --system Connect to system manager\n"
" --user Connect to user service manager\n"
" --global Enable/disable unit files globally\n"
" -f --force When enabling unit files, override existing symlinks\n"
" When shutting down, execute action immediately\n"
" --defaults When disabling unit files, remove default symlinks only\n\n"
......@@ -4472,6 +4483,8 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
{ "kill-who", required_argument, NULL, ARG_KILL_WHO },
{ "signal", required_argument, NULL, 's' },
{ "no-ask-password", no_argument, NULL, ARG_NO_ASK_PASSWORD },
{ "host", required_argument, NULL, 'H' },
{ "privileged",no_argument, NULL, 'P' },
{ NULL, 0, NULL, 0 }
};
......@@ -4483,7 +4496,7 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
/* Only when running as systemctl we ask for passwords */
arg_ask_password = true;
while ((c = getopt_long(argc, argv, "ht:p:aqfs:", options, NULL)) >= 0) {
while ((c = getopt_long(argc, argv, "ht:p:aqfs:H:P", options, NULL)) >= 0) {
switch (c) {
......@@ -4605,6 +4618,15 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
arg_ask_password = false;
break;
case 'P':
arg_transport = TRANSPORT_POLKIT;
break;
case 'H':
arg_transport = TRANSPORT_SSH;
arg_host = optarg;
break;
case '?':
return -EINVAL;
......@@ -4614,6 +4636,11 @@ static int systemctl_parse_argv(int argc, char *argv[]) {
}
}
if (arg_transport != TRANSPORT_NORMAL && arg_user) {
log_error("Cannot access user instance remotely.");
return -EINVAL;
}
return 1;
}
......@@ -5622,7 +5649,16 @@ int main(int argc, char*argv[]) {
goto finish;
}
bus_connect(arg_user ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, &private_bus, &error);
if (arg_transport == TRANSPORT_NORMAL)
bus_connect(arg_user ? DBUS_BUS_SESSION : DBUS_BUS_SYSTEM, &bus, &private_bus, &error);
else if (arg_transport == TRANSPORT_POLKIT) {
bus_connect_system_polkit(&bus, &error);
private_bus = false;
} else if (arg_transport == TRANSPORT_SSH) {
bus_connect_system_ssh(NULL, arg_host, &bus, &error);
private_bus = false;
} else
assert_not_reached("Uh, invalid transport...");
switch (arg_action) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment