Commit c5ad51f7 authored by 谢宇轩's avatar 谢宇轩 😅

Merge branch 'developer' into 'master'

feat: 初始化项目

See merge request !1
parents 311f66aa 807b0942
FROM golang:1.18.2-bullseye
RUN set -eux; \
apt-get update; \
apt-get install -y --no-install-recommends \
arch-test \
file \
patch \
; \
rm -rf /var/lib/apt/lists/*
# https://github.com/golang/go/issues/56426
RUN set -eux; \
wget -O /tmp/go-mips.patch 'https://github.com/golang/go/commit/2c7c98c3ad719aa9d6d2594827a6894ff9950042.patch'; \
patch --strip=1 --directory=/usr/local/go --input=/tmp/go-mips.patch
# note: we cannot add "-s" here because then "govulncheck" does not work (see SECURITY.md); the ~0.2MiB increase (as of 2022-12-16, Go 1.18) is worth it
ENV BUILD_FLAGS="-v -ldflags '-d -w'"
RUN set -eux; \
{ \
echo '#!/usr/bin/env bash'; \
echo 'set -Eeuo pipefail -x'; \
echo 'eval "go build $BUILD_FLAGS -o /go/bin/gosu-$ARCH"'; \
echo 'file "/go/bin/gosu-$ARCH"'; \
echo 'if arch-test "$ARCH"; then'; \
# there's a fun QEMU + Go 1.18+ bug that causes our binaries (especially on ARM arches) to hang indefinitely *sometimes*, hence the "timeout" and looping here
echo ' try() { for (( i = 0; i < 30; i++ )); do if timeout 1s "$@"; then return 0; fi; done; return 1; }'; \
echo ' try "/go/bin/gosu-$ARCH" --version'; \
echo ' try "/go/bin/gosu-$ARCH" nobody id'; \
echo ' try "/go/bin/gosu-$ARCH" nobody ls -l /proc/self/fd'; \
echo 'fi'; \
} > /usr/local/bin/gosu-build-and-test.sh; \
chmod +x /usr/local/bin/gosu-build-and-test.sh
# disable CGO for ALL THE THINGS (to help ensure no libc)
ENV CGO_ENABLED 0
WORKDIR /go/src/github.com/tianon/gosu
COPY go.mod go.sum ./
RUN set -eux; \
go mod download; \
go mod verify
COPY *.go ./
# gosu-$(dpkg --print-architecture)
RUN ARCH=amd64 GOARCH=amd64 gosu-build-and-test.sh
RUN ARCH=i386 GOARCH=386 gosu-build-and-test.sh
RUN ARCH=armel GOARCH=arm GOARM=5 gosu-build-and-test.sh
RUN ARCH=armhf GOARCH=arm GOARM=6 gosu-build-and-test.sh
#RUN ARCH=armhf GOARCH=arm GOARM=7 gosu-build-and-test.sh # boo Raspberry Pi, making life hard (armhf-is-v7 vs armhf-is-v6 ...)
RUN ARCH=arm64 GOARCH=arm64 gosu-build-and-test.sh
RUN ARCH=mips64el GOARCH=mips64le gosu-build-and-test.sh
RUN ARCH=ppc64el GOARCH=ppc64le gosu-build-and-test.sh
RUN ARCH=riscv64 GOARCH=riscv64 gosu-build-and-test.sh
RUN ARCH=s390x GOARCH=s390x gosu-build-and-test.sh
RUN set -eux; ls -lAFh /go/bin/gosu-*; file /go/bin/gosu-*
FROM alpine:3.17
# add "nobody" to ALL groups (makes testing edge cases more interesting)
RUN cut -d: -f1 /etc/group | xargs -n1 addgroup nobody
RUN { \
echo '#!/bin/sh'; \
echo 'set -ex'; \
echo; \
echo 'spec="$1"; shift'; \
echo; \
echo 'expec="$1"; shift'; \
echo 'real="$(gosu "$spec" id -u):$(gosu "$spec" id -g):$(gosu "$spec" id -G)"'; \
echo '[ "$expec" = "$real" ]'; \
echo; \
echo 'expec="$1"; shift'; \
# have to "|| true" this one because of "id: unknown ID 1000" (rightfully) having a nonzero exit code
echo 'real="$(gosu "$spec" id -un):$(gosu "$spec" id -gn):$(gosu "$spec" id -Gn)" || true'; \
echo '[ "$expec" = "$real" ]'; \
} > /usr/local/bin/gosu-t \
&& chmod +x /usr/local/bin/gosu-t
COPY gosu /usr/local/bin/
# adjust users so we can make sure the tests are interesting
RUN chgrp nobody /usr/local/bin/gosu \
&& chmod +s /usr/local/bin/gosu
ENV GOSU_PLEASE_LET_ME_BE_COMPLETELY_INSECURE_I_GET_TO_KEEP_ALL_THE_PIECES="I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhäuser Gate. All those moments will be lost in time, like tears in rain. Time to die."
USER nobody
ENV HOME /omg/really/gosu/nowhere
# now we should be nobody, ALL groups, and have a bogus useless HOME value
RUN id
RUN gosu-t 0 "0:0:$(id -G root)" "root:root:$(id -Gn root)"
RUN gosu-t 0:0 '0:0:0' 'root:root:root'
RUN gosu-t root "0:0:$(id -G root)" "root:root:$(id -Gn root)"
RUN gosu-t 0:root '0:0:0' 'root:root:root'
RUN gosu-t root:0 '0:0:0' 'root:root:root'
RUN gosu-t root:root '0:0:0' 'root:root:root'
RUN gosu-t 1000 "1000:$(id -g):$(id -g)" "1000:$(id -gn):$(id -gn)"
RUN gosu-t 0:1000 '0:1000:1000' 'root:1000:1000'
RUN gosu-t 1000:1000 '1000:1000:1000' '1000:1000:1000'
RUN gosu-t root:1000 '0:1000:1000' 'root:1000:1000'
RUN gosu-t 1000:root '1000:0:0' '1000:root:root'
RUN gosu-t 1000:daemon "1000:$(id -g daemon):$(id -g daemon)" '1000:daemon:daemon'
RUN gosu-t games "$(id -u games):$(id -g games):$(id -G games)" 'games:games:games users'
RUN gosu-t games:daemon "$(id -u games):$(id -g daemon):$(id -g daemon)" 'games:daemon:daemon'
RUN gosu-t 0: "0:0:$(id -G root)" "root:root:$(id -Gn root)"
RUN gosu-t '' "$(id -u):$(id -g):$(id -G)" "$(id -un):$(id -gn):$(id -Gn)"
RUN gosu-t ':0' "$(id -u):0:0" "$(id -un):root:root"
RUN [ "$(gosu 0 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu 0:0 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu root env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu 0:root env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu root:0 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu root:root env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu 0:1000 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu root:1000 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu 1000 env | grep '^HOME=')" = 'HOME=/' ]
RUN [ "$(gosu 1000:0 env | grep '^HOME=')" = 'HOME=/' ]
RUN [ "$(gosu 1000:root env | grep '^HOME=')" = 'HOME=/' ]
RUN [ "$(gosu games env | grep '^HOME=')" = 'HOME=/usr/games' ]
RUN [ "$(gosu games:daemon env | grep '^HOME=')" = 'HOME=/usr/games' ]
# make sure we error out properly in unexpected cases like an invalid username
RUN ! gosu bogus true
RUN ! gosu 0day true
RUN ! gosu 0:bogus true
RUN ! gosu 0:0day true
# something missing? some other functionality we could test easily? PR! :D
FROM debian:bullseye-slim
# add "nobody" to ALL groups (makes testing edge cases more interesting)
RUN cut -d: -f1 /etc/group | xargs -n1 -I'{}' usermod -aG '{}' nobody
# emulate Alpine's "games" user (which is part of the "users" group)
RUN usermod -aG users games
RUN { \
echo '#!/bin/sh'; \
echo 'set -ex'; \
echo; \
echo 'spec="$1"; shift'; \
echo; \
echo 'expec="$1"; shift'; \
echo 'real="$(gosu "$spec" id -u):$(gosu "$spec" id -g):$(gosu "$spec" id -G)"'; \
echo '[ "$expec" = "$real" ]'; \
echo; \
echo 'expec="$1"; shift'; \
# have to "|| true" this one because of "id: unknown ID 1000" (rightfully) having a nonzero exit code
echo 'real="$(gosu "$spec" id -un):$(gosu "$spec" id -gn):$(gosu "$spec" id -Gn)" || true'; \
echo '[ "$expec" = "$real" ]'; \
} > /usr/local/bin/gosu-t \
&& chmod +x /usr/local/bin/gosu-t
COPY gosu /usr/local/bin/
# adjust users so we can make sure the tests are interesting
RUN chgrp nogroup /usr/local/bin/gosu \
&& chmod +s /usr/local/bin/gosu
ENV GOSU_PLEASE_LET_ME_BE_COMPLETELY_INSECURE_I_GET_TO_KEEP_ALL_THE_PIECES="I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhäuser Gate. All those moments will be lost in time, like tears in rain. Time to die."
USER nobody
ENV HOME /omg/really/gosu/nowhere
# now we should be nobody, ALL groups, and have a bogus useless HOME value
RUN id
RUN gosu-t 0 "0:0:$(id -G root)" "root:root:$(id -Gn root)"
RUN gosu-t 0:0 '0:0:0' 'root:root:root'
RUN gosu-t root "0:0:$(id -G root)" "root:root:$(id -Gn root)"
RUN gosu-t 0:root '0:0:0' 'root:root:root'
RUN gosu-t root:0 '0:0:0' 'root:root:root'
RUN gosu-t root:root '0:0:0' 'root:root:root'
RUN gosu-t 1000 "1000:$(id -g):$(id -g)" "1000:$(id -gn):$(id -gn)"
RUN gosu-t 0:1000 '0:1000:1000' 'root:1000:1000'
RUN gosu-t 1000:1000 '1000:1000:1000' '1000:1000:1000'
RUN gosu-t root:1000 '0:1000:1000' 'root:1000:1000'
RUN gosu-t 1000:root '1000:0:0' '1000:root:root'
RUN gosu-t 1000:daemon "1000:$(id -g daemon):$(id -g daemon)" '1000:daemon:daemon'
RUN gosu-t games "$(id -u games):$(id -g games):$(id -G games)" 'games:games:games users'
RUN gosu-t games:daemon "$(id -u games):$(id -g daemon):$(id -g daemon)" 'games:daemon:daemon'
RUN gosu-t 0: "0:0:$(id -G root)" "root:root:$(id -Gn root)"
RUN gosu-t '' "$(id -u):$(id -g):$(id -G)" "$(id -un):$(id -gn):$(id -Gn)"
RUN gosu-t ':0' "$(id -u):0:0" "$(id -un):root:root"
RUN [ "$(gosu 0 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu 0:0 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu root env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu 0:root env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu root:0 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu root:root env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu 0:1000 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu root:1000 env | grep '^HOME=')" = 'HOME=/root' ]
RUN [ "$(gosu 1000 env | grep '^HOME=')" = 'HOME=/' ]
RUN [ "$(gosu 1000:0 env | grep '^HOME=')" = 'HOME=/' ]
RUN [ "$(gosu 1000:root env | grep '^HOME=')" = 'HOME=/' ]
RUN [ "$(gosu games env | grep '^HOME=')" = 'HOME=/usr/games' ]
RUN [ "$(gosu games:daemon env | grep '^HOME=')" = 'HOME=/usr/games' ]
# make sure we error out properly in unexpected cases like an invalid username
RUN ! gosu bogus true
RUN ! gosu 0day true
RUN ! gosu 0:bogus true
RUN ! gosu 0:0day true
# something missing? some other functionality we could test easily? PR! :D
# Installation
We assume installation inside Docker (probably not the right tool for most use-cases outside Docker), and that you don't have either `wget` or `ca-certificates` already installed -- adjust (and version bump `GOSU_VERSION`) as necessary!
## `FROM debian`
[Debian 9 ("Debian Stretch") or newer](https://packages.debian.org/gosu):
```dockerfile
RUN set -eux; \
apt-get update; \
apt-get install -y gosu; \
rm -rf /var/lib/apt/lists/*; \
# verify that the binary works
gosu nobody true
```
Older Debian releases (or newer `gosu` releases):
```dockerfile
ENV GOSU_VERSION 1.16
RUN set -eux; \
# save list of currently installed packages for later so we can clean up
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y --no-install-recommends ca-certificates wget; \
if ! command -v gpg; then \
apt-get install -y --no-install-recommends gnupg2 dirmngr; \
elif gpg --version | grep -q '^gpg (GnuPG) 1\.'; then \
# "This package provides support for HKPS keyservers." (GnuPG 1.x only)
apt-get install -y --no-install-recommends gnupg-curl; \
fi; \
rm -rf /var/lib/apt/lists/*; \
\
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
\
# verify the signature
export GNUPGHOME="$(mktemp -d)"; \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
command -v gpgconf && gpgconf --kill all || :; \
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
\
# clean up fetch dependencies
apt-mark auto '.*' > /dev/null; \
[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
\
chmod +x /usr/local/bin/gosu; \
# verify that the binary works
gosu --version; \
gosu nobody true
```
## `FROM alpine` (3.7+)
**Note:** when using Alpine, it's probably also worth checking out [`su-exec`](https://github.com/ncopa/su-exec) (`apk add --no-cache su-exec`) instead, which since version 0.2 is fully `gosu`-compatible in a fraction of the file size.
```dockerfile
ENV GOSU_VERSION 1.16
RUN set -eux; \
\
apk add --no-cache --virtual .gosu-deps \
ca-certificates \
dpkg \
gnupg \
; \
\
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
\
# verify the signature
export GNUPGHOME="$(mktemp -d)"; \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
command -v gpgconf && gpgconf --kill all || :; \
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
\
# clean up fetch dependencies
apk del --no-network .gosu-deps; \
\
chmod +x /usr/local/bin/gosu; \
# verify that the binary works
gosu --version; \
gosu nobody true
```
This diff is collapsed.
# gosu
This is a simple tool grown out of the simple fact that `su` and `sudo` have very strange and often annoying TTY and signal-forwarding behavior. They're also somewhat complex to setup and use (especially in the case of `sudo`), which allows for a great deal of expressivity, but falls flat if all you need is "run this specific application as this specific user and get out of the pipeline".
The core of how `gosu` works is stolen directly from how Docker/libcontainer itself starts an application inside a container (and in fact, is using the `/etc/passwd` processing code directly from libcontainer's codebase).
```console
$ gosu
Usage: ./gosu user-spec command [args]
eg: ./gosu tianon bash
./gosu nobody:root bash -c 'whoami && id'
./gosu 1000:1 id
./gosu version: 1.1 (go1.3.1 on linux/amd64; gc)
```
Once the user/group is processed, we switch to that user, then we `exec` the specified process and `gosu` itself is no longer resident or involved in the process lifecycle at all. This avoids all the issues of signal passing and TTY, and punts them to the process invoking `gosu` and the process being invoked by `gosu`, where they belong.
## Warning
The core use case for `gosu` is to step _down_ from `root` to a non-privileged user during container startup (specifically in the `ENTRYPOINT`, usually).
Uses of `gosu` beyond that could very well suffer from vulnerabilities such as CVE-2016-2779 (from which the Docker use case naturally shields us); see [`tianon/gosu#37`](https://github.com/tianon/gosu/issues/37) for some discussion around this point.
## Installation
High-level steps:
1. download `gosu-$(dpkg --print-architecture | awk -F- '{ print $NF }')` as `gosu`
2. download `gosu-$(dpkg --print-architecture | awk -F- '{ print $NF }').asc` as `gosu.asc`
3. fetch my public key (to verify your download): `gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4`
4. `gpg --batch --verify gosu.asc gosu`
5. `chmod +x gosu`
For explicit `Dockerfile` instructions, see [`INSTALL.md`](INSTALL.md).
## Why?
```console
$ docker run -it --rm ubuntu:trusty su -c 'exec ps aux'
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 46636 2688 ? Ss+ 02:22 0:00 su -c exec ps a
root 6 0.0 0.0 15576 2220 ? Rs 02:22 0:00 ps aux
$ docker run -it --rm ubuntu:trusty sudo ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 3.0 0.0 46020 3144 ? Ss+ 02:22 0:00 sudo ps aux
root 7 0.0 0.0 15576 2172 ? R+ 02:22 0:00 ps aux
$ docker run -it --rm -v $PWD/gosu-amd64:/usr/local/bin/gosu:ro ubuntu:trusty gosu root ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 7140 768 ? Rs+ 02:22 0:00 ps aux
```
Additionally, due to the fact that `gosu` is using Docker's own code for processing these `user:group`, it has exact 1:1 parity with Docker's own `--user` flag.
If you're curious about the edge cases that `gosu` handles, see [`Dockerfile.test`](Dockerfile.test) for the "test suite" (and the associated [`test.sh`](test.sh) script that wraps this up for testing arbitrary binaries).
(Note that `sudo` has different goals from this project, and it is *not* intended to be a `sudo` replacement; for example, see [this Stack Overflow answer](https://stackoverflow.com/a/48105623) for a short explanation of why `sudo` does `fork`+`exec` instead of just `exec`.)
## Alternatives
### `su-exec`
As mentioned in `INSTALL.md`, [`su-exec`](https://github.com/ncopa/su-exec) is a very minimal re-write of `gosu` in C, making for a much smaller binary, and is available in the `main` Alpine package repository.
### `chroot`
With the `--userspec` flag, `chroot` can provide similar benefits/behavior:
```console
$ 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 7136 756 ? Rs+ 17:04 0:00 ps aux
```
### `setpriv`
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 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 9592 1252 pts/0 RNs+ 23:21 0:00 ps faux
```
### Others
I'm not terribly familiar with them, but a few other alternatives I'm aware of include:
- `chpst` (part of `runit`)
# CVEs
This project does not rebuild/release to "fix" CVEs which do not apply to actual builds of `gosu`. For example, this includes any CVE in Go which applies to interfaces that `gosu` does not ever invoke, such as `net/http`, `archive/tar`, `encoding/xml`, etc.
Before reporting that `gosu` is "vulnerable" to a particular CVE, please run [`govulncheck`](https://pkg.go.dev/golang.org/x/vuln/cmd/govulncheck) to determine whether the latest release is *actually* using the vulnerable functionality. See [this excellent blog post](https://go.dev/blog/vuln) from the Go team for more information about the `govulncheck` tool and the methodology by which it is maintained.
If you have a tool which is reporting that `gosu` is vulnerable to a particular CVE but `govulncheck` does not agree, **please** report this as a false positive to your CVE scanning vendor so that they can improve their tooling. (If you wish to verify that your reported CVE is part of `govulncheck`'s dataset and thus covered by their tool, you can check [the vulndb repository](https://github.com/golang/vulndb) where they track those.)
# Reporting Vulnerabilities
The surface area of `gosu` itself is really limited -- it only directly contains a small amount of Go code to instrument an interface that is part of [`runc`](https://github.com/opencontainers/runc) (and which itself is a pretty limited interface) for providing the same behavior as Docker's `--user` flag, but from within a running container.
If you believe you have found a new vulnerability in `gosu`, chances are very high that it's actually a vulnerability in `runc` (or at the very least, `runc`'s code), and should be [reported appropriately and responsibly](https://github.com/opencontainers/.github/blob/master/SECURITY.md).
After all this, if you still believe you have discovered a novel vulnerability in the limited code that is `gosu` itself, please [use GitHub's (private) advisory reporting feature](https://github.com/tianon/gosu/security/advisories/new) to responsibly report it.
#!/bin/bash
set -e
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
set -x
docker build --pull -t gosu .
rm -f gosu* SHA256SUMS*
docker run --rm gosu sh -c 'cd /go/bin && tar -c gosu*' | tar -xv
sha256sum gosu* | tee SHA256SUMS
file gosu*
ls -lFh gosu* SHA256SUMS*
"./gosu-$(dpkg --print-architecture)" --help
module github.com/tianon/gosu
go 1.18
require github.com/opencontainers/runc v1.1.0
require golang.org/x/sys v0.0.0-20220907062415-87db552b00fd // indirect
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/godbus/dbus/v5 v5.0.6/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
github.com/opencontainers/runc v1.1.0 h1:O9+X96OcDjkmmZyfaG996kV7yq8HsoU2h1XRRQcefG8=
github.com/opencontainers/runc v1.1.0/go.mod h1:Tj1hFw6eFWp/o33uxGf5yF2BX5yz2Z6iptFpuvbbKqc=
github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0=
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U=
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
FROM alpine:3.17
# https://github.com/tianon/gosu/releases
ENV GOSU_VERSION 1.16
RUN set -eux; \
apk add --no-cache --virtual .fetch-deps dpkg gnupg; \
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
gpgconf --kill all; \
rm -rf "$GNUPGHOME"; unset GNUPGHOME; \
apk del --no-network .fetch-deps; \
chmod +x /usr/local/bin/gosu; \
gosu --version; \
gosu nobody true; \
# hard link to / for ease of COPY --from
ln -v /usr/local/bin/gosu /
FROM debian:bullseye-slim
# https://github.com/tianon/gosu/releases
ENV GOSU_VERSION 1.16
RUN set -eux; \
savedAptMark="$(apt-mark showmanual)"; \
apt-get update; \
apt-get install -y --no-install-recommends ca-certificates dirmngr gnupg wget; \
rm -rf /var/lib/apt/lists/*; \
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; \
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
gpgconf --kill all; \
rm -rf "$GNUPGHOME"; unset GNUPGHOME; \
apt-mark auto '.*' > /dev/null; \
[ -z "$savedAptMark" ] || apt-mark manual $savedAptMark > /dev/null; \
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false; \
chmod +x /usr/local/bin/gosu; \
gosu --version; \
gosu nobody true; \
# hard link to / for ease of COPY --from
ln -v /usr/local/bin/gosu /
image: tianon/gosu:alpine
manifests:
- { image: tianon/gosu:alpine-amd64, platform: { os: linux, architecture: amd64 } }
- { image: tianon/gosu:alpine-arm32v6, platform: { os: linux, architecture: arm, variant: v6 } }
- { image: tianon/gosu:alpine-arm32v7, platform: { os: linux, architecture: arm, variant: v7 } }
- { image: tianon/gosu:alpine-arm64v8, platform: { os: linux, architecture: arm64, variant: v8 } }
- { image: tianon/gosu:alpine-i386, platform: { os: linux, architecture: 386 } }
- { image: tianon/gosu:alpine-ppc64le, platform: { os: linux, architecture: ppc64le } }
- { image: tianon/gosu:alpine-s390x, platform: { os: linux, architecture: s390x } }
#!/usr/bin/env bash
set -Eeuo pipefail
declare -A platforms=(
[amd64]='linux/amd64'
[arm32v5]='linux/arm/v5'
[arm32v6]='linux/arm/v6'
[arm32v7]='linux/arm/v7'
[arm64v8]='linux/arm64/v8'
[i386]='linux/386'
[mips64le]='linux/mips64le'
[ppc64le]='linux/ppc64le'
[s390x]='linux/s390x'
)
declare -A arches=(
[alpine]='amd64 arm32v6 arm32v7 arm64v8 i386 ppc64le s390x'
[debian]='amd64 arm32v5 arm32v7 arm64v8 i386 mips64le ppc64le s390x'
)
preferredOrder=( alpine debian )
_platformToOCI() {
local platform="$1"; shift
local os="${platform%%/*}"
platform="${platform#$os/}"
local architecture="${platform%%/*}"
platform="${platform#$architecture/}"
local variant="$platform"
[ "$architecture" != "$variant" ] || variant=
echo "{ os: $os, architecture: $architecture${variant:+, variant: $variant} }"
}
declare -A latest=()
for variant in "${preferredOrder[@]}"; do
cat > "$variant.yml" <<-EOYAML
image: tianon/gosu:$variant
manifests:
EOYAML
for arch in ${arches[$variant]}; do
platform="${platforms[$arch]}"
docker build --pull --platform "$platform" --tag "tianon/gosu:$variant-$arch" - < "Dockerfile.$variant"
: "${latest[$arch]:=$variant}"
platform="$(_platformToOCI "$platform")"
echo " - { image: tianon/gosu:$variant-$arch, platform: $platform }" >> "$variant.yml"
done
done
cat > latest.yml <<-'EOYAML'
image: tianon/gosu:latest
manifests:
EOYAML
mapfile -d '' sorted < <(printf '%s\0' "${!latest[@]}" | sort -z)
for arch in "${sorted[@]}"; do
variant="${latest[$arch]}"
docker tag "tianon/gosu:$variant-$arch" "tianon/gosu:$arch"
platform="$(_platformToOCI "${platforms[$arch]}")"
echo " - { image: tianon/gosu:$arch, platform: $platform }" >> latest.yml
done
echo
echo '$ # now:'
echo
echo '$ docker push --all-tags tianon/gosu'
for variant in "${preferredOrder[@]}" latest; do
echo "\$ manifest-tool push from-spec $variant.yml"
done
image: tianon/gosu:debian
manifests:
- { image: tianon/gosu:debian-amd64, platform: { os: linux, architecture: amd64 } }
- { image: tianon/gosu:debian-arm32v5, platform: { os: linux, architecture: arm, variant: v5 } }
- { image: tianon/gosu:debian-arm32v7, platform: { os: linux, architecture: arm, variant: v7 } }
- { image: tianon/gosu:debian-arm64v8, platform: { os: linux, architecture: arm64, variant: v8 } }
- { image: tianon/gosu:debian-i386, platform: { os: linux, architecture: 386 } }
- { image: tianon/gosu:debian-mips64le, platform: { os: linux, architecture: mips64le } }
- { image: tianon/gosu:debian-ppc64le, platform: { os: linux, architecture: ppc64le } }
- { image: tianon/gosu:debian-s390x, platform: { os: linux, architecture: s390x } }
image: tianon/gosu:latest
manifests:
- { image: tianon/gosu:amd64, platform: { os: linux, architecture: amd64 } }
- { image: tianon/gosu:arm32v5, platform: { os: linux, architecture: arm, variant: v5 } }
- { image: tianon/gosu:arm32v6, platform: { os: linux, architecture: arm, variant: v6 } }
- { image: tianon/gosu:arm32v7, platform: { os: linux, architecture: arm, variant: v7 } }
- { image: tianon/gosu:arm64v8, platform: { os: linux, architecture: arm64, variant: v8 } }
- { image: tianon/gosu:i386, platform: { os: linux, architecture: 386 } }
- { image: tianon/gosu:mips64le, platform: { os: linux, architecture: mips64le } }
- { image: tianon/gosu:ppc64le, platform: { os: linux, architecture: ppc64le } }
- { image: tianon/gosu:s390x, platform: { os: linux, architecture: s390x } }
package main // import "github.com/tianon/gosu"
import (
"bytes"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"syscall"
"text/template"
)
func init() {
// make sure we only have one process and that it runs on the main thread (so that ideally, when we Exec, we keep our user switches and stuff)
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
}
func version() string {
return fmt.Sprintf(`%s (%s on %s/%s; %s)`, Version, runtime.Version(), runtime.GOOS, runtime.GOARCH, runtime.Compiler)
}
func usage() string {
t := template.Must(template.New("usage").Parse(`
Usage: {{ .Self }} user-spec command [args]
eg: {{ .Self }} tianon bash
{{ .Self }} nobody:root bash -c 'whoami && id'
{{ .Self }} 1000:1 id
{{ .Self }} version: {{ .Version }}
{{ .Self }} license: Apache-2.0 (full text at https://github.com/tianon/gosu)
`))
var b bytes.Buffer
template.Must(t, t.Execute(&b, struct {
Self string
Version string
}{
Self: filepath.Base(os.Args[0]),
Version: version(),
}))
return strings.TrimSpace(b.String()) + "\n"
}
func main() {
log.SetFlags(0) // no timestamps on our logs
if ok := os.Getenv("GOSU_PLEASE_LET_ME_BE_COMPLETELY_INSECURE_I_GET_TO_KEEP_ALL_THE_PIECES"); ok != "I've seen things you people wouldn't believe. Attack ships on fire off the shoulder of Orion. I watched C-beams glitter in the dark near the Tannhäuser Gate. All those moments will be lost in time, like tears in rain. Time to die." {
if fi, err := os.Stat("/proc/self/exe"); err != nil {
log.Fatalf("error: %v", err)
} else if fi.Mode()&os.ModeSetuid != 0 {
// ... oh no
log.Fatalf("error: %q appears to be installed with the 'setuid' bit set, which is an *extremely* insecure and completely unsupported configuration! (what you want instead is likely 'sudo' or 'su')", os.Args[0])
}
}
if len(os.Args) >= 2 {
switch os.Args[1] {
case "--help", "-h", "-?":
fmt.Println(usage())
os.Exit(0)
case "--version", "-v":
fmt.Println(version())
os.Exit(0)
}
}
if len(os.Args) <= 2 {
log.Println(usage())
os.Exit(1)
}
// clear HOME so that SetupUser will set it
os.Unsetenv("HOME")
if err := SetupUser(os.Args[1]); err != nil {
log.Fatalf("error: failed switching to %q: %v", os.Args[1], err)
}
name, err := exec.LookPath(os.Args[2])
if err != nil {
log.Fatalf("error: %v", err)
}
if err = syscall.Exec(name, os.Args[2:], os.Environ()); err != nil {
log.Fatalf("error: exec failed: %v", err)
}
}
package main
import (
"os"
"syscall"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/user"
)
// this function comes from libcontainer/init_linux.go
// we don't use that directly because we don't want the whole namespaces package imported here
// (also, because we need minor modifications and it's not even exported)
// SetupUser changes the groups, gid, and uid for the user inside the container
func SetupUser(u string) error {
// Set up defaults.
defaultExecUser := user.ExecUser{
Uid: syscall.Getuid(),
Gid: syscall.Getgid(),
Home: "/",
}
passwdPath, err := user.GetPasswdPath()
if err != nil {
return err
}
groupPath, err := user.GetGroupPath()
if err != nil {
return err
}
execUser, err := user.GetExecUserPath(u, &defaultExecUser, passwdPath, groupPath)
if err != nil {
return err
}
if err := syscall.Setgroups(execUser.Sgids); err != nil {
return err
}
if err := system.Setgid(execUser.Gid); err != nil {
return err
}
if err := system.Setuid(execUser.Uid); err != nil {
return err
}
// if we didn't get HOME already, set it based on the user's HOME
if envHome := os.Getenv("HOME"); envHome == "" {
if err := os.Setenv("HOME", execUser.Home); err != nil {
return err
}
}
return nil
}
#!/bin/bash
set -e
cd "$(dirname "$(readlink -f "$BASH_SOURCE")")"
set -x
rm -f gosu*.asc SHA256SUMS.asc
for f in gosu*; do
gpg --output "$f.asc" --detach-sign "$f"
done
sha256sum gosu* > SHA256SUMS
gpg --output SHA256SUMS.asc --detach-sign SHA256SUMS
ls -lFh gosu* SHA256SUMS*
#!/usr/bin/env bash
set -Eeuo pipefail
usage() {
echo "usage: $0 [--platform] gosu-binary"
echo " eg: $0 ./gosu-amd64"
echo " $0 --debian ./gosu-amd64"
}
df='Dockerfile.test-alpine'
case "${1:-}" in
--alpine | --debian)
df="Dockerfile.test-${1#--}"
shift
;;
esac
gosu="${1:-}"
shift || { usage >&2; exit 1; }
[ -f "$gosu" ] || { usage >&2; exit 1; }
trap '{ set +x; echo; echo FAILED; echo; } >&2' ERR
set -x
dir="$(mktemp -d -t gosu-test-XXXXXXXXXX)"
base="$(basename "$dir")"
img="gosu-test:$base"
trap "rm -rf '$dir'" EXIT
cp -T "$df" "$dir/Dockerfile"
cp -T "$gosu" "$dir/gosu"
docker build -t "$img" "$dir"
rm -rf "$dir"
trap - EXIT
trap "docker rm -f '$base' > /dev/null; docker rmi -f '$img' > /dev/null" EXIT
# using explicit "--init=false" in case dockerd is running with "--init" (because that will skew our process numbers)
docker run -d --init=false --name "$base" "$img" gosu root sleep 1000
sleep 1 # give it plenty of time to get through "gosu" and into the "sleep"
[ "$(docker top "$base" | wc -l)" = 2 ]
# "docker top" should have only two lines
# -- ps headers and a single line for the single process running in the container
package main
const Version = "1.16"
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment