machined: expose "UID shift" concept for containers
authorLennart Poettering <lennart@poettering.net>
Fri, 10 Feb 2017 18:44:09 +0000 (19:44 +0100)
committerLennart Poettering <lennart@poettering.net>
Fri, 17 Feb 2017 09:22:28 +0000 (10:22 +0100)
UID/GID mapping with userns can be arbitrarily complex. Let's break this
down to a single admin-friendly parameter: let's expose the UID/GID
shift of a container via a new bus call for each container, and let's
show this as part of "machinectl status" if it is not 0.

This should work for pretty much all real-life full OS container setups
(i.e. the stuff machined is suppose to be useful for).  For everything
else we generate a clean error, clarifying that we can't expose the
mapping.

src/machine/machine-dbus.c
src/machine/machine-dbus.h
src/machine/machine.c
src/machine/machine.h
src/machine/machinectl.c
src/machine/machined-dbus.c
src/machine/org.freedesktop.machine1.conf

index af745b6..9c95c63 100644 (file)
@@ -1276,6 +1276,32 @@ int bus_machine_method_open_root_directory(sd_bus_message *message, void *userda
         return sd_bus_reply_method_return(message, "h", fd);
 }
 
+int bus_machine_method_get_uid_shift(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        Machine *m = userdata;
+        uid_t shift = 0;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        /* You wonder why this is a method and not a property? Well, properties are not supposed to return errors, but
+         * we kinda have to for this. */
+
+        if (m->class == MACHINE_HOST)
+                return sd_bus_reply_method_return(message, "u", UINT32_C(0));
+
+        if (m->class != MACHINE_CONTAINER)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "UID/GID shift may only be determined for container machines.");
+
+        r = machine_get_uid_shift(m, &shift);
+        if (r == -ENXIO)
+                return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Machine %s uses a complex UID/GID mapping, cannot determine shift", m->name);
+        if (r < 0)
+                return r;
+
+        return sd_bus_reply_method_return(message, "u", (uint32_t) shift);
+}
+
 const sd_bus_vtable machine_vtable[] = {
         SD_BUS_VTABLE_START(0),
         SD_BUS_PROPERTY("Name", "s", NULL, offsetof(Machine, name), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -1293,6 +1319,7 @@ const sd_bus_vtable machine_vtable[] = {
         SD_BUS_METHOD("Kill", "si", NULL, bus_machine_method_kill, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("GetAddresses", NULL, "a(iay)", bus_machine_method_get_addresses, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("GetOSRelease", NULL, "a{ss}", bus_machine_method_get_os_release, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("GetUIDShift", NULL, "u", bus_machine_method_get_uid_shift, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("OpenPTY", NULL, "hs", bus_machine_method_open_pty, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("OpenLogin", NULL, "hs", bus_machine_method_open_login, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("OpenShell", "ssasas", "hs", bus_machine_method_open_shell, SD_BUS_VTABLE_UNPRIVILEGED),
index c513783..2aa7b4c 100644 (file)
@@ -39,6 +39,7 @@ int bus_machine_method_open_shell(sd_bus_message *message, void *userdata, sd_bu
 int bus_machine_method_bind_mount(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_machine_method_copy(sd_bus_message *message, void *userdata, sd_bus_error *error);
 int bus_machine_method_open_root_directory(sd_bus_message *message, void *userdata, sd_bus_error *error);
+int bus_machine_method_get_uid_shift(sd_bus_message *message, void *userdata, sd_bus_error *error);
 
 int machine_send_signal(Machine *m, bool new_machine);
 int machine_send_create_reply(Machine *m, sd_bus_error *error);
index 067a7e2..d3433d9 100644 (file)
@@ -38,6 +38,7 @@
 #include "parse-util.h"
 #include "process-util.h"
 #include "special.h"
+#include "stdio-util.h"
 #include "string-table.h"
 #include "terminal-util.h"
 #include "unit-name.h"
@@ -604,6 +605,96 @@ void machine_release_unit(Machine *m) {
         m->unit = mfree(m->unit);
 }
 
+int machine_get_uid_shift(Machine *m, uid_t *ret) {
+        char p[strlen("/proc//uid_map") + DECIMAL_STR_MAX(pid_t) + 1];
+        uid_t uid_base, uid_shift, uid_range;
+        gid_t gid_base, gid_shift, gid_range;
+        _cleanup_fclose_ FILE *f = NULL;
+        int k;
+
+        assert(m);
+        assert(ret);
+
+        /* Return the base UID/GID of the specified machine. Note that this only works for containers with simple
+         * mappings. In most cases setups should be simple like this, and administrators should only care about the
+         * basic offset a container has relative to the host. This is what this function exposes.
+         *
+         * If we encounter any more complex mappings we politely refuse this with ENXIO. */
+
+        if (m->class == MACHINE_HOST) {
+                *ret = 0;
+                return 0;
+        }
+
+        if (m->class != MACHINE_CONTAINER)
+                return -EOPNOTSUPP;
+
+        xsprintf(p, "/proc/" PID_FMT "/uid_map", m->leader);
+        f = fopen(p, "re");
+        if (!f) {
+                if (errno == ENOENT) {
+                        /* If the file doesn't exist, user namespacing is off in the kernel, return a zero mapping hence. */
+                        *ret = 0;
+                        return 0;
+                }
+
+                return -errno;
+        }
+
+        /* Read the first line. There's at least one. */
+        errno = 0;
+        k = fscanf(f, UID_FMT " " UID_FMT " " UID_FMT "\n", &uid_base, &uid_shift, &uid_range);
+        if (k != 3) {
+                if (ferror(f))
+                        return -errno;
+
+                return -EBADMSG;
+        }
+
+        /* Not a mapping starting at 0? Then it's a complex mapping we can't expose here. */
+        if (uid_base != 0)
+                return -ENXIO;
+        /* Insist that at least the nobody user is mapped, everything else is weird, and hence complex, and we don't support it */
+        if (uid_range < (uid_t) 65534U)
+                return -ENXIO;
+
+        /* If there's more than one line, then we don't support this mapping. */
+        if (fgetc(f) != EOF)
+                return -ENXIO;
+
+        fclose(f);
+
+        xsprintf(p, "/proc/" PID_FMT "/gid_map", m->leader);
+        f = fopen(p, "re");
+        if (!f)
+                return -errno;
+
+        /* Read the first line. There's at least one. */
+        errno = 0;
+        k = fscanf(f, GID_FMT " " GID_FMT " " GID_FMT "\n", &gid_base, &gid_shift, &gid_range);
+        if (k != 3) {
+                if (ferror(f))
+                        return -errno;
+
+                return -EBADMSG;
+        }
+
+        /* If there's more than one line, then we don't support this file. */
+        if (fgetc(f) != EOF)
+                return -ENXIO;
+
+        /* If the UID and GID mapping doesn't match, we don't support this mapping. */
+        if (uid_base != (uid_t) gid_base)
+                return -ENXIO;
+        if (uid_shift != (uid_t) gid_shift)
+                return -ENXIO;
+        if (uid_range != (uid_t) gid_range)
+                return -ENXIO;
+
+        *ret = uid_shift;
+        return 0;
+}
+
 static const char* const machine_class_table[_MACHINE_CLASS_MAX] = {
         [MACHINE_CONTAINER] = "container",
         [MACHINE_VM] = "vm",
index e5d7536..6bdb204 100644 (file)
@@ -108,3 +108,5 @@ KillWho kill_who_from_string(const char *s) _pure_;
 
 int machine_openpt(Machine *m, int flags);
 int machine_open_terminal(Machine *m, const char *path, int mode);
+
+int machine_get_uid_shift(Machine *m, uid_t *ret);
index fe4f1b7..99be391 100644 (file)
@@ -611,6 +611,37 @@ static int print_os_release(sd_bus *bus, const char *method, const char *name, c
         return 0;
 }
 
+static int print_uid_shift(sd_bus *bus, const char *name) {
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+        _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+        uint32_t shift;
+        int r;
+
+        assert(bus);
+        assert(name);
+
+        r = sd_bus_call_method(bus,
+                               "org.freedesktop.machine1",
+                               "/org/freedesktop/machine1",
+                               "org.freedesktop.machine1.Manager",
+                               "GetMachineUIDShift",
+                               &error,
+                               &reply,
+                               "s", name);
+        if (r < 0)
+                return log_debug_errno(r, "Failed to query UID/GID shift: %s", bus_error_message(&error, r));
+
+        r = sd_bus_message_read(reply, "u", &shift);
+        if (r < 0)
+                return r;
+
+        if (shift == 0) /* Don't show trivial mappings */
+                return 0;
+
+        printf("       UID Shift: %" PRIu32 "\n", shift);
+        return 0;
+}
+
 typedef struct MachineStatusInfo {
         char *name;
         sd_id128_t id;
@@ -714,6 +745,8 @@ static void print_machine_status_info(sd_bus *bus, MachineStatusInfo *i) {
 
         print_os_release(bus, "GetMachineOSRelease", i->name, "\t      OS: ");
 
+        print_uid_shift(bus, i->name);
+
         if (i->unit) {
                 printf("\t    Unit: %s\n", i->unit);
                 show_unit_cgroup(bus, i->unit, i->leader);
index fd9e5b5..c9b92d2 100644 (file)
@@ -729,6 +729,26 @@ static int method_open_machine_root_directory(sd_bus_message *message, void *use
         return bus_machine_method_open_root_directory(message, machine, error);
 }
 
+static int method_get_machine_uid_shift(sd_bus_message *message, void *userdata, sd_bus_error *error) {
+        Manager *m = userdata;
+        Machine *machine;
+        const char *name;
+        int r;
+
+        assert(message);
+        assert(m);
+
+        r = sd_bus_message_read(message, "s", &name);
+        if (r < 0)
+                return r;
+
+        machine = hashmap_get(m->machines, name);
+        if (!machine)
+                return sd_bus_error_setf(error, BUS_ERROR_NO_SUCH_MACHINE, "No machine '%s' known", name);
+
+        return bus_machine_method_get_uid_shift(message, machine, error);
+}
+
 static int method_remove_image(sd_bus_message *message, void *userdata, sd_bus_error *error) {
         _cleanup_(image_unrefp) Image* i = NULL;
         const char *name;
@@ -1416,6 +1436,7 @@ const sd_bus_vtable manager_vtable[] = {
         SD_BUS_METHOD("CopyFromMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("CopyToMachine", "sss", NULL, method_copy_machine, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("OpenMachineRootDirectory", "s", "h", method_open_machine_root_directory, SD_BUS_VTABLE_UNPRIVILEGED),
+        SD_BUS_METHOD("GetMachineUIDShift", "s", "u", method_get_machine_uid_shift, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("RemoveImage", "s", NULL, method_remove_image, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("RenameImage", "ss", NULL, method_rename_image, SD_BUS_VTABLE_UNPRIVILEGED),
         SD_BUS_METHOD("CloneImage", "ssb", NULL, method_clone_image, SD_BUS_VTABLE_UNPRIVILEGED),
index 82ebfba..daa365a 100644 (file)
 
                 <allow send_destination="org.freedesktop.machine1"
                        send_interface="org.freedesktop.machine1.Manager"
+                       send_member="GetMachineUIDShift"/>
+
+                <allow send_destination="org.freedesktop.machine1"
+                       send_interface="org.freedesktop.machine1.Manager"
                        send_member="OpenMachineLogin"/>
 
                 <allow send_destination="org.freedesktop.machine1"
 
                 <allow send_destination="org.freedesktop.machine1"
                        send_interface="org.freedesktop.machine1.Machine"
+                       send_member="GetUIDShift"/>
+
+                <allow send_destination="org.freedesktop.machine1"
+                       send_interface="org.freedesktop.machine1.Machine"
                        send_member="OpenLogin"/>
 
                 <allow send_destination="org.freedesktop.machine1"