Skip to content

Commit

Permalink
Add official setpriv-wrapper.sh which implements gosu in pure POS…
Browse files Browse the repository at this point in the history
…IX shell via `setpriv`
  • Loading branch information
tianon committed Jun 3, 2024
1 parent a094511 commit 8903867
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 10 deletions.
19 changes: 18 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,32 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: sudo apt-get update && sudo apt-get install -y --no-install-recommends binfmt-support qemu-user-static

- name: hack in qemu-binfmt
run: sudo apt-get update && sudo apt-get install -y --no-install-recommends binfmt-support qemu-user-static

- run: ./build.sh

# explicitly test our (native) amd64/i386 binaries
- run: ./test.sh gosu-amd64
- run: ./test.sh gosu-i386
- run: ./test.sh --debian gosu-amd64
- run: ./test.sh --debian gosu-i386

# now that we've successfully tested gosu itself, let's hack the test suite a little bit to not use setuid (and to have util-linux's setpriv installed) so we can also smoke test "setpriv-wrapper.sh"
- name: hack tests for setpriv
run: |
sed -ri -e '/^USER /d' Dockerfile.test-*
awk '{ print } toupper($1) == "FROM" { print "RUN apk add --no-cache setpriv" }' Dockerfile.test-alpine > Dockerfile.test-alpine.new
mv Dockerfile.test-alpine.new Dockerfile.test-alpine
- run: ./test.sh setpriv-wrapper.sh
- run: ./test.sh --debian setpriv-wrapper.sh

# smoke test our Docker image builds
- run: docker build --pull --file hub/Dockerfile.alpine hub
- run: docker build --pull --file hub/Dockerfile.debian hub

# run "govulncheck" automatically to ensure we don't have any new/unknown vulnerabilities
- uses: actions/setup-go@v4
with:
go-version: 1.18
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile.test-alpine
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM alpine:3.19
FROM alpine:3.20

# add "nobody" to ALL groups (makes testing edge cases more interesting)
RUN cut -d: -f1 /etc/group | xargs -rtn1 addgroup nobody
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,24 +58,26 @@ If you're curious about the edge cases that `gosu` handles, see [`Dockerfile.tes

## Alternatives

### `chroot`
### `setpriv`

With the `--userspec` flag, `chroot` can provide similar benefits/behavior:
Available in newer `util-linux` (`>= 2.32.1-0.2`, in Debian; https://manpages.debian.org/buster/util-linux/setpriv.1.en.html):

```console
$ docker run -it --rm ubuntu:trusty chroot --userspec=nobody / ps aux
$ docker run -it --rm buildpack-deps:buster-scm setpriv --reuid=nobody --regid=nogroup --init-groups ps faux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
nobody 1 5.0 0.0 7136 756 ? Rs+ 17:04 0:00 ps aux
nobody 1 5.0 0.0 9592 1252 pts/0 RNs+ 23:21 0:00 ps faux
```

### `setpriv`
In this repository, you'll find an official `setpriv-wrapper.sh` which implements a `gosu`-compatible interface which passes `gosu`'s test suite.

Available in newer `util-linux` (`>= 2.32.1-0.2`, in Debian; https://manpages.debian.org/buster/util-linux/setpriv.1.en.html):
### `chroot`

With the `--userspec` flag, `chroot` can provide similar benefits/behavior:

```console
$ docker run -it --rm buildpack-deps:buster-scm setpriv --reuid=nobody --regid=nogroup --init-groups ps faux
$ docker run -it --rm ubuntu:trusty chroot --userspec=nobody / ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
nobody 1 5.0 0.0 9592 1252 pts/0 RNs+ 23:21 0:00 ps faux
nobody 1 5.0 0.0 7136 756 ? Rs+ 17:04 0:00 ps aux
```

### `su-exec`
Expand Down
68 changes: 68 additions & 0 deletions setpriv-wrapper.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#!/usr/bin/env sh
set -eu

#
# This script is a wrapper around "setpriv" (part of util-linux) which implements a fully gosu-compatible interface (and passes gosu's test suite).
#
# It is written in POSIX shell for maximum compatbility, but notably does *not* work with BusyBox's setpriv (as of 2024-06-03) as BusyBox does not implement enough functionality.
#

# TODO GOSU_PLEASE_LET_ME_BE_COMPLETELY_INSECURE_I_GET_TO_KEEP_ALL_THE_PIECES (block setuid/setgid) -- however, you can't effectively setuid a shell script/interpreted file, so maybe it's fine? 🤔

usage() {
cat <<-EOU
Usage: $0 user-spec command [args]
eg: $0 tianon bash
$0 nobody:root bash -c 'whoami && id'
$0 1000:1 id
$0 license: Apache-2.0 (full text at https://github.com/tianon/gosu)
EOU
}

case "${1:-}" in
--help | -h | '-?') usage; exit 0 ;;
--version | -v) echo '???'; exit 0 ;;
esac
if [ "$#" -lt 2 ]; then
usage >&2
exit 1
fi

spec="$1"; shift
: "${spec:=0}"
spec="${spec%:}" # "0:" is parsed by moby/sys/user the same as "0"
case "$spec" in
*:*)
user="${spec%%:*}"
: "${user:=0}"
group="${spec#$user:}"
[ "$group" != "$spec" ]
passwd="$(getent passwd "$user" || :)" # for HOME scraping below
set -- --reuid "$user" --regid "$group" --clear-groups -- "$@"
;;

*)
user="$spec"
if passwd="$(getent passwd "$user")" && [ -n "$passwd" ]; then
group="$(printf '%s' "$passwd" | cut -d: -f4)"
set -- --reuid "$user" --regid "$group" --init-groups -- "$@"
else
passwd= # to be safe/explicit (for HOME scraping below)
case "$user" in
*[!0-9]* | '') echo >&2 "error: '$user' is not a user (and is also not numeric)"; exit 1 ;;
*) group='0' ;;
# (thanks to https://stackoverflow.com/a/16444570/433558 for this perfect pure-POSIX "is it fully numeric" hack!)
esac
set -- --reuid "$user" --regid "$group" --clear-groups -- "$@"
fi
;;
esac

unset HOME
HOME="$(printf '%s' "$passwd" | cut -d: -f6)"
: "${HOME:=/}" # see "setup-user.go"
export HOME

exec setpriv "$@"

0 comments on commit 8903867

Please sign in to comment.