diff --git a/TODO b/TODO index 92169dea0bf..76f86b80005 100644 --- a/TODO +++ b/TODO @@ -242,13 +242,6 @@ Features: assert_ret(). Only export the stuff we are sure about, and keep some symbols internally where things are not clear whether we want other projects to use. -* machined: allow running in a per-user instance too, to allow unpriv - systemd-nspawn and systemd-vmspawn do something useful. (Alternatively: open - up system machined to unpriv client's registering their machines, and enforce - they come with some prefix or suffix that clarifies they are the - user's. i.e. when a user registers a machine it must be called - foobar. or so.). - * importd/…: define per-user dirs for container/VM images too. * add a new specifier to unit files that figures out the DDI the unit file is @@ -314,12 +307,8 @@ Features: to read them from. This way the data doesn't remain in the SMBIOS blob during runtime, but only in the credentials fs. -* machined: make machine registration available via varlink to simplify - nspawn/vmspawn, and to have an extensible way to register VM/machine metadata - -* ssh-proxy: add support for "ssh machine/foobar" to automatically connect to - machined registered machine "foobar". Requires updating machined to track CID - and unix-export dir of containers. +* machined: optionally track nspawn unix-export/ runtime for each machined, and + then update systemd-ssh-proxy so that it can connect to that. * add a new ExecStart= flag that inserts the configured user's shell as first word in the command line. (maybe use character '.'). Usecase: tool such as diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index 6f437c06c4e..5b3538d416f 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -5,6 +5,7 @@ #include "sd-id128.h" #include "sd-json.h" +#include "bus-polkit.h" #include "hostname-util.h" #include "json-util.h" #include "machine-varlink.h" @@ -126,16 +127,18 @@ int vl_method_register(Varlink *link, sd_json_variant *parameters, VarlinkMethod int r; static const sd_json_dispatch_field dispatch_table[] = { - { "name", SD_JSON_VARIANT_STRING, machine_name, offsetof(Machine, name), SD_JSON_MANDATORY }, - { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(Machine, id), 0 }, - { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, service), 0 }, - { "class", SD_JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), SD_JSON_MANDATORY }, - { "leader", SD_JSON_VARIANT_UNSIGNED, machine_leader, offsetof(Machine, leader), 0 }, - { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, root_directory), 0 }, - { "ifIndices", SD_JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 }, - { "vSockCid", SD_JSON_VARIANT_UNSIGNED, machine_cid, offsetof(Machine, vsock_cid), 0 }, - { "sshAddress", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, ssh_address), SD_JSON_STRICT }, - { "sshPrivateKeyPath", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, ssh_private_key_path), 0 }, + { "name", SD_JSON_VARIANT_STRING, machine_name, offsetof(Machine, name), SD_JSON_MANDATORY }, + { "id", SD_JSON_VARIANT_STRING, sd_json_dispatch_id128, offsetof(Machine, id), 0 }, + { "service", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, service), 0 }, + { "class", SD_JSON_VARIANT_STRING, dispatch_machine_class, offsetof(Machine, class), SD_JSON_MANDATORY }, + { "leader", SD_JSON_VARIANT_UNSIGNED, machine_leader, offsetof(Machine, leader), 0 }, + { "rootDirectory", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, root_directory), 0 }, + { "ifIndices", SD_JSON_VARIANT_ARRAY, machine_ifindices, 0, 0 }, + { "vSockCid", SD_JSON_VARIANT_UNSIGNED, machine_cid, offsetof(Machine, vsock_cid), 0 }, + { "sshAddress", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, offsetof(Machine, ssh_address), SD_JSON_STRICT }, + { "sshPrivateKeyPath", SD_JSON_VARIANT_STRING, json_dispatch_path, offsetof(Machine, ssh_private_key_path), 0 }, + { "allocateUnit", SD_JSON_VARIANT_BOOLEAN, sd_json_dispatch_stdbool, offsetof(Machine, allocate_unit), 0 }, + VARLINK_DISPATCH_POLKIT_FIELD, {} }; @@ -147,6 +150,16 @@ int vl_method_register(Varlink *link, sd_json_variant *parameters, VarlinkMethod if (r != 0) return r; + r = varlink_verify_polkit_async( + link, + manager->bus, + "org.freedesktop.machine1.create-machine", + (const char**) STRV_MAKE("name", machine->name, + "class", machine_class_to_string(machine->class)), + &manager->polkit_registry); + if (r <= 0) + return r; + if (!pidref_is_set(&machine->leader)) { r = varlink_get_peer_pidref(link, &machine->leader); if (r < 0) @@ -159,11 +172,13 @@ int vl_method_register(Varlink *link, sd_json_variant *parameters, VarlinkMethod if (r < 0) return r; - r = cg_pidref_get_unit(&machine->leader, &machine->unit); - if (r < 0) - return r; + if (!machine->allocate_unit) { + r = cg_pidref_get_unit(&machine->leader, &machine->unit); + if (r < 0) + return r; + } - r = machine_start(machine, NULL, NULL); + r = machine_start(machine, /* properties= */ NULL, /* error= */ NULL); if (r < 0) return r; diff --git a/src/machine/machine.h b/src/machine/machine.h index 8f1feda14be..157ac0bb6d9 100644 --- a/src/machine/machine.h +++ b/src/machine/machine.h @@ -57,6 +57,7 @@ struct Machine { bool started:1; bool stopping:1; bool referenced:1; + bool allocate_unit; sd_bus_message *create_message; diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 38acfe97cd7..7c74c0b1a24 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -512,7 +512,7 @@ static int manager_varlink_init_machine(Manager *m) { if (m->varlink_machine_server) return 0; - r = varlink_server_new(&s, VARLINK_SERVER_ROOT_ONLY|VARLINK_SERVER_INHERIT_USERDATA); + r = varlink_server_new(&s, VARLINK_SERVER_ACCOUNT_UID|VARLINK_SERVER_INHERIT_USERDATA); if (r < 0) return log_error_errno(r, "Failed to allocate varlink server object: %m"); diff --git a/src/machine/org.freedesktop.machine1.policy b/src/machine/org.freedesktop.machine1.policy index f031e4e4800..fe125ed0db3 100644 --- a/src/machine/org.freedesktop.machine1.policy +++ b/src/machine/org.freedesktop.machine1.policy @@ -91,6 +91,17 @@ org.freedesktop.login1.shell org.freedesktop.login1.login + + Create a local virtual machine or container + Authentication is required to create a local virtual machine or container. + + auth_admin + auth_admin + auth_admin_keep + + org.freedesktop.login1.shell org.freedesktop.login1.login + + Manage local virtual machine and container images Authentication is required to manage local virtual machine and container images. diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index ed428c6a0b0..6eec5a27a08 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1659,12 +1659,6 @@ static int verify_arguments(void) { SET_FLAG(arg_mount_settings, MOUNT_PRIVILEGED, arg_privileged); if (!arg_privileged) { - /* machined is not accessible to unpriv clients */ - if (arg_register) { - log_notice("Automatically implying --register=no, since machined is not accessible to unprivileged clients."); - arg_register = false; - } - if (!arg_private_network) { log_notice("Automatically implying --private-network, since mounting /sys/ in an unprivileged user namespaces requires network namespacing."); arg_private_network = true; @@ -5350,7 +5344,7 @@ static int run_container( } if (arg_register || !arg_keep_unit) { - if (arg_privileged) + if (arg_privileged || arg_register) r = sd_bus_default_system(&bus); else r = sd_bus_default_user(&bus); diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index b5f8f5c0751..4d935272735 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -14,7 +14,11 @@ static VARLINK_DEFINE_METHOD( 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_INPUT(sshPrivateKeyPath, VARLINK_STRING, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Controls whether to allocate a scope unit for the machine to register. If false, the client already took care of that and registered a service/scope specific to the machine."), + VARLINK_DEFINE_INPUT(allocateUnit, VARLINK_BOOL, VARLINK_NULLABLE), + VARLINK_FIELD_COMMENT("Whether to allow interactive authentication on this operation."), + VARLINK_DEFINE_INPUT(allowInteractiveAuthentication, VARLINK_BOOL, VARLINK_NULLABLE)); static VARLINK_DEFINE_STRUCT_TYPE( Timestamp, diff --git a/src/vmspawn/vmspawn-register.c b/src/vmspawn/vmspawn-register.c index 2140bba9cd8..124ee111b2d 100644 --- a/src/vmspawn/vmspawn-register.c +++ b/src/vmspawn/vmspawn-register.c @@ -22,7 +22,8 @@ int register_machine( const char *directory, unsigned cid, const char *address, - const char *key_path) { + const char *key_path, + bool keep_unit) { _cleanup_(varlink_unrefp) Varlink *vl = NULL; int r; @@ -70,7 +71,9 @@ int register_machine( SD_JSON_BUILD_PAIR_CONDITION(VSOCK_CID_IS_REGULAR(cid), "vSockCid", SD_JSON_BUILD_UNSIGNED(cid)), SD_JSON_BUILD_PAIR_CONDITION(!!directory, "rootDirectory", SD_JSON_BUILD_STRING(directory)), SD_JSON_BUILD_PAIR_CONDITION(!!address, "sshAddress", SD_JSON_BUILD_STRING(address)), - SD_JSON_BUILD_PAIR_CONDITION(!!key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(key_path))); + SD_JSON_BUILD_PAIR_CONDITION(!!key_path, "sshPrivateKeyPath", SD_JSON_BUILD_STRING(key_path)), + SD_JSON_BUILD_PAIR_CONDITION(isatty(STDIN_FILENO), "allowInteractiveAuthentication", SD_JSON_BUILD_BOOLEAN(true)), + SD_JSON_BUILD_PAIR_CONDITION(!keep_unit, "allocateUnit", SD_JSON_BUILD_BOOLEAN(true))); } int unregister_machine(sd_bus *bus, const char *machine_name) { diff --git a/src/vmspawn/vmspawn-register.h b/src/vmspawn/vmspawn-register.h index 69f56714b2b..d46e28c7925 100644 --- a/src/vmspawn/vmspawn-register.h +++ b/src/vmspawn/vmspawn-register.h @@ -11,5 +11,7 @@ int register_machine( const char *directory, unsigned cid, const char *address, - const char *key_path); + const char *key_path, + bool keep_unit); + int unregister_machine(sd_bus *bus, const char *machine_name); diff --git a/src/vmspawn/vmspawn.c b/src/vmspawn/vmspawn.c index 73e165feadd..05eb443bd08 100644 --- a/src/vmspawn/vmspawn.c +++ b/src/vmspawn/vmspawn.c @@ -107,6 +107,7 @@ static char *arg_forward_journal = NULL; static bool arg_runtime_directory_created = false; static bool arg_privileged = false; static bool arg_register = false; +static bool arg_keep_unit = false; static sd_id128_t arg_uuid = {}; static char **arg_kernel_cmdline_extra = NULL; static char **arg_extra_drives = NULL; @@ -170,6 +171,7 @@ static int help(void) { " --uuid=UUID Set a specific machine UUID for the VM\n" "\n%3$sProperties:%4$s\n" " --register=BOOLEAN Register VM with systemd-machined\n" + " --keep-unit Don't let systemd-machined allocate scope unit for us\n" "\n%3$sUser Namespacing:%4$s\n" " --private-users=UIDBASE[:NUIDS]\n" " Configure the UID/GID range to map into the\n" @@ -235,6 +237,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_NETWORK_USER_MODE, ARG_UUID, ARG_REGISTER, + ARG_KEEP_UNIT, ARG_BIND, ARG_BIND_RO, ARG_EXTRA_DRIVE, @@ -277,6 +280,7 @@ static int parse_argv(int argc, char *argv[]) { { "network-user-mode", no_argument, NULL, ARG_NETWORK_USER_MODE }, { "uuid", required_argument, NULL, ARG_UUID }, { "register", required_argument, NULL, ARG_REGISTER }, + { "keep-unit", no_argument, NULL, ARG_KEEP_UNIT }, { "bind", required_argument, NULL, ARG_BIND }, { "bind-ro", required_argument, NULL, ARG_BIND_RO }, { "extra-drive", required_argument, NULL, ARG_EXTRA_DRIVE }, @@ -443,6 +447,11 @@ static int parse_argv(int argc, char *argv[]) { r = parse_boolean_argument("--register=", optarg, &arg_register); if (r < 0) return r; + + break; + + case ARG_KEEP_UNIT: + arg_keep_unit = true; break; case ARG_BIND: @@ -2055,7 +2064,8 @@ static int run_virtual_machine(int kvm_device_fd, int vhost_device_fd) { arg_directory, child_cid, child_cid != VMADDR_CID_ANY ? vm_address : NULL, - ssh_private_key_path); + ssh_private_key_path, + arg_keep_unit); if (r < 0) return r; } @@ -2229,8 +2239,10 @@ static int verify_arguments(void) { if (!strv_isempty(arg_initrds) && !arg_linux) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --initrd= cannot be used without --linux=."); - if (arg_register && !arg_privileged) - return log_error_errno(SYNTHETIC_ERRNO(EPERM), "--register= requires root privileges, refusing."); + if (arg_keep_unit && arg_register && cg_pid_get_owner_uid(0, NULL) >= 0) + /* Save the user from accidentally registering either user-$SESSION.scope or user@.service. + * The latter is not technically a user session, but we don't need to labour the point. */ + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--keep-unit --register=yes may not be used when invoked from a user session."); return 0; } diff --git a/test/units/TEST-50-DISSECT.mountfsd.sh b/test/units/TEST-50-DISSECT.mountfsd.sh index 604a9dbf387..0ab1a610796 100755 --- a/test/units/TEST-50-DISSECT.mountfsd.sh +++ b/test/units/TEST-50-DISSECT.mountfsd.sh @@ -84,7 +84,7 @@ if ! systemd-detect-virt -c; then -p DelegateSubgroup=supervisor \ -p Environment=SYSTEMD_LOG_LEVEL=debug \ --wait -- \ - systemd-nspawn --keep-unit -i /var/tmp/unpriv.raw --read-only --pipe echo thisisatest >/tmp/unpriv.out2 + systemd-nspawn --keep-unit --register=no -i /var/tmp/unpriv.raw --read-only --pipe echo thisisatest >/tmp/unpriv.out2 echo thisisatest | cmp /tmp/unpriv.out2 - fi diff --git a/units/systemd-vmspawn@.service.in b/units/systemd-vmspawn@.service.in index 608002040cd..fc4522ddb81 100644 --- a/units/systemd-vmspawn@.service.in +++ b/units/systemd-vmspawn@.service.in @@ -16,7 +16,7 @@ After=network.target modprobe@tun.service RequiresMountsFor=/var/lib/machines/%i [Service] -ExecStart=systemd-vmspawn --quiet --network-tap --machine=%i +ExecStart=systemd-vmspawn --quiet --register=yes --keep-unit --network-tap --machine=%i KillMode=mixed Type=notify Slice=machine.slice