machined: expose "UID shift" concept for containers

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.
This commit is contained in:
Lennart Poettering 2017-02-10 19:44:09 +01:00
parent f73e6ee687
commit 3401419bb8
7 changed files with 183 additions and 0 deletions

View 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),

View 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);

View 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",

View 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);

View 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);

View 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),

View File

@ -64,6 +64,10 @@
send_interface="org.freedesktop.machine1.Manager"
send_member="GetMachineOSRelease"/>
<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"/>
@ -148,6 +152,10 @@
send_interface="org.freedesktop.machine1.Machine"
send_member="GetOSRelease"/>
<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"/>