From e92b724289120c262c635e6005703477ebaa3fe0 Mon Sep 17 00:00:00 2001 From: Radostin Stoyanov Date: Mon, 24 Jul 2017 09:14:01 +0100 Subject: Add remapping ownership of files in rootfs When Libvirt creates LXC container with enabled user namespace the ownership of files in the container should be mapped to the specified target UID/GID. The implementation of the mapping is inspired by the tool uidmapshift: http://bazaar.launchpad.net/%7Eserge-hallyn/+junk/nsexec/view/head:/uidmapshift.c Mapping values can be specified with the flags: --idmap Map both UIDs/GIDs --uidmap Map UIDs --gidmap Map GIDs Each of these flags can be specified multiple times. Example: virt-bootstrap docker://fedora /tmp/foo --uidmap 0:1000:10 --gidmap 0:1000:10 Will map the ownership of files with UIDs/GIDs: 0-9 to 1000-1009 The same result can be achived with: virt-bootstrap docker://fedora /tmp/foo --idmap 0:1000:10 Multiple mapping values can be specified as follows: virt_bootstrap.py docker://ubuntu /tmp/foo --idmap 0:1000:10 --idmap 500:1500:10 This will map the UID/GIDs: 0-9 to 1000-1009 and 500-509 to 1500-1509 --- src/virtBootstrap/virt_bootstrap.py | 127 ++++++++++++++++++++++++++++++++++-- 1 file changed, 122 insertions(+), 5 deletions(-) (limited to 'src/virtBootstrap/virt_bootstrap.py') diff --git a/src/virtBootstrap/virt_bootstrap.py b/src/virtBootstrap/virt_bootstrap.py index 3989a03..5506445 100755 --- a/src/virtBootstrap/virt_bootstrap.py +++ b/src/virtBootstrap/virt_bootstrap.py @@ -69,12 +69,108 @@ def get_source(source_type): raise Exception("Invalid image URL scheme: '%s'" % source_type) +# The implementation for remapping ownership of all files inside a +# container's rootfs is inspired by the tool uidmapshift: +# +# Original author: Serge Hallyn +# Original license: GPLv2 +# http://bazaar.launchpad.net/%7Eserge-hallyn/+junk/nsexec/view/head:/uidmapshift.c + +def get_map_id(old_id, opts): + """ + Calculate new map_id. + """ + if old_id >= opts['first'] and old_id < opts['last']: + return old_id + opts['offset'] + return -1 + + +def parse_idmap(idmap): + """ + Parse user input to 'start', 'target' and 'count' values. + + Example: + ['0:1000:10', '500:500:10'] + is converted to: + [[0, 1000, 10], [500, 500, 10]] + """ + if not isinstance(idmap, list) or idmap == []: + return None + try: + idmap_list = [] + for x in idmap: + start, target, count = x.split(':') + idmap_list.append([int(start), int(target), int(count)]) + + return idmap_list + except Exception: + raise ValueError("Invalid UID/GID mapping value: %s" % idmap) + + +def get_mapping_opts(mapping): + """ + Get range options from UID/GID mapping + """ + start = mapping[0] if mapping[0] > -1 else 0 + target = mapping[1] if mapping[1] > -1 else 0 + count = mapping[2] if mapping[2] > -1 else 1 + + opts = { + 'first': start, + 'last': start + count, + 'offset': target - start + } + return opts + + +def map_id(path, map_uid, map_gid): + """ + Remapping ownership of all files inside a container's rootfs. + + map_gid and map_uid: Contain integers in a list with format: + [, , ] + """ + if map_uid: + uid_opts = get_mapping_opts(map_uid) + if map_gid: + gid_opts = get_mapping_opts(map_gid) + + for root, _ignore, files in os.walk(os.path.realpath(path)): + for name in [root] + files: + file_path = os.path.join(root, name) + + stat_info = os.lstat(file_path) + old_uid = stat_info.st_uid + old_gid = stat_info.st_gid + + new_uid = get_map_id(old_uid, uid_opts) if map_uid else -1 + new_gid = get_map_id(old_gid, gid_opts) if map_gid else -1 + os.lchown(file_path, new_uid, new_gid) + + +def mapping_uid_gid(dest, uid_map, gid_map): + """ + Mapping ownership for each uid_map and gid_map. + """ + len_diff = len(uid_map) - len(gid_map) + + if len_diff < 0: + uid_map += [None] * abs(len_diff) + elif len_diff > 0: + gid_map += [None] * len_diff + + for uid, gid in zip(uid_map, gid_map): + map_id(dest, uid, gid) + + # pylint: disable=too-many-arguments def bootstrap(uri, dest, fmt='dir', username=None, password=None, root_password=None, + uid_map=None, + gid_map=None, not_secure=False, no_cache=False, progress_cb=None): @@ -104,9 +200,14 @@ def bootstrap(uri, dest, no_cache=no_cache, progress=prog).unpack(dest) - if fmt == "dir" and root_password is not None: - logger.info("Setting password of the root account") - utils.set_root_password(dest, root_password) + if fmt == "dir": + if root_password is not None: + logger.info("Setting password of the root account") + utils.set_root_password(dest, root_password) + + if uid_map or gid_map: + logger.info("Mapping UID/GID") + mapping_uid_gid(dest, uid_map, gid_map) def set_logging_conf(loglevel=None): @@ -156,6 +257,15 @@ def main(): help=_("Password for accessing the source registry")) parser.add_argument("--root-password", default=None, help=_("Set root password")) + parser.add_argument("--uidmap", default=None, action='append', + metavar="::", + help=_("Map UIDs")) + parser.add_argument("--gidmap", default=None, action='append', + metavar="::", + help=_("Map GIDs")) + parser.add_argument("--idmap", default=None, action='append', + metavar="::", + help=_("Map both UIDs/GIDs")) parser.add_argument("--no-cache", action="store_true", help=_("Do not store downloaded Docker images")) parser.add_argument("-f", "--format", default='dir', @@ -170,8 +280,6 @@ def main(): const=utils.write_progress, help=_("Show only progress information")) - # TODO add UID / GID mapping parameters - try: args = parser.parse_args() @@ -179,6 +287,13 @@ def main(): # Configure logging lovel/format set_logging_conf(args.loglevel) + uid_map = parse_idmap(args.idmap) if args.idmap else [] + gid_map = list(uid_map) if args.idmap else [] + if args.uidmap: + uid_map += parse_idmap(args.uidmap) + if args.gidmap: + gid_map += parse_idmap(args.gidmap) + # do the job here! bootstrap(uri=args.uri, dest=args.dest, @@ -186,6 +301,8 @@ def main(): username=args.username, password=args.password, root_password=args.root_password, + uid_map=uid_map, + gid_map=gid_map, not_secure=args.not_secure, no_cache=args.no_cache, progress_cb=args.status_only) -- cgit