]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
machined: add varlink interface for registering machines 32709/head
authorSam Leonard <sam.leonard@codethink.co.uk>
Tue, 23 Apr 2024 09:26:59 +0000 (10:26 +0100)
committerLuca Boccassi <bluca@debian.org>
Wed, 8 May 2024 10:54:31 +0000 (11:54 +0100)
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.

man/systemd-machined.service.xml
src/machine/machine-varlink.c [new file with mode: 0644]
src/machine/machine-varlink.h [new file with mode: 0644]
src/machine/machined-varlink.c
src/machine/machined.c
src/machine/machined.h
src/machine/meson.build
src/shared/meson.build
src/shared/varlink-io.systemd.Machine.c [new file with mode: 0644]
src/shared/varlink-io.systemd.Machine.h [new file with mode: 0644]

index f3d7755973316da8b2917480a326a26349de085d..b2899ff0fd552b43b683b5d60ea147c419868899 100644 (file)
 
     <para>The daemon provides both a C library interface
     (which is shared with <citerefentry><refentrytitle>systemd-logind.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>)
-    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
+    <command>varlinkctl introspect /run/systemd/machine/io.systemd.Machine io.systemd.Machine</command>.
     For more information please consult
     <citerefentry><refentrytitle>sd-login</refentrytitle><manvolnum>3</manvolnum></citerefentry>
     and
diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c
new file mode 100644 (file)
index 0000000..377b3d3
--- /dev/null
@@ -0,0 +1,171 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <limits.h>
+
+#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 (file)
index 0000000..ce4ec54
--- /dev/null
@@ -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);
index 6ca98e27cf4288147af915837f6604e135ba0649..0d3ae627c1f662e8d9b088285303eda717945cb4 100644 (file)
@@ -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);
 }
index 2638ed572ed93cbb79b5aa4d886accc632ded9b4..d7087e4672c743c7c77c6f5b53b25d19e3419c76 100644 (file)
@@ -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);
index 280c32bab6890f7d270e5d735bdac254bc3021a2..67abed0fd67e2523b6df1d51b2680aff9fcb16b2 100644 (file)
@@ -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);
index c82a32589d0919c36811cc4ab77719bacdb353c8..3150b33de5748ca7de2494c5e164fbd11a7233bf 100644 (file)
@@ -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',
index 17313aefedc4cc939605ea88e7db44cf29aafd7d..d01367a159502c42af0b9b0c84e78f985e0eabb4 100644 (file)
@@ -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 (file)
index 0000000..936f01f
--- /dev/null
@@ -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 (file)
index 0000000..c9fc85f
--- /dev/null
@@ -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;