Package: autopkgtest Version: 4.2 Severity: wishlist Tags: patch Hi Martin,
in the context of #833407 I told you about my plan of adding a virtualization backend which would allow completely unprivileged chroot operation by using linux user namespaces. In contrast to what I thought was required back then, I now managed to write that backend using just lxc-usernsexec and lxc-unshare. Thus, I was able to get it to work using the existing Python modules. You can find the script attached. As you can see, it is extremely simple, which I find makes the beauty of it all. All you need is: - the lxc package installed for lxc-usernsexec and lxc-unshare - sbuild from git (a tiny fix to its autopkgtest backend is required) - autopkgtest - a tarball as it is created by sbuild-createchroot for schroot - the attached virtualization backend as /usr/bin/autopkgtest-virt-uchroot Then you can do: $ sbuild --chroot-mode=autopkgtest --autopkgtest-virt-server=uchroot \ --autopkgtest-virt-server-opts="-- /srv/chroot/%r-%a-sbuild.tar.gz /tmp/rootfs" By putting these arguments into your ~/.sbuildrc the above call can be reduced to just running "sbuild". The string /srv/chroot/%r-%a.tar.gz will resolve to, for example, /srv/chroot/unstable-amd64-sbuild.tar.gz which is a chroot as created by sbuild-createchroot. Using the script from #829134, this tarball can also be created without superuser privileges and I might thus add this script to sbuild-createchroot as well, for unprivileged tarball generation. The path /tmp/rootfs is the path that the rootfs will be extracted to and can be at any location that the user has access to. I called the backend uchroot because schroot is chroot with _s_uid. So uchroot is a chroot as a _u_ser. I don't think there is an existing backend which allows unprivileged package building with so little overhead in terms of configuration. The only two inputs are the chroot tarball and the location to extract it to. It would be great if this backend could be added to autopkgtest itself. If you think that it is not a good fit for autopkgtest, then I can maintain it in a separate package. What do you think? Thanks! cheers, josch
#!/usr/bin/python3 # # autopkgtest-virt-uchroot is part of autopkgtest # autopkgtest is a tool for testing Debian binary packages # # autopkgtest is Copyright (C) 2006-2007 Canonical Ltd. # autopkgtest-virt-uchroot is Copyright (C) 2016 Johannes Schauer # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. # # See the file CREDITS for a full list of credits information (often # installed as /usr/share/doc/autopkgtest/CREDITS). import sys import os import argparse import shlex import stat sys.path.insert(0, '/usr/share/autopkgtest/lib') sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname( os.path.abspath(__file__))), 'lib')) import VirtSubproc import adtlog tarball = None rootdir = None def parse_args(): global tarball, rootdir parser = argparse.ArgumentParser() parser.add_argument('-d', '--debug', action='store_true', help='Enable debugging output') parser.add_argument('tarball', help='path to rootfs tarball') parser.add_argument('rootdir', help='path to extract the rootfs') args = parser.parse_args() tarball = args.tarball rootdir = args.rootdir if args.debug: adtlog.verbosity = 2 def hook_open(): global tarball, rootdir # We want to find out our user and group id inside the chroot but we want # to avoid having to parse /etc/subuid and /etc/subgid. We solve the # situation by creating a temporary file from inside the user namespace # and then checking its user and group ids from outside the user namespace. probe = VirtSubproc.check_exec(['lxc-usernsexec', 'mktemp', '/tmp/uchroot.XXXXXX'], outp=True) inner_uid = os.stat(probe)[stat.ST_UID] inner_gid = os.stat(probe)[stat.ST_GID] VirtSubproc.check_exec(['lxc-usernsexec', 'rm', probe]) outer_uid = os.getuid() outer_gid = os.getgid() # Make sure that the target directory exists. os.makedirs(rootdir) # Change its ownership to be root inside the user namespace. VirtSubproc.check_exec(['lxc-usernsexec', '-m', 'u:0:%d:1' % outer_uid, '-m', 'g:0:%d:1' % outer_gid, '-m', 'u:1:%d:1' % inner_uid, '-m', 'g:1:%d:1' % inner_gid, '--', 'chown', '1:1', rootdir]) # Unpack the tarball into the new directory. # Make sure not to extract any character special files because we cannot # mknod. VirtSubproc.check_exec(['lxc-usernsexec', '--', 'tar', '--exclude=./dev/urandom', '--exclude=./dev/random', '--exclude=./dev/full', '--exclude=./dev/null', '--exclude=./dev/zero', '--exclude=./dev/tty', '--directory', rootdir, '--extract', '--file', tarball]) # A shell script that prepares the environment by bind-mounting all the # important things. # The chmod is done such that somebody accidentally using the chroot # without the right bind-mounts will not fill up their disk. shellcommand = """ mkdir -p {rootdir}/dev touch {rootdir}/dev/null chmod -rwx {rootdir}/dev/null mount -o bind /dev/null {rootdir}/dev/null touch {rootdir}/dev/zero chmod -rwx {rootdir}/dev/zero mount -o bind /dev/zero {rootdir}/dev/zero touch {rootdir}/dev/full chmod -rwx {rootdir}/dev/full mount -o bind /dev/full {rootdir}/dev/full touch {rootdir}/dev/random chmod -rwx {rootdir}/dev/random mount -o bind /dev/random {rootdir}/dev/random touch {rootdir}/dev/urandom chmod -rwx {rootdir}/dev/urandom mount -o bind /dev/urandom {rootdir}/dev/urandom touch {rootdir}/dev/tty chmod -rwx {rootdir}/dev/tty mount -o bind /dev/tty {rootdir}/dev/tty mkdir -p {rootdir}/sys mount -o rbind /sys {rootdir}/sys mkdir -p {rootdir}/proc mount -t proc proc {rootdir}/proc export PATH=$PATH:/usr/sbin:/sbin exec chroot {rootdir} "$@" """.format(rootdir=shlex.quote(rootdir)) VirtSubproc.auxverb = ['lxc-usernsexec', '--', 'lxc-unshare', '-s', 'MOUNT|PID|UTSNAME|IPC', '--', 'sh', '-c', shellcommand, '--'] # Test whether the auxverb is able to successfully run /bin/true status = VirtSubproc.execute_timeout(None, 5, VirtSubproc.auxverb + ['true'])[0] if status != 0: VirtSubproc.bomb('failed to connect to VM') def hook_downtmp(path): return VirtSubproc.downtmp_mktemp(path) def hook_revert(): hook_cleanup() hook_open() def hook_cleanup(): global rootdir VirtSubproc.check_exec(['lxc-usernsexec', '--', 'rm', '-rf', rootdir]) def hook_capabilities(): return ['revert', 'root-on-testbed'] parse_args() VirtSubproc.main()