[Piuparts-devel] [Git][debian/piuparts][helmutg/feature-nonroot] 7 commits: piuparts: reexecute via unshare when run as non-root

Nicolas Dandrimont (@olasd) gitlab at salsa.debian.org
Thu Nov 14 10:09:11 GMT 2024



Nicolas Dandrimont pushed to branch helmutg/feature-nonroot at Debian / piuparts


Commits:
37eb3270 by Helmut Grohne at 2024-11-14T11:09:01+01:00
piuparts: reexecute via unshare when run as non-root

When piuparts is run as root, it currently errors out and exits. Rather
than doing that, we now try reexecuting it inside an unshared namespace
since we recently made piuparts work when run in an unprivileged user
namespace. This enables running piuparts as non-root out of the box.

- - - - -
c6cbcebc by Helmut Grohne at 2024-11-14T11:09:01+01:00
install piuparts to /usr/bin

Since we can now run piuparts as a regular user, install it in a place
where a user can run it.

- - - - -
6fff741b by Helmut Grohne at 2024-11-14T11:09:01+01:00
suggest installing uidmap

Unprivileged piuparts only works when uidmap is installed.

- - - - -
504c8654 by Helmut Grohne at 2024-11-14T11:09:01+01:00
piuparts.1: document what it takes run piuparts as non-root

Reported-by: Holger Levsen <holger at layer-acht.org>

- - - - -
4dad8cbe by Nicolas Dandrimont at 2024-11-14T11:09:01+01:00
Add autopkgtest for unshared piuparts

- - - - -
1e373449 by Helmut Grohne at 2024-11-14T11:09:01+01:00
attempt to fix unshared piuparts on gitlab runners

We get "Permission denied" while debootstrap tries to mount its proc
filesystem. I guess that we somehow fail the mnt_already_visible test in
fs/namespace.c. If that holds true, mounting an initial proc should
help.

- - - - -
bb303dd7 by Nicolas Dandrimont at 2024-11-14T11:09:01+01:00
skip unshare autopkgtest if --mount-proc is unavailable

The salsaci runners are running on docker, which shadows enough parts of
/proc that unshare is unable to remount it in the new namespace. Skip
the test if that's the case.

- - - - -


14 changed files:

- Makefile
- conf/piuparts-slave.sudoers
- debian/control
- debian/piuparts.install
- debian/piuparts.lintian-overrides
- debian/tests/all-python-versions
- debian/tests/control
- + debian/tests/unshare
- docs/README_pejacevic.txt
- docs/piuparts/piuparts.1.txt
- instances/piuparts.conf-template.pejacevic
- instances/piuparts.conf.anbe
- piuparts.py
- slave-bin/slave_stop.in


Changes:

=====================================
Makefile
=====================================
@@ -1,5 +1,5 @@
 prefix = /usr/local
-sbindir = $(prefix)/sbin
+bindir = $(prefix)/bin
 sharedir = $(prefix)/share
 mandir = $(sharedir)/man
 man1dir = $(mandir)/man1
@@ -30,7 +30,7 @@ define placeholder_substitution
 	-e 's/__PIUPARTS_VERSION__/$(version)/g' \
 	-e 's%@libdir@%$(libdir)%g' \
 	-e 's%@sharedir@%$(sharedir)%g' \
-	-e 's%@sbindir@%$(sbindir)%g' \
+	-e 's%@bindir@%$(bindir)%g' \
 	$< > $@
 endef
 
@@ -147,8 +147,8 @@ install-master: build-master-stamp install-common
 	#install -m 0644 known_problems/*.conf $(DESTDIR)$(etcdir)/piuparts/known_problems/
 
 install-slave: install-common
-	install -d $(DESTDIR)$(sbindir)
-	install -m 0755 piuparts $(DESTDIR)$(sbindir)/
+	install -d $(DESTDIR)$(bindir)
+	install -m 0755 piuparts $(DESTDIR)$(bindir)/
 
 	install -d $(DESTDIR)$(sharedir)/piuparts
 	install -m 0755 piuparts-slave $(DESTDIR)$(sharedir)/piuparts/


=====================================
conf/piuparts-slave.sudoers
=====================================
@@ -3,7 +3,7 @@
 #
 
 # The piuparts slave needs to handle chroots.
-piupartss	ALL = NOPASSWD: /usr/sbin/piuparts *, \
+piupartss	ALL = NOPASSWD: /usr/bin/piuparts *, \
 				/bin/umount /srv/piuparts.debian.org/tmp/tmp*, \
 				/usr/bin/test -f /srv/piuparts.debian.org/tmp/tmp*, \
 				/usr/bin/rm -rf --one-file-system /srv/piuparts.debian.org/tmp/tmp*


=====================================
debian/control
=====================================
@@ -45,6 +45,7 @@ Recommends:
 Suggests:
  schroot,
  docker.io,
+ uidmap,
 Description: .deb package installation, upgrading, and removal testing tool
  piuparts tests that .deb packages (as used by Debian) handle
  installation, upgrading, and removal correctly. It does this by


=====================================
debian/piuparts.install
=====================================
@@ -1,2 +1,2 @@
-/usr/sbin/piuparts
+/usr/bin/piuparts
 /etc/piuparts/scripts*


=====================================
debian/piuparts.lintian-overrides
=====================================
@@ -3,5 +3,5 @@ uses-dpkg-database-directly [etc/piuparts/scripts-debug-purge/post_remove_postrm
 uses-dpkg-database-directly [etc/piuparts/scripts-debug-remove/pre_remove_prerm_postrm_set-x]
 uses-dpkg-database-directly [etc/piuparts/scripts/post_remove_exceptions]
 uses-dpkg-database-directly [etc/piuparts/scripts/pre_remove_40_find_missing_md5sums]
-uses-dpkg-database-directly [usr/sbin/piuparts]
+uses-dpkg-database-directly [usr/bin/piuparts]
 debian-news-entry-has-unknown-version 0.45 [usr/share/doc/piuparts/NEWS.Debian.gz:1]


=====================================
debian/tests/all-python-versions
=====================================
@@ -13,7 +13,7 @@ cd "$AUTOPKGTEST_TMP"
 create_packages
 
 for pyvers in $(py3versions -vi); do
-    test_this "python$pyvers" -X dev /usr/sbin/piuparts t.deb
+    test_this "python$pyvers" -X dev /usr/bin/piuparts t.deb
 done
 
 exit 0


=====================================
debian/tests/control
=====================================
@@ -5,3 +5,7 @@ Restrictions: needs-root
 Tests: all-python-versions
 Depends: piuparts, python3-all, debootstrap
 Restrictions: needs-root
+
+Tests: unshare
+Depends: piuparts, uidmap, debootstrap
+Restrictions: needs-root, skippable


=====================================
debian/tests/unshare
=====================================
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+set -eu
+
+if [ -z "$AUTOPKGTEST_NORMAL_USER" ]; then
+    echo "No normal user available, test cannot be run"
+    exit 77
+fi
+
+AUTOPKGTEST_NORMAL_UID="$(id -u "$AUTOPKGTEST_NORMAL_USER")"
+AUTOPKGTEST_NORMAL_GID="$(id -g "$AUTOPKGTEST_NORMAL_USER")"
+
+if ! grep -qx "$AUTOPKGTEST_NORMAL_UID:.*" /etc/subuid; then
+    echo "Adding a subuid allocation for $AUTOPKGTEST_NORMAL_USER"
+    echo "$AUTOPKGTEST_NORMAL_UID:1000000:65536" >> /etc/subuid
+fi
+
+if ! grep -qx "$AUTOPKGTEST_NORMAL_GID:.*" /etc/subgid; then
+    echo "Adding a subgid allocation for $AUTOPKGTEST_NORMAL_USER's group"
+    echo "$AUTOPKGTEST_NORMAL_GID:1000000:65536" >> /etc/subgid
+fi
+
+if ! unshare --user --map-auto --setuid 0 --setgid 0 --mount --pid --fork --mount-proc true; then
+    echo "Unshare failed, bailing"
+    exit 77
+fi
+
+. "$(dirname "$0")/common.sh"
+
+echo running "$0"
+
+test_this piuparts --version
+
+cd "$AUTOPKGTEST_TMP"
+
+create_packages
+
+chown "$AUTOPKGTEST_NORMAL_USER:" t.deb f.deb
+
+test_this runuser -u "$AUTOPKGTEST_NORMAL_USER" -- piuparts t.deb


=====================================
docs/README_pejacevic.txt
=====================================
@@ -103,7 +103,7 @@ This is actually done by DSA:
 .. code-block:: text
 
  # The piuparts slave needs to handle chroots.
- piupartss       ALL = NOPASSWD: /usr/sbin/piuparts *, \
+ piupartss       ALL = NOPASSWD: /usr/bin/piuparts *, \
                                  /bin/umount /srv/piuparts.debian.org/tmp/tmp*, \
                                  /usr/bin/test -f /srv/piuparts.debian.org/tmp/tmp*, \
                                  /usr/bin/rm -rf --one-file-system /srv/piuparts.debian.org/tmp/tmp*


=====================================
docs/piuparts/piuparts.1.txt
=====================================
@@ -33,7 +33,7 @@ When processing changes files, by default, all packages in a changes file will b
 
 :program:`piuparts` outputs to the standard output some log messages to show what is going on. If a log file is used, the messages go there as well.
 
-:program:`piuparts` needs to be run as root.
+:program:`piuparts` requires root rights to test packages. It does not have to be run in the initial namespace though. When running it as non-root, it'll create a new Linux namespace and rerun itself as root inside said namespace. For this to work, your user needs have an subuid range (which happens by default since a few years) and you need to install the `uidmap` package to provide setuid helpers :program:`newuidmap` and :program:`newgidmap`.
 
 OPTIONS
 -------


=====================================
instances/piuparts.conf-template.pejacevic
=====================================
@@ -220,7 +220,7 @@ piuparts-command =
 	sudo
 	env PYTHONPATH=%(PYTHONPATH)s
 	timeout -s INT -k 5m 80m
-	/srv/piuparts.debian.org/sbin/piuparts
+	/srv/piuparts.debian.org/bin/piuparts
 PYTHONPATH = /srv/piuparts.debian.org/lib/python3/dist-packages
 master-directory = /srv/piuparts.debian.org/master
 slave-directory = /srv/piuparts.debian.org/slave


=====================================
instances/piuparts.conf.anbe
=====================================
@@ -450,7 +450,7 @@ piuparts-command =
 	nice
 	env PYTHONPATH=%(PYTHONPATH)s
 	timeout -s INT -k 5m 110m
-	/srv/piuparts/sbin/piuparts
+	/srv/piuparts/bin/piuparts
 PYTHONPATH = /srv/piuparts/lib/python3/dist-packages
 master-directory = /srv/piuparts/master
 slave-directory = /srv/piuparts/slave


=====================================
piuparts.py
=====================================
@@ -3630,7 +3630,31 @@ def main():
 
     # check if user has root privileges
     if os.getuid():
-        print("You need to be root to use piuparts.")
+        path_env = os.environ["PATH"].split(":")
+        for d in ("/usr/sbin", "/sbin"):
+            if d not in path_env:
+                path_env.append(d)
+        os.environ["PATH"] = ":".join(path_env)
+        # Make debootstrap (<< 1.0.134) happy when it cannot do mknod.
+        os.environ.setdefault("container", "lxc")
+        logging.info("Running non-root. Reexecuting in a usernamespace.")
+        os.execvp(
+            "unshare",
+            [
+                "unshare",
+                "--user",
+                "--map-auto",
+                "--setuid",
+                "0",
+                "--setgid",
+                "0",
+                "--mount",
+                "--pid",
+                "--fork",
+                "--mount-proc",
+                *sys.argv,
+            ],
+        )
         sys.exit(1)
 
     logging.info("-" * 78)


=====================================
slave-bin/slave_stop.in
=====================================
@@ -45,7 +45,7 @@ while pgrep --full '/usr/bin/python3 @sharedir@/piuparts/piuparts-slave' > /dev/
 	echo -n "$(date -u +%T) - "
 	pgrep --full '/usr/bin/python3 @sharedir@/piuparts/piuparts-slave' | xargs -r echo -n "slaves running: "
 	SLEEP=$(( $i * $i ))
-	BUSY=$(ps fax | grep -v grep | grep '/usr/bin/python3 @sbindir@/piuparts' | awk '{print $NF}')
+	BUSY=$(ps fax | grep -v grep | grep '/usr/bin/python3 @bindir@/piuparts' | awk '{print $NF}')
 	if [ -n "$BUSY" ] ; then
 		# really/meaningful busy
 		echo



View it on GitLab: https://salsa.debian.org/debian/piuparts/-/compare/2e8af6a885c43f71fb11a3fd182608e750588f3f...bb303dd73aa486473d70341bc1fe2281d2b703d2

-- 
View it on GitLab: https://salsa.debian.org/debian/piuparts/-/compare/2e8af6a885c43f71fb11a3fd182608e750588f3f...bb303dd73aa486473d70341bc1fe2281d2b703d2
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/piuparts-devel/attachments/20241114/986cb73e/attachment-0001.htm>


More information about the Piuparts-devel mailing list