[reprotest] 01/01: Add the ability to vary the user (Closes: #872412)

Ximin Luo infinity0 at debian.org
Mon Aug 21 18:05:31 UTC 2017


This is an automated email from the git hooks/post-receive script.

infinity0 pushed a commit to branch master
in repository reprotest.

commit 15d986f43fc225298a352ef042e6d15ed16792a1
Author: Ximin Luo <infinity0 at debian.org>
Date:   Mon Aug 21 20:04:00 2017 +0200

    Add the ability to vary the user (Closes: #872412)
---
 README.rst            | 33 ++++++++++++++++++++++
 debian/control        |  6 +++-
 reprotest/__init__.py | 77 ++++++++++++++++++++++++++++++++++++++++-----------
 reprotest/presets.py  |  2 +-
 4 files changed, 100 insertions(+), 18 deletions(-)

diff --git a/README.rst b/README.rst
index a4be817..f6e6d3e 100644
--- a/README.rst
+++ b/README.rst
@@ -188,6 +188,39 @@ This flag is already set in our presets, in the situations where it is
 appropriate to do so.
 
 
+Varying the user
+================
+
+If you also vary fileordering at the same time, each user you use needs to be
+in the "fuse" group. Do that by running `usermod -aG fuse $OTHERUSER` as root.
+
+Avoid sudo(1) password prompts
+------------------------------
+
+There is currently no good way to do this. The following is a very brittle and
+unclean solution. You will have to decide for yourself if it's worth it for
+your use-case::
+
+    $ OTHERUSER=(YOUR OTHER USER HERE)
+    $ a="[a-zA-Z0-9]"
+    $ cat <<EOF | sudo tee -a /etc/sudoers.d/local-reprotest
+    $USER ALL = ($OTHERUSER) NOPASSWD: ALL
+    $USER ALL = NOPASSWD: /bin/chown -R --from=$OTHERUSER $USER /tmp/autopkgtest.$a$a$a$a$a$a/const_build_path/
+    $USER ALL = NOPASSWD: /bin/chown -R --from=$OTHERUSER $USER /tmp/autopkgtest.$a$a$a$a$a$a/experiment/
+    $USER ALL = NOPASSWD: /bin/chown -R --from=$USER $OTHERUSER /tmp/autopkgtest.$a$a$a$a$a$a/const_build_path/
+    $USER ALL = NOPASSWD: /bin/chown -R --from=$USER $OTHERUSER /tmp/autopkgtest.$a$a$a$a$a$a/experiment/
+    EOF
+
+Repeat this for each user you'd like to use. Obviously, don't pick a privileged
+user for this purpose, such as root.
+
+(Simplifying the above using wildcards would open up passwordless access to
+chown anything on your system, because wildcards here match whitespace. I don't
+know what the sudo authors were thinking.)
+
+No, this is really not nice at all - suggestions and patches welcome.
+
+
 Known bugs
 ==========
 
diff --git a/debian/control b/debian/control
index 7c7ad38..72528aa 100644
--- a/debian/control
+++ b/debian/control
@@ -37,7 +37,11 @@ Depends: ${python3:Depends},
  procps,
  python3-pkg-resources,
  ${misc:Depends}
-Recommends: diffutils | diffoscope (>= 84), disorderfs, locales-all, faketime
+Recommends: diffutils | diffoscope (>= 84),
+ disorderfs,
+ faketime,
+ locales-all,
+ sudo,
 Suggests: autodep8, schroot, qemu-system, qemu-utils
 Description: Build software and check it for reproducibility.
  reprotest builds the same source code twice in different environments, and
diff --git a/reprotest/__init__.py b/reprotest/__init__.py
index 06fe272..7a296d4 100644
--- a/reprotest/__init__.py
+++ b/reprotest/__init__.py
@@ -4,6 +4,8 @@
 import argparse
 import collections
 import configparser
+import getpass
+import grp
 import logging
 import os
 import pathlib
@@ -138,7 +140,10 @@ class Script(collections.namedtuple('_Script', 'build_command setup cleanup')):
         return self._replace(setup=new_setup)
 
     def append_setup_exec(self, *args):
-        return self.append_setup(_shell_ast.SimpleCommand.make(*map(_shell_ast.Quote, args)))
+        return self.append_setup_exec_raw(*map(_shell_ast.Quote, args))
+
+    def append_setup_exec_raw(self, *args):
+        return self.append_setup(_shell_ast.SimpleCommand.make(*args))
 
     def prepend_cleanup(self, command):
         '''Adds a command to the cleanup phase.
@@ -152,7 +157,10 @@ class Script(collections.namedtuple('_Script', 'build_command setup cleanup')):
         return self._replace(cleanup=new_cleanup)
 
     def prepend_cleanup_exec(self, *args):
-        return self.prepend_cleanup(_shell_ast.SimpleCommand.make(*map(_shell_ast.Quote, args)))
+        return self.prepend_cleanup_exec_raw(*map(_shell_ast.Quote, args))
+
+    def prepend_cleanup_exec_raw(self, *args):
+        return self.prepend_cleanup(_shell_ast.SimpleCommand.make(*args))
 
     def move_tree(self, source, target):
         return self.append_setup_exec(
@@ -234,20 +242,17 @@ build_path_same.negative = True
 
 def fileordering(script, env, tree, *args):
     old_tree = os.path.join(dirname(tree.experiment), basename(tree.experiment) + '-before-disorderfs', '')
-    disorderfs = ['sh', '-ec',
-        'disorderfs --shuffle-dirents=yes --multi-user="$(if [ $(id -u) = 0 ]; then echo yes; else echo no; fi)" "$@"',
-        '-', old_tree, tree.experiment]
     _ = script.experiment.move_tree(tree.experiment, old_tree)
     _ = _.append_setup_exec('mkdir', '-p', tree.experiment)
     _ = _.prepend_cleanup_exec('rmdir', tree.experiment)
-    _ = _.append_setup_exec(*disorderfs)
+    _ = _.append_setup_exec('disorderfs', '--shuffle-dirents=yes', old_tree, tree.experiment)
     _ = _.prepend_cleanup_exec('fusermount', '-u', tree.experiment)
+    # the "user_group" variation hacks PATH to run "sudo -u XXX" instead of various tools, pick it up here
+    binpath = os.path.join(dirname(tree.experiment), 'bin')
+    _ = _.prepend_cleanup_exec_raw('export', 'PATH="%s:$PATH"' % binpath)
     new_script = _
     return Pair(script.control, new_script), env, tree
 
-# # def fileordering(script, env, tree):
-#     return script, env, tree
-
 # Note: this has to go after anything that might modify 'tree' e.g. build_path
 def home(script, env, tree, *args):
     # choose an existent HOME, see Debian bug #860428
@@ -343,9 +348,39 @@ def umask(script, env, tree, *args):
     new_experiment = script.experiment.append_setup_exec('umask', '0002')
     return Pair(new_control, new_experiment), env, tree
 
-# TODO: This requires superuser privileges.
-# # def user_group(script, env, tree, *args):
-#     return script, env, tree
+# Note: this needs to go before anything that might need to run setup commands
+# as the other user (e.g. due to permissions).
+#
+# TODO: add a default Debian-reprotest user in the Debian packaging and make this cross-distro
+V_USER_POSSIBLE_USERGROUPS = []
+def user_group(script, env, tree, *args):
+    if not V_USER_POSSIBLE_USERGROUPS:
+        logging.warn("IGNORING user_group variation, because no --user-groups were given.")
+        return script, env, tree
+    olduser = getpass.getuser()
+    oldgroup = grp.getgrgid(os.getgid()).gr_name
+    user, group = random.choice(list(set(V_USER_POSSIBLE_USERGROUPS) - set([(olduser, oldgroup)])))
+    sudobuild = _shell_ast.SimpleCommand.make('sudo', '-E', '-u', user, '-g', group)
+    binpath = os.path.join(dirname(tree.experiment), 'bin')
+
+    _ = script.experiment.append_command(sudobuild)
+    # disorderfs needs to run as a different user.
+    # we prefer that to running it as root, principle of least-privilege.
+    _ = _.append_setup_exec('sh', '-ec', r'''
+mkdir "{0}"
+printf '#!/bin/sh\nsudo -u "{1}" -g "{2}" /usr/bin/disorderfs "$@"\n' > "{0}"/disorderfs
+chmod +x "{0}"/disorderfs
+printf '#!/bin/sh\nsudo -u "{1}" -g "{2}" /bin/mkdir "$@"\n' > "{0}"/mkdir
+chmod +x "{0}"/mkdir
+printf '#!/bin/sh\nsudo -u "{1}" -g "{2}" /bin/fusermount "$@"\n' > "{0}"/fusermount
+chmod +x "{0}"/fusermount
+'''.format(binpath, user, group))
+    _ = _.append_setup_exec_raw('export', 'PATH="%s:$PATH"' % binpath)
+    _ = _.append_setup_exec('sudo', 'chown', '-R', '--from=%s' % olduser, user, tree.experiment)
+    # TODO: artifacts probably shouldn't be chown'd back
+    _ = _.prepend_cleanup_exec('sudo', 'chown', '-R', '--from=%s' % user, olduser, tree.experiment)
+    new_experiment = _
+    return Pair(script.control, new_experiment), env, tree
 
 
 # The order of the variations *is* important, because the command to
@@ -353,6 +388,7 @@ def umask(script, env, tree, *args):
 VARIATIONS = types.MappingProxyType(collections.OrderedDict([
     ('environment', environment),
     ('build_path', build_path_same),
+    ('user_group', user_group),
     # ('cpu', cpu),
     # ('domain_host', domain_host),
     ('fileordering', fileordering),
@@ -365,7 +401,6 @@ VARIATIONS = types.MappingProxyType(collections.OrderedDict([
     ('time', faketime),
     ('timezone', timezone),
     ('umask', umask),
-    # ('user_group', user_group),
 ]))
 
 
@@ -379,7 +414,8 @@ def build(script, env, source_root_orig, source_root_build, dist_root, artifact_
     testbed.check_exec(
         ['sh', '-ec', 'cd "%s" && rm -rf %s' %
         (source_root_orig, artifact_pattern)])
-    new_script = script.append_setup_exec('cd', source_root_build)
+    # this dance is necessary because the cwd can't be cd'd into during the setup phase under some variations like user_group
+    new_script = script.append_setup_exec_raw('export', 'REPROTEST_BUILD_PATH=%s' % source_root_build)
     logging.info("executing: %s", new_script)
     argv = ['sh', '-ec', str(new_script)]
     xenv = ['%s=%s' % (k, v) for k, v in env.items()]
@@ -421,7 +457,7 @@ def check(build_command, artifact_pattern, virtual_server_args, source_root,
                      os.path.join(store_dir, "experiment"))
 
     logging.debug("virtual_server_args: %r", virtual_server_args)
-    script = Pair.of(Script(build_command))
+    script = Pair.of(Script('cd "$REPROTEST_BUILD_PATH"; unset REPROTEST_BUILD_PATH; ' + build_command))
     env = Pair(types.MappingProxyType(os.environ.copy()),
                types.MappingProxyType(os.environ.copy()))
 
@@ -453,8 +489,8 @@ def check(build_command, artifact_pattern, virtual_server_args, source_root,
                 vary = VARIATIONS[variation]
                 negative = hasattr(vary, "negative") and vary.negative
                 if (variation in variations) != negative:
-                    script, env, tree = vary(script, env, tree, source_root)
                     logging.info("will %s: %s", "FIX" if negative else "vary", variation)
+                    script, env, tree = vary(script, env, tree, source_root)
                     logging.log(5, "builds: %r", (script, env, tree))
 
             try:
@@ -581,6 +617,11 @@ COMMAND_LINE_OPTIONS = types.MappingProxyType(collections.OrderedDict([
         'list (without spaces).  These take precedence over what '
         'you set for "variations". Default is nothing, i.e. test '
         'whatever you set for "variations".'})),
+    ('--user-groups', types.MappingProxyType({
+        'type': lambda s: frozenset(tuple(x.split(':',1)) for x in s.split(',')),
+        'default': frozenset(),
+        'help': 'Comma-separated list of possible user:group combinations which '
+        'will be randomly chosen to `sudo -i` to when varying user_group.'})),
     ('--diffoscope-arg', types.MappingProxyType({
         'default': [], 'action': 'append',
         'help': 'Give extra arguments to diffoscope when running it.'})),
@@ -699,6 +740,10 @@ def main():
         variations = command_line_options['variations']
     if 'dont_vary' in command_line_options:
         variations = variations - frozenset(command_line_options['dont_vary'])
+    user_groups = command_line_options.get(
+        'user_groups',
+        config_options.get('user_groups'))
+    V_USER_POSSIBLE_USERGROUPS.extend(user_groups)
     verbosity = command_line_options.get(
         'verbosity',
         config_options.get('verbosity', 0))
diff --git a/reprotest/presets.py b/reprotest/presets.py
index ccf8580..1a8c703 100644
--- a/reprotest/presets.py
+++ b/reprotest/presets.py
@@ -68,7 +68,7 @@ def preset_deb_schroot(preset):
     return preset.str_replace.build_command("dpkg-buildpackage",
         'PATH=/sbin:/usr/sbin:$PATH apt-get -y --no-install-recommends build-dep ./; dpkg-buildpackage'
     ).set.testbed_init(
-        'apt-get -y --no-install-recommends install util-linux disorderfs faketime locales-all 2>/dev/null; \
+        'apt-get -y --no-install-recommends install disorderfs faketime locales-all sudo util-linux 2>/dev/null; \
         test -c /dev/fuse || mknod -m 666 /dev/fuse c 10 229'
     )
 

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/reproducible/reprotest.git



More information about the Reproducible-commits mailing list