├── README.md └── run.py /README.md: -------------------------------------------------------------------------------- 1 | # Proxmox unprivileged container/host uid/gid mapping syntax tool 2 | 3 | ## What 4 | 5 | If running a Proxmox LXC container in unprivileged mode, and a container uid/gid to host uid/gid mapping is necessary, this tool will provide the correct syntax needed. It will not modify any files on its own. 6 | 7 | ## Why 8 | 9 | LXC uid/gid mapping syntax is difficult to understand. Any error results in non-functional or hung containers. Logging output isn't particularly helpful. Some people may just run privileged containers rather than grok the syntax, unnecessarily sacrificing security. 10 | 11 | ## Requirements 12 | 13 | Python 2.7+ / 3.7+ 14 | 15 | ### Usage 16 | 17 | ```bash 18 | usage: run.py [-h] 19 | containeruid[:containergid][=hostuid[:hostgid]] [containeruid[:containergid][=hostuid[:hostgid]] ...] 20 | 21 | Proxmox unprivileged container to host uid:gid mapping syntax tool. 22 | 23 | positional arguments: 24 | containeruid[:containergid][=hostuid[:hostgid]] 25 | Container uid and optional gid to map to host. If a gid is not specified, the uid will be used 26 | for the gid value. 27 | 28 | optional arguments: 29 | -h, --help show this help message and exit 30 | ``` 31 | 32 | ### Example 33 | 34 | ```bash 35 | $ ./run.py 1000=1005 1005=1001 36 | 37 | # Add to /etc/pve/lxc/.conf: 38 | lxc.idmap: u 0 100000 1000 39 | lxc.idmap: g 0 100000 1000 40 | lxc.idmap: u 1000 1005 1 41 | lxc.idmap: g 1000 1005 1 42 | lxc.idmap: u 1001 101001 4 43 | lxc.idmap: g 1001 101001 4 44 | lxc.idmap: u 1005 1001 1 45 | lxc.idmap: g 1005 1001 1 46 | lxc.idmap: u 1006 101006 64530 47 | lxc.idmap: g 1006 101006 64530 48 | 49 | # Add to /etc/subuid: 50 | root:1005:1 51 | root:1001:1 52 | 53 | # Add to /etc/subgid: 54 | root:1005:1 55 | root:1001:1 56 | ``` 57 | -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import argparse 4 | 5 | # validates user input 6 | def parser_validate(value, min = 1, max = 65535): 7 | (container, host) = value.split('=') if '=' in value else (value, value) 8 | (containeruid, containergid) = container.split(':') if ':' in container else (container, container) 9 | (hostuid, hostgid) = host.split(':') if ':' in host else (host, host) 10 | 11 | if containeruid == "": 12 | containeruid = None 13 | if containergid == "": 14 | containergid = None 15 | if hostuid == "": 16 | hostuid = None 17 | if hostgid == "": 18 | hostgid = None 19 | 20 | if containeruid is not None and not containeruid.isdigit(): 21 | raise argparse.ArgumentTypeError('Container UID "%s" is not a number' % containeruid) 22 | elif containergid is not None and not containergid.isdigit(): 23 | raise argparse.ArgumentTypeError('Container GID "%s" is not a number' % containergid) 24 | elif containeruid is not None and not min <= int(containeruid) <= max: 25 | raise argparse.ArgumentTypeError('Container UID "%s" is not in range %s-%s' % (containeruid, min, max)) 26 | elif containergid is not None and not min <= int(containergid) <= max: 27 | raise argparse.ArgumentTypeError('Container GID "%s" is not in range %s-%s' % (containergid, min, max)) 28 | 29 | if hostuid is not None and not hostuid.isdigit(): 30 | raise argparse.ArgumentTypeError('Host UID "%s" is not a number' % hostuid) 31 | elif hostgid is not None and not hostgid.isdigit(): 32 | raise argparse.ArgumentTypeError('Host GID "%s" is not a number' % hostgid) 33 | elif hostuid is not None and not min <= int(hostuid) <= max: 34 | raise argparse.ArgumentTypeError('Host UID "%s" is not in range %s-%s' % (hostuid, min, max)) 35 | elif hostgid is not None and not min <= int(hostgid) <= max: 36 | raise argparse.ArgumentTypeError('Host GID "%s" is not in range %s-%s' % (hostgid, min, max)) 37 | else: 38 | return ( 39 | int(containeruid) if containeruid is not None else None, 40 | int(containergid) if containergid is not None else None, 41 | int(hostuid) if hostuid is not None else None, 42 | int(hostgid) if hostgid is not None else None 43 | ) 44 | 45 | # creates lxc mapping strings 46 | def create_map(id_type, id_list): 47 | ret = list() 48 | 49 | # Case where no uid/gid is specified 50 | if not id_list: 51 | ret.append('lxc.idmap: %s 0 100000 65536' % (id_type)) 52 | return(ret) 53 | 54 | for i, (containerid, hostid) in enumerate(id_list): 55 | if i == 0: 56 | ret.append('lxc.idmap: %s 0 100000 %s' % (id_type, containerid)) 57 | elif id_list[i][0] != id_list[i-1][0] + 1: 58 | range = (id_list[i-1][0] + 1, id_list[i-1][0] + 100001, (containerid - 1) - id_list[i-1][0]) 59 | ret.append('lxc.idmap: %s %s %s %s' % (id_type, range[0], range[1], range[2])) 60 | 61 | ret.append('lxc.idmap: %s %s %s 1' % (id_type, containerid, hostid)) 62 | 63 | if i is len(id_list) - 1: 64 | range = (containerid + 1, containerid + 100001, 65535 - containerid) 65 | ret.append('lxc.idmap: %s %s %s %s' % (id_type, range[0], range[1], range[2])) 66 | 67 | return(ret) 68 | 69 | # collect user input 70 | parser = argparse.ArgumentParser(description='Proxmox unprivileged container to host uid:gid mapping syntax tool.') 71 | parser.add_argument('id', nargs = '+', type = parser_validate, metavar='containeruid[:containergid][=hostuid[:hostgid]]', help = 'Container uid and optional gid to map to host. If a gid is not specified, the uid will be used for the gid value.') 72 | parser_args = parser.parse_args() 73 | 74 | # create sorted uid/gid lists 75 | uid_list = sorted([(i[0], i[2]) for i in parser_args.id if i[0] is not None], key=lambda tup: tup[0]) 76 | gid_list = sorted([(i[1], i[3]) for i in parser_args.id if i[1] is not None], key=lambda tup: tup[0]) 77 | 78 | # calls function that creates mapping strings 79 | uid_map = create_map('u', uid_list) 80 | gid_map = create_map('g', gid_list) 81 | 82 | # output mapping strings 83 | print('\n# Add to /etc/pve/lxc/.conf:') 84 | for i in enumerate(uid_map): 85 | print(uid_map[i[0]]) 86 | for i in enumerate(gid_map): 87 | print(gid_map[i[0]]) 88 | 89 | if uid_list: 90 | print('\n# Add to /etc/subuid:') 91 | for uid in uid_list: 92 | print('root:%s:1' % uid[1]) 93 | 94 | if gid_list: 95 | print('\n# Add to /etc/subgid:') 96 | for gid in gid_list: 97 | print('root:%s:1' %gid[1]) 98 | --------------------------------------------------------------------------------