From: Sam Leonard Date: Tue, 23 Apr 2024 09:26:59 +0000 (+0100) Subject: machined: add varlink interface for registering machines X-Git-Tag: v256-rc2~71^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5b44c81ff868a4d1b78a74e4770f7a8b2f1d0f91;p=thirdparty%2Fsystemd.git machined: add varlink interface for registering machines This commit adds the new varlink interface io.systemd.Machine at /run/systemd/machine/io.systemd.Machine with a single method Register It supports all combinations of RegisterMachine[WithSSH,WithNetwork] all under the same method. --- diff --git a/man/systemd-machined.service.xml b/man/systemd-machined.service.xml index f3d77559733..b2899ff0fd5 100644 --- a/man/systemd-machined.service.xml +++ b/man/systemd-machined.service.xml @@ -100,10 +100,12 @@ The daemon provides both a C library interface (which is shared with systemd-logind.service8) - as well as a D-Bus interface. + as well as a D-Bus interface and a Varlink interface. The library interface may be used to introspect and watch the state of virtual machines/containers. The bus interface provides the same but in addition may also be used to register or terminate - machines. + machines. The Varlink interface may be used to register machines with optional extensions, e.g. with an + SSH key / address; it can be queried with + varlinkctl introspect /run/systemd/machine/io.systemd.Machine io.systemd.Machine. For more information please consult sd-login3 and diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c new file mode 100644 index 00000000000..377b3d3f0e1 --- /dev/null +++ b/src/machine/machine-varlink.c @@ -0,0 +1,171 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "sd-id128.h" + +#include "hostname-util.h" +#include "json.h" +#include "machine-varlink.h" +#include "machine.h" +#include "path-util.h" +#include "pidref.h" +#include "process-util.h" +#include "socket-util.h" +#include "string-util.h" +#include "varlink.h" + +static JSON_DISPATCH_ENUM_DEFINE(dispatch_machine_class, MachineClass, machine_class_from_string); + +static int machine_name(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + char **m = ASSERT_PTR(userdata); + const char *hostname; + int r; + + assert(variant); + + if (!json_variant_is_string(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + hostname = json_variant_string(variant); + if (!hostname_is_valid(hostname, /* flags= */ 0)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Invalid machine name"); + + r = free_and_strdup(m, hostname); + if (r < 0) + return json_log_oom(variant, flags); + + return 0; +} + +static int machine_leader(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + PidRef *leader = ASSERT_PTR(userdata); + _cleanup_(pidref_done) PidRef temp = PIDREF_NULL; + uint64_t k; + int r; + + if (!json_variant_is_unsigned(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an integer.", strna(name)); + + k = json_variant_unsigned(variant); + if (k > PID_T_MAX || !pid_is_valid(k)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid PID.", strna(name)); + + if (k == 1) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a valid leader PID.", strna(name)); + + r = pidref_set_pid(&temp, k); + if (r < 0) + return json_log(variant, flags, r, "Failed to pin process " PID_FMT ": %m", leader->pid); + + pidref_done(leader); + + *leader = TAKE_PIDREF(temp); + + return 0; +} + +static int machine_ifindices(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + Machine *m = ASSERT_PTR(userdata); + _cleanup_free_ int *netif = NULL; + size_t n_netif, k = 0; + + assert(variant); + + if (!json_variant_is_array(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not an array.", strna(name)); + + n_netif = json_variant_elements(variant); + + netif = new(int, n_netif); + if (!netif) + return json_log_oom(variant, flags); + + JsonVariant *i; + JSON_VARIANT_ARRAY_FOREACH(i, variant) { + uint64_t b; + + if (!json_variant_is_unsigned(i)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Element %zu of JSON field '%s' is not an unsigned integer.", k, strna(name)); + + b = json_variant_unsigned(i); + if (b > INT_MAX || b <= 0) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Invalid network interface index %"PRIu64, b); + + netif[k++] = (int) b; + } + assert(k == n_netif); + + free_and_replace(m->netif, netif); + m->n_netif = n_netif; + + return 0; +} + +static int machine_cid(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + unsigned cid, *c = ASSERT_PTR(userdata); + + assert(variant); + + if (!json_variant_is_unsigned(variant)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a string.", strna(name)); + + cid = json_variant_unsigned(variant); + if (!VSOCK_CID_IS_REGULAR(cid)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "JSON field '%s' is not a regular VSOCK CID.", strna(name)); + + *c = cid; + + return 0; +} + +int vl_method_register(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata) { + Manager *manager = ASSERT_PTR(userdata); + _cleanup_(machine_freep) Machine *machine = NULL; + int r; + + static const JsonDispatch dispatch_table[] = { + { "name", JSON_VARIANT_STRING, machine_name, offsetof(Machine, name), JSON_MANDATORY }, + { "id", JSON_VARIANT_STRING, json_dispatch_id128, offsetof(Machine, id), 0 }, + { "service", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Machine, service), 0 }, + { "class", JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), JSON_MANDATORY }, + { "leader", JSON_VARIANT_UNSIGNED, machine_leader, offsetof(Machine, leader), 0 }, + { "rootDirectory", JSON_VARIANT_STRING, json_dispatch_absolute_path, offsetof(Machine, root_directory), 0 }, + { "ifIndices", JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 }, + { "vsockCid", JSON_VARIANT_UNSIGNED, machine_cid, offsetof(Machine, vsock_cid), 0 }, + { "sshAddress", JSON_VARIANT_STRING, json_dispatch_string, offsetof(Machine, ssh_address), JSON_SAFE }, + { "sshPrivateKeyPath", JSON_VARIANT_STRING, json_dispatch_absolute_path, offsetof(Machine, ssh_private_key_path), 0 }, + {} + }; + + r = machine_new(_MACHINE_CLASS_INVALID, NULL, &machine); + if (r < 0) + return r; + + r = varlink_dispatch(link, parameters, dispatch_table, machine); + if (r != 0) + return r; + + if (!pidref_is_set(&machine->leader)) { + r = varlink_get_peer_pidref(link, &machine->leader); + if (r < 0) + return r; + } + + r = machine_link(manager, machine); + if (r < 0) + return r; + + r = cg_pidref_get_unit(&machine->leader, &machine->unit); + if (r < 0) + return r; + + r = machine_start(machine, NULL, NULL); + if (r < 0) + return r; + + /* the manager will free this machine */ + TAKE_PTR(machine); + + return varlink_reply(link, NULL); +} diff --git a/src/machine/machine-varlink.h b/src/machine/machine-varlink.h new file mode 100644 index 00000000000..ce4ec54dc1c --- /dev/null +++ b/src/machine/machine-varlink.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink.h" + +int vl_method_register(Varlink *link, JsonVariant *parameters, VarlinkMethodFlags flags, void *userdata); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 6ca98e27cf4..0d3ae627c1f 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -1,10 +1,12 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #include "format-util.h" +#include "machine-varlink.h" #include "machined-varlink.h" #include "mkdir.h" #include "user-util.h" #include "varlink.h" +#include "varlink-io.systemd.Machine.h" #include "varlink-io.systemd.UserDatabase.h" typedef struct LookupParameters { @@ -378,13 +380,13 @@ static int vl_method_get_memberships(Varlink *link, JsonVariant *parameters, Var return varlink_error(link, "io.systemd.UserDatabase.NoRecordFound", NULL); } -int manager_varlink_init(Manager *m) { +static int manager_varlink_init_userdb(Manager *m) { _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; int r; assert(m); - if (m->varlink_server) + if (m->varlink_userdb_server) return 0; r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); @@ -415,12 +417,64 @@ int manager_varlink_init(Manager *m) { if (r < 0) return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); - m->varlink_server = TAKE_PTR(s); + m->varlink_userdb_server = TAKE_PTR(s); + return 0; +} + +static int manager_varlink_init_machine(Manager *m) { + _cleanup_(varlink_server_unrefp) VarlinkServer *s = NULL; + int r; + + assert(m); + + if (m->varlink_machine_server) + return 0; + + r = varlink_server_new(&s, VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_INHERIT_USERDATA); + if (r < 0) + return log_error_errno(r, "Failed to allocate varlink server object: %m"); + + varlink_server_set_userdata(s, m); + + r = varlink_server_add_interface(s, &vl_interface_io_systemd_Machine); + if (r < 0) + return log_error_errno(r, "Failed to add UserDatabase interface to varlink server: %m"); + + r = varlink_server_bind_method(s, "io.systemd.Machine.Register", vl_method_register); + if (r < 0) + return log_error_errno(r, "Failed to register varlink methods: %m"); + + (void) mkdir_p("/run/systemd/machine", 0755); + + r = varlink_server_listen_address(s, "/run/systemd/machine/io.systemd.Machine", 0666); + if (r < 0) + return log_error_errno(r, "Failed to bind to varlink socket: %m"); + + r = varlink_server_attach_event(s, m->event, SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach varlink connection to event loop: %m"); + + m->varlink_machine_server = TAKE_PTR(s); + return 0; +} + +int manager_varlink_init(Manager *m) { + int r; + + r = manager_varlink_init_userdb(m); + if (r < 0) + return r; + + r = manager_varlink_init_machine(m); + if (r < 0) + return r; + return 0; } void manager_varlink_done(Manager *m) { assert(m); - m->varlink_server = varlink_server_unref(m->varlink_server); + m->varlink_userdb_server = varlink_server_unref(m->varlink_userdb_server); + m->varlink_machine_server = varlink_server_unref(m->varlink_machine_server); } diff --git a/src/machine/machined.c b/src/machine/machined.c index 2638ed572ed..d7087e4672c 100644 --- a/src/machine/machined.c +++ b/src/machine/machined.c @@ -316,7 +316,10 @@ static bool check_idle(void *userdata) { if (m->operations) return false; - if (varlink_server_current_connections(m->varlink_server) > 0) + if (varlink_server_current_connections(m->varlink_userdb_server) > 0) + return false; + + if (varlink_server_current_connections(m->varlink_machine_server) > 0) return false; manager_gc(m, true); diff --git a/src/machine/machined.h b/src/machine/machined.h index 280c32bab68..67abed0fd67 100644 --- a/src/machine/machined.h +++ b/src/machine/machined.h @@ -40,7 +40,8 @@ struct Manager { sd_event_source *nscd_cache_flush_event; #endif - VarlinkServer *varlink_server; + VarlinkServer *varlink_userdb_server; + VarlinkServer *varlink_machine_server; }; int manager_add_machine(Manager *m, const char *name, Machine **_machine); diff --git a/src/machine/meson.build b/src/machine/meson.build index c82a32589d0..3150b33de57 100644 --- a/src/machine/meson.build +++ b/src/machine/meson.build @@ -3,6 +3,7 @@ libmachine_core_sources = files( 'image-dbus.c', 'machine-dbus.c', + 'machine-varlink.c', 'machine.c', 'machined-core.c', 'machined-dbus.c', diff --git a/src/shared/meson.build b/src/shared/meson.build index 17313aefedc..d01367a1595 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -180,6 +180,7 @@ shared_sources = files( 'varlink-io.systemd.Credentials.c', 'varlink-io.systemd.Hostname.c', 'varlink-io.systemd.Journal.c', + 'varlink-io.systemd.Machine.c', 'varlink-io.systemd.ManagedOOM.c', 'varlink-io.systemd.MountFileSystem.c', 'varlink-io.systemd.NamespaceResource.c', diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c new file mode 100644 index 00000000000..936f01f3669 --- /dev/null +++ b/src/shared/varlink-io.systemd.Machine.c @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "varlink-idl.h" +#include "varlink-io.systemd.Machine.h" + +static VARLINK_DEFINE_METHOD( + Register, + VARLINK_DEFINE_INPUT(name, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(id, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(service, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(class, VARLINK_STRING, 0), + VARLINK_DEFINE_INPUT(leader, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(rootDirectory, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(ifIndices, VARLINK_INT, VARLINK_ARRAY|VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(vsockCid, VARLINK_INT, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(sshAddress, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_DEFINE_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE)); + +VARLINK_DEFINE_INTERFACE( + io_systemd_Machine, + "io.systemd.Machine", + &vl_method_Register); diff --git a/src/shared/varlink-io.systemd.Machine.h b/src/shared/varlink-io.systemd.Machine.h new file mode 100644 index 00000000000..c9fc85f1507 --- /dev/null +++ b/src/shared/varlink-io.systemd.Machine.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "varlink-idl.h" + +extern const VarlinkInterface vl_interface_io_systemd_Machine;