On Mon, 2017-07-24 at 09:14 +0100, Radostin Stoyanov wrote: > 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(-) > > 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 <serge.hallyn@xxxxxxxxxx> > +# 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: > + [<start>, <target>, <count>] > + """ > + 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="<start>:<target>:<count>", > + help=_("Map UIDs")) > + parser.add_argument("--gidmap", default=None, action='append', > + metavar="<start>:<target>:<count>", > + help=_("Map GIDs")) > + parser.add_argument("--idmap", default=None, action='append', > + metavar="<start>:<target>:<count>", > + 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) ACK -- Cedric _______________________________________________ virt-tools-list mailing list virt-tools-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/virt-tools-list