Merge pull request #32701 from bluca/vmspawn/machinectl-shell
vmspawn: implement machinectl shell for vm class machines and send termination signal via D-Bus-over-SSH
This commit is contained in:
@@ -5,36 +5,72 @@
|
||||
|
||||
#include "bus-error.h"
|
||||
#include "bus-locator.h"
|
||||
#include "json.h"
|
||||
#include "macro.h"
|
||||
#include "process-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "string-util.h"
|
||||
#include "varlink.h"
|
||||
#include "vmspawn-register.h"
|
||||
|
||||
int register_machine(sd_bus *bus, const char *machine_name, sd_id128_t uuid, const char *service, const char *directory) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
int register_machine(
|
||||
sd_bus *bus,
|
||||
const char *machine_name,
|
||||
sd_id128_t uuid,
|
||||
const char *service,
|
||||
const char *directory,
|
||||
unsigned cid,
|
||||
const char *address,
|
||||
const char *key_path) {
|
||||
|
||||
_cleanup_(varlink_unrefp) Varlink *vl = NULL;
|
||||
int r;
|
||||
|
||||
assert(bus);
|
||||
assert(machine_name);
|
||||
assert(service);
|
||||
|
||||
r = bus_call_method(
|
||||
bus,
|
||||
bus_machine_mgr,
|
||||
"RegisterMachine",
|
||||
&error,
|
||||
NULL,
|
||||
"sayssus",
|
||||
machine_name,
|
||||
SD_BUS_MESSAGE_APPEND_ID128(uuid),
|
||||
service,
|
||||
"vm",
|
||||
(uint32_t) getpid_cached(),
|
||||
strempty(directory));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r));
|
||||
/* First try to use varlink, as it provides more features (such as SSH support). */
|
||||
r = varlink_connect_address(&vl, "/run/systemd/machine/io.systemd.Machine");
|
||||
if (r == -ENOENT || ERRNO_IS_DISCONNECT(r)) {
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
|
||||
return 0;
|
||||
assert(bus);
|
||||
|
||||
/* In case we are running with an older machined, fallback to the existing D-Bus method. */
|
||||
r = bus_call_method(
|
||||
bus,
|
||||
bus_machine_mgr,
|
||||
"RegisterMachine",
|
||||
&error,
|
||||
NULL,
|
||||
"sayssus",
|
||||
machine_name,
|
||||
SD_BUS_MESSAGE_APPEND_ID128(uuid),
|
||||
service,
|
||||
"vm",
|
||||
(uint32_t) getpid_cached(),
|
||||
strempty(directory));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to register machine: %s", bus_error_message(&error, r));
|
||||
|
||||
return 0;
|
||||
}
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to machined on /run/systemd/machine/io.systemd.Machine: %m");
|
||||
|
||||
|
||||
return varlink_callb_and_log(vl,
|
||||
"io.systemd.Machine.Register",
|
||||
NULL,
|
||||
JSON_BUILD_OBJECT(
|
||||
JSON_BUILD_PAIR_STRING("name", machine_name),
|
||||
JSON_BUILD_PAIR_CONDITION(!sd_id128_is_null(uuid), "id", JSON_BUILD_ID128(uuid)),
|
||||
JSON_BUILD_PAIR_STRING("service", service),
|
||||
JSON_BUILD_PAIR_STRING("class", "vm"),
|
||||
JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(cid), "vsockCid", JSON_BUILD_UNSIGNED(cid)),
|
||||
JSON_BUILD_PAIR_CONDITION(directory, "rootDirectory", JSON_BUILD_STRING(directory)),
|
||||
JSON_BUILD_PAIR_CONDITION(address, "sshAddress", JSON_BUILD_STRING(address)),
|
||||
JSON_BUILD_PAIR_CONDITION(key_path, "sshPrivateKeyPath", JSON_BUILD_STRING(key_path))));
|
||||
}
|
||||
|
||||
int unregister_machine(sd_bus *bus, const char *machine_name) {
|
||||
|
||||
@@ -3,5 +3,13 @@
|
||||
#include "sd-bus.h"
|
||||
#include "sd-id128.h"
|
||||
|
||||
int register_machine(sd_bus *bus, const char *machine_name, sd_id128_t uuid, const char *service, const char *directory);
|
||||
int register_machine(
|
||||
sd_bus *bus,
|
||||
const char *machine_name,
|
||||
sd_id128_t uuid,
|
||||
const char *service,
|
||||
const char *directory,
|
||||
unsigned cid,
|
||||
const char *address,
|
||||
const char *key_path);
|
||||
int unregister_machine(sd_bus *bus, const char *machine_name);
|
||||
|
||||
+161
-10
@@ -18,6 +18,9 @@
|
||||
#include "architecture.h"
|
||||
#include "bootspec.h"
|
||||
#include "build.h"
|
||||
#include "bus-internal.h"
|
||||
#include "bus-locator.h"
|
||||
#include "bus-wait-for-jobs.h"
|
||||
#include "chase.h"
|
||||
#include "common-signal.h"
|
||||
#include "copy.h"
|
||||
@@ -58,6 +61,7 @@
|
||||
#include "signal-util.h"
|
||||
#include "socket-util.h"
|
||||
#include "stat-util.h"
|
||||
#include "stdio-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "time-util.h"
|
||||
@@ -71,6 +75,12 @@
|
||||
|
||||
#define VM_TAP_HASH_KEY SD_ID128_MAKE(01,d0,c6,4c,2b,df,24,fb,c0,f8,b2,09,7d,59,b2,93)
|
||||
|
||||
typedef struct SSHInfo {
|
||||
unsigned cid;
|
||||
char *private_key_path;
|
||||
unsigned port;
|
||||
} SSHInfo;
|
||||
|
||||
static bool arg_quiet = false;
|
||||
static PagerFlags arg_pager_flags = 0;
|
||||
static char *arg_directory = NULL;
|
||||
@@ -697,11 +707,61 @@ static int setup_notify_parent(sd_event *event, int fd, int *exit_status, sd_eve
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int bus_open_in_machine(sd_bus **ret, unsigned cid, unsigned port, const char *private_key_path) {
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_free_ char *ssh_escaped = NULL, *bus_address = NULL;
|
||||
char port_str[DECIMAL_STR_MAX(unsigned)], cid_str[DECIMAL_STR_MAX(unsigned)];
|
||||
int r;
|
||||
|
||||
assert(ret);
|
||||
assert(private_key_path);
|
||||
|
||||
r = sd_bus_new(&bus);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
const char *ssh = secure_getenv("SYSTEMD_SSH") ?: "ssh";
|
||||
ssh_escaped = bus_address_escape(ssh);
|
||||
if (!ssh_escaped)
|
||||
return -ENOMEM;
|
||||
|
||||
xsprintf(port_str, "%u", port);
|
||||
xsprintf(cid_str, "%u", cid);
|
||||
|
||||
bus_address = strjoin(
|
||||
"unixexec:path=", ssh_escaped,
|
||||
/* -x: Disable X11 forwarding
|
||||
* -T: Disable PTY allocation */
|
||||
",argv1=-xT",
|
||||
",argv2=-o,argv3=IdentitiesOnly yes",
|
||||
",argv4=-o,argv5=IdentityFile=", private_key_path,
|
||||
",argv6=-p,argv7=", port_str,
|
||||
",argv8=--",
|
||||
",argv9=root@vsock/", cid_str,
|
||||
",argv10=systemd-stdio-bridge"
|
||||
);
|
||||
if (!bus_address)
|
||||
return -ENOMEM;
|
||||
|
||||
free_and_replace(bus->address, bus_address);
|
||||
bus->bus_client = true;
|
||||
bus->trusted = true;
|
||||
bus->runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
bus->is_local = false;
|
||||
|
||||
r = sd_bus_start(bus);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
*ret = TAKE_PTR(bus);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
|
||||
PidRef *pidref = userdata;
|
||||
int r;
|
||||
|
||||
/* TODO: actually talk to qemu and ask the guest to shutdown here */
|
||||
/* Backup method to shut down the VM when D-BUS access over SSH is not available */
|
||||
|
||||
if (pidref) {
|
||||
r = pidref_kill(pidref, SIGKILL);
|
||||
@@ -718,6 +778,61 @@ static int on_orderly_shutdown(sd_event_source *s, const struct signalfd_siginfo
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int forward_signal_to_vm_pid1(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) {
|
||||
_cleanup_(bus_wait_for_jobs_freep) BusWaitForJobs *w = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
|
||||
_cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
|
||||
SSHInfo *ssh_info = ASSERT_PTR(userdata);
|
||||
const char *vm_pid1;
|
||||
int r;
|
||||
|
||||
assert(s);
|
||||
assert(si);
|
||||
|
||||
r = bus_open_in_machine(&bus, ssh_info->cid, ssh_info->port, ssh_info->private_key_path);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to connect to VM to forward signal: %m");
|
||||
|
||||
r = bus_wait_for_jobs_new(bus, &w);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Could not watch job: %m");
|
||||
|
||||
r = bus_call_method(
|
||||
bus,
|
||||
bus_systemd_mgr,
|
||||
"GetUnitByPID",
|
||||
&error,
|
||||
NULL,
|
||||
"");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to get init process of VM: %s", bus_error_message(&error, r));
|
||||
|
||||
r = sd_bus_message_read(reply, "o", &vm_pid1);
|
||||
if (r < 0)
|
||||
return bus_log_parse_error(r);
|
||||
|
||||
r = bus_wait_for_jobs_one(w, vm_pid1, /* quiet */ false, NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = bus_call_method(
|
||||
bus,
|
||||
bus_systemd_mgr,
|
||||
"KillUnit",
|
||||
&error,
|
||||
NULL,
|
||||
"ssi",
|
||||
vm_pid1,
|
||||
"leader",
|
||||
si->ssi_signo);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to forward signal to PID 1 of the VM: %s", bus_error_message(&error, r));
|
||||
log_info("Sent signal %"PRIu32" to the VM's PID 1.", si->ssi_signo);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int on_child_exit(sd_event_source *s, const siginfo_t *si, void *userdata) {
|
||||
sd_event_exit(sd_event_source_get_event(s), 0);
|
||||
return 0;
|
||||
@@ -1200,6 +1315,7 @@ static int generate_ssh_keypair(const char *key_path, const char *key_type) {
|
||||
}
|
||||
|
||||
static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
SSHInfo ssh_info; /* Used when talking to pid1 via SSH, but must survive until the function ends. */
|
||||
_cleanup_(ovmf_config_freep) OvmfConfig *ovmf_config = NULL;
|
||||
_cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
|
||||
_cleanup_free_ char *machine = NULL, *qemu_binary = NULL, *mem = NULL, *trans_scope = NULL, *kernel = NULL;
|
||||
@@ -1222,12 +1338,6 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (arg_register) {
|
||||
r = register_machine(bus, arg_machine, arg_uuid, trans_scope, arg_directory);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
bool use_kvm = arg_kvm > 0;
|
||||
if (arg_kvm < 0) {
|
||||
r = qemu_check_kvm_support();
|
||||
@@ -1867,6 +1977,18 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
r = unit_name_to_prefix(trans_scope, &scope_prefix);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to strip .scope suffix from scope: %m");
|
||||
|
||||
/* on distros that provide their own sshd@.service file we need to provide a dropin which
|
||||
* picks up our public key credential */
|
||||
r = machine_credential_set(
|
||||
&arg_credentials,
|
||||
"systemd.unit-dropin.sshd-vsock@.service:"
|
||||
"[Service]\n"
|
||||
"ExecStart=\n"
|
||||
"ExecStart=sshd -i -o 'AuthorizedKeysFile=%d/ssh.ephemeral-authorized_keys-all .ssh/authorized_keys'\n"
|
||||
"ImportCredential=ssh.ephemeral-authorized_keys-all\n");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to set credential systemd.unit-dropin.sshd-vsock@.service: %m");
|
||||
}
|
||||
|
||||
if (ARCHITECTURE_SUPPORTS_SMBIOS)
|
||||
@@ -1919,6 +2041,23 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
log_debug("Executing: %s", joined);
|
||||
}
|
||||
|
||||
if (arg_register) {
|
||||
char vm_address[STRLEN("vsock/") + DECIMAL_STR_MAX(unsigned)];
|
||||
|
||||
xsprintf(vm_address, "vsock/%u", child_cid);
|
||||
r = register_machine(
|
||||
bus,
|
||||
arg_machine,
|
||||
arg_uuid,
|
||||
trans_scope,
|
||||
arg_directory,
|
||||
child_cid,
|
||||
child_cid != VMADDR_CID_ANY ? vm_address : NULL,
|
||||
ssh_private_key_path);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
assert_se(sigprocmask_many(SIG_BLOCK, /* old_sigset=*/ NULL, SIGCHLD, SIGWINCH) >= 0);
|
||||
|
||||
_cleanup_(sd_event_source_unrefp) sd_event_source *notify_event_source = NULL;
|
||||
@@ -1962,9 +2101,21 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) {
|
||||
return log_error_errno(r, "Failed to setup event loop to handle VSOCK notify events: %m");
|
||||
}
|
||||
|
||||
/* shutdown qemu when we are shutdown */
|
||||
(void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref);
|
||||
(void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref);
|
||||
/* If we have the vsock address and the SSH key, ask pid1 inside the guest to shutdown. */
|
||||
if (child_cid != VMADDR_CID_ANY && ssh_private_key_path) {
|
||||
ssh_info = (SSHInfo) {
|
||||
.cid = child_cid,
|
||||
.private_key_path = ssh_private_key_path,
|
||||
.port = 22,
|
||||
};
|
||||
|
||||
(void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, forward_signal_to_vm_pid1, &ssh_info);
|
||||
(void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, forward_signal_to_vm_pid1, &ssh_info);
|
||||
} else {
|
||||
/* As a fallback in case SSH cannot be used, send a shutdown signal to the VMM instead. */
|
||||
(void) sd_event_add_signal(event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref);
|
||||
(void) sd_event_add_signal(event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, on_orderly_shutdown, &child_pidref);
|
||||
}
|
||||
|
||||
(void) sd_event_add_signal(event, NULL, (SIGRTMIN+18) | SD_EVENT_SIGNAL_PROCMASK, sigrtmin18_handler, NULL);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user