diff --git a/src/machine/machine-varlink.c b/src/machine/machine-varlink.c index 8ad3c87469..1d2814b26a 100644 --- a/src/machine/machine-varlink.c +++ b/src/machine/machine-varlink.c @@ -570,3 +570,161 @@ int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_met return sd_varlink_reply(link, v); } + +typedef struct MachineMapParameters { + const char *name; + PidRef pidref; + uid_t uid; + gid_t gid; +} MachineMapParameters; + +static void machine_map_paramaters_done(MachineMapParameters *p) { + assert(p); + pidref_done(&p->pidref); +} + +int vl_method_map_from(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + VARLINK_DISPATCH_MACHINE_LOOKUP_FIELDS(MachineOpenParameters), + { "uid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(MachineMapParameters, uid), 0 }, + { "gid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(MachineMapParameters, gid), 0 }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_(machine_map_paramaters_done) MachineMapParameters p = { + .pidref = PIDREF_NULL, + .uid = UID_INVALID, + .gid = GID_INVALID, + }; + uid_t converted_uid = UID_INVALID; + gid_t converted_gid = GID_INVALID; + Machine *machine; + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.uid != UID_INVALID && !uid_is_valid(p.uid)) + return sd_varlink_error_invalid_parameter_name(link, "uid"); + + if (p.gid != GID_INVALID && !gid_is_valid(p.gid)) + return sd_varlink_error_invalid_parameter_name(link, "gid"); + + r = lookup_machine_by_name_or_pidref(link, manager, p.name, &p.pidref, &machine); + if (r == -ESRCH) + return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); + if (r < 0) + return r; + + if (machine->class != MACHINE_CONTAINER) + return sd_varlink_error(link, "io.systemd.Machine.NotSupported", NULL); + + if (p.uid != UID_INVALID) { + r = machine_translate_uid(machine, p.uid, &converted_uid); + if (r == -ESRCH) + return sd_varlink_error(link, "io.systemd.Machine.NoSuchUser", NULL); + if (r < 0) + return log_debug_errno(r, "Failed to map uid=%u for machine '%s': %m", p.uid, machine->name); + } + + if (p.gid != UID_INVALID) { + r = machine_translate_gid(machine, p.gid, &converted_gid); + if (r == -ESRCH) + return sd_varlink_error(link, "io.systemd.Machine.NoSuchGroup", NULL); + if (r < 0) + return log_debug_errno(r, "Failed to map gid=%u for machine '%s': %m", p.gid, machine->name); + } + + r = sd_json_buildo(&v, + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("uid", converted_uid, UID_INVALID), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("gid", converted_gid, GID_INVALID)); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} + +int vl_method_map_to(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + static const sd_json_dispatch_field dispatch_table[] = { + { "uid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(MachineMapParameters, uid), 0 }, + { "gid", SD_JSON_VARIANT_UNSIGNED, sd_json_dispatch_uid_gid, offsetof(MachineMapParameters, gid), 0 }, + {} + }; + + Manager *manager = ASSERT_PTR(userdata); + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_(machine_map_paramaters_done) MachineMapParameters p = { + .pidref = PIDREF_NULL, + .uid = UID_INVALID, + .gid = GID_INVALID, + }; + Machine *machine_by_uid = NULL, *machine_by_gid = NULL; + uid_t converted_uid = UID_INVALID; + gid_t converted_gid = GID_INVALID; + const char *machine_name = NULL; + int r; + + assert(link); + assert(parameters); + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &p); + if (r != 0) + return r; + + if (p.uid != UID_INVALID) { + if (!uid_is_valid(p.uid)) + return sd_varlink_error_invalid_parameter_name(link, "uid"); + if (p.uid < 0x10000) + return sd_varlink_error(link, "io.systemd.Machine.UserInHostRange", NULL); + } + + if (p.gid != GID_INVALID) { + if (!gid_is_valid(p.gid)) + return sd_varlink_error_invalid_parameter_name(link, "gid"); + if (p.gid < 0x10000) + return sd_varlink_error(link, "io.systemd.Machine.GroupInHostRange", NULL); + } + + if (p.uid != UID_INVALID) { + r = manager_find_machine_for_uid(manager, p.uid, &machine_by_uid, &converted_uid); + if (r < 0) + return log_debug_errno(r, "Failed to find machine for uid=%u: %m", p.uid); + if (!r) + return sd_varlink_error(link, "io.systemd.Machine.NoSuchUser", NULL); + } + + if (p.gid != GID_INVALID) { + r = manager_find_machine_for_gid(manager, p.gid, &machine_by_gid, &converted_gid); + if (r < 0) + return log_debug_errno(r, "Failed to find machine for gid=%u: %m", p.gid); + if (!r) + return sd_varlink_error(link, "io.systemd.Machine.NoSuchGroup", NULL); + } + + if (machine_by_uid && machine_by_gid && machine_by_uid != machine_by_gid) { + log_debug_errno(SYNTHETIC_ERRNO(ESRCH), "Mapping of UID %u and GID %u resulted in two different machines", p.uid, p.gid); + return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); + } + + if (machine_by_uid) + machine_name = machine_by_uid->name; + else if (machine_by_gid) + machine_name = machine_by_gid->name; + else + return sd_varlink_error(link, "io.systemd.Machine.NoSuchMachine", NULL); + + r = sd_json_buildo(&v, + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("uid", converted_uid, UID_INVALID), + JSON_BUILD_PAIR_UNSIGNED_NOT_EQUAL("gid", converted_gid, GID_INVALID), + JSON_BUILD_PAIR_STRING_NON_EMPTY("machineName", machine_name)); + if (r < 0) + return r; + + return sd_varlink_reply(link, v); +} diff --git a/src/machine/machine-varlink.h b/src/machine/machine-varlink.h index 380f011dbe..984a8d8f3e 100644 --- a/src/machine/machine-varlink.h +++ b/src/machine/machine-varlink.h @@ -25,3 +25,5 @@ int vl_method_unregister_internal(sd_varlink *link, sd_json_variant *parameters, int vl_method_terminate_internal(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_kill(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); int vl_method_open(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_map_from(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); +int vl_method_map_to(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata); diff --git a/src/machine/machined-varlink.c b/src/machine/machined-varlink.c index 3385aa8a17..e0e27c4496 100644 --- a/src/machine/machined-varlink.c +++ b/src/machine/machined-varlink.c @@ -771,6 +771,8 @@ static int manager_varlink_init_machine(Manager *m) { "io.systemd.Machine.Terminate", vl_method_terminate, "io.systemd.Machine.Kill", vl_method_kill, "io.systemd.Machine.Open", vl_method_open, + "io.systemd.Machine.MapFrom", vl_method_map_from, + "io.systemd.Machine.MapTo", vl_method_map_to, "io.systemd.MachineImage.List", vl_method_list_images, "io.systemd.MachineImage.Update", vl_method_update_image, "io.systemd.MachineImage.Clone", vl_method_clone_image, diff --git a/src/shared/varlink-io.systemd.Machine.c b/src/shared/varlink-io.systemd.Machine.c index 83a20f4f0e..696d402002 100644 --- a/src/shared/varlink-io.systemd.Machine.c +++ b/src/shared/varlink-io.systemd.Machine.c @@ -122,6 +122,31 @@ static SD_VARLINK_DEFINE_METHOD( SD_VARLINK_FIELD_COMMENT("Path to the allocated pseudo TTY"), SD_VARLINK_DEFINE_OUTPUT(ptyPath, SD_VARLINK_STRING, 0)); +static SD_VARLINK_DEFINE_METHOD( + MapFrom, + VARLINK_DEFINE_MACHINE_LOOKUP_AND_POLKIT_INPUT_FIELDS, + SD_VARLINK_FIELD_COMMENT("UID in the machine to map to host UID"), + SD_VARLINK_DEFINE_INPUT(uid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("GID in the machine to map to host GID"), + SD_VARLINK_DEFINE_INPUT(gid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Mapped UID"), + SD_VARLINK_DEFINE_OUTPUT(uid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Mapped GID"), + SD_VARLINK_DEFINE_OUTPUT(gid, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + MapTo, + SD_VARLINK_FIELD_COMMENT("Host UID to map to machine UID"), + SD_VARLINK_DEFINE_INPUT(uid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Host GID to map to machine GID"), + SD_VARLINK_DEFINE_INPUT(gid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Mapped UID"), + SD_VARLINK_DEFINE_OUTPUT(uid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Mapped GID"), + SD_VARLINK_DEFINE_OUTPUT(gid, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Machine's name which owns mapped UID/GID"), + SD_VARLINK_DEFINE_OUTPUT(machineName, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + static SD_VARLINK_DEFINE_ERROR(NoSuchMachine); static SD_VARLINK_DEFINE_ERROR(MachineExists); static SD_VARLINK_DEFINE_ERROR(NoPrivateNetworking); @@ -130,6 +155,10 @@ static SD_VARLINK_DEFINE_ERROR(NoUIDShift); static SD_VARLINK_DEFINE_ERROR(NotAvailable); static SD_VARLINK_DEFINE_ERROR(NotSupported); static SD_VARLINK_DEFINE_ERROR(NoIPC); +static SD_VARLINK_DEFINE_ERROR(NoSuchUser); +static SD_VARLINK_DEFINE_ERROR(NoSuchGroup); +static SD_VARLINK_DEFINE_ERROR(UserInHostRange); +static SD_VARLINK_DEFINE_ERROR(GroupInHostRange); SD_VARLINK_DEFINE_INTERFACE( io_systemd_Machine, @@ -154,6 +183,10 @@ SD_VARLINK_DEFINE_INTERFACE( &vl_type_MachineOpenMode, SD_VARLINK_SYMBOL_COMMENT("Allocates a pseudo TTY in the container in various modes"), &vl_method_Open, + SD_VARLINK_SYMBOL_COMMENT("Maps given machine's UID/GID to host's UID/GID"), + &vl_method_MapFrom, + SD_VARLINK_SYMBOL_COMMENT("Maps given host's UID/GID to a machine and corresponding UID/GID"), + &vl_method_MapTo, SD_VARLINK_SYMBOL_COMMENT("No matching machine currently running"), &vl_error_NoSuchMachine, &vl_error_MachineExists, @@ -168,4 +201,12 @@ SD_VARLINK_DEFINE_INTERFACE( SD_VARLINK_SYMBOL_COMMENT("Requested operation is not supported"), &vl_error_NotSupported, SD_VARLINK_SYMBOL_COMMENT("There is no IPC service (such as system bus or varlink) in the container"), - &vl_error_NoIPC); + &vl_error_NoIPC, + SD_VARLINK_SYMBOL_COMMENT("No such user"), + &vl_error_NoSuchUser, + SD_VARLINK_SYMBOL_COMMENT("No such group"), + &vl_error_NoSuchGroup, + SD_VARLINK_SYMBOL_COMMENT("User belongs to host UID range"), + &vl_error_UserInHostRange, + SD_VARLINK_SYMBOL_COMMENT("Group belongs to host GID range"), + &vl_error_GroupInHostRange); diff --git a/test/units/TEST-13-NSPAWN.machined.sh b/test/units/TEST-13-NSPAWN.machined.sh index 59ba8c92db..85eb89b14d 100755 --- a/test/units/TEST-13-NSPAWN.machined.sh +++ b/test/units/TEST-13-NSPAWN.machined.sh @@ -357,12 +357,7 @@ TS="$(date '+%H:%M:%S')" (! varlinkctl --more call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"acquireMetadata": "yes"}') journalctl --sync (! journalctl -u systemd-machined.service --since="$TS" --grep 'Connection busy') -# terminate machines machinectl terminate container-without-os-release -machinectl terminate long-running -# wait for the container being stopped, otherwise acquiring image metadata by io.systemd.MachineImage.List may fail in the below. -timeout 30 bash -c "while machinectl status long-running &>/dev/null; do sleep .5; done" -systemctl kill --signal=KILL systemd-nspawn@long-running.service || : (ip addr show lo | grep -q 192.168.1.100) || ip address add 192.168.1.100/24 dev lo (! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.List '{"name": ".host"}' | grep 'addresses') @@ -386,11 +381,26 @@ varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "login"}' varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell"}' +# test io.systemd.Machine.MapFrom +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapFrom '{"name": "long-running", "uid":0, "gid": 0}' +container_uid=$(varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapFrom '{"name": "long-running", "uid":0}' | jq '.uid') +container_gid=$(varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapFrom '{"name": "long-running", "gid":0}' | jq '.gid') +# test io.systemd.Machine.MapTo +varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapTo "{\"uid\": $container_uid, \"gid\": $container_gid}" | grep "long-running" +(! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapTo '{"uid": 0}') +(! varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.MapTo '{"gid": 0}') + rm -f /tmp/none-existent-file varlinkctl call /run/systemd/machine/io.systemd.Machine io.systemd.Machine.Open '{"name": ".host", "mode": "shell", "user": "root", "path": "/bin/sh", "args": ["/bin/sh", "-c", "echo $FOO > /tmp/none-existent-file"], "environment": ["FOO=BAR"]}' timeout 30 bash -c "until test -e /tmp/none-existent-file; do sleep .5; done" grep -q "BAR" /tmp/none-existent-file +# terminate machines +machinectl terminate long-running +# wait for the container being stopped, otherwise acquiring image metadata by io.systemd.MachineImage.List may fail in the below. +timeout 10 bash -c "while machinectl status long-running &>/dev/null; do sleep .5; done" +systemctl kill --signal=KILL systemd-nspawn@long-running.service || : + # test io.systemd.MachineImage.List varlinkctl --more call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{}' | grep 'long-running' varlinkctl --more call /run/systemd/machine/io.systemd.MachineImage io.systemd.MachineImage.List '{}' | grep '.host'