Skip to content

Commit

Permalink
fix: container tools broken without a home
Browse files Browse the repository at this point in the history
Some tools get installed and used based on a home directory. This can
cause trouble when using a container started from this image with a
UID:GID that does not map to a user/group account in the container.
Examples include:

* The install directory is `/root` and the container user has no access
* The tool relies on a $HOME directory, which may not exist
  * https://medium.com/redbubble/running-a-docker-container-as-a-non-root-user-7d2e00f8ee15

The following tools are susceptible to this issue and therefore have
been provided with explicit install/home directories that allow them to
be globally accessible:

* Corepack, Yarn, and pnpm
* Rust, Cargo, and rustup
* Gradle

This issue was first discovered by @marvin-hansen and documented here:
#310 (comment)

In that case, GitHub Actions was used and it was found that `HOME` is
[overridden for containers](actions/runner#863)
and set to `/github/home`, which is a directory that is unknown during
image creation. Instead of attempting to create a custom solution for
GitHub Actions specifically, the solution presented here is meant to be
useful for any environment that starts a container from the `phylum-ci`
image. Since there are many CI environments already supported, each with
their own methods of running containers, this approach was deemed to be
the most prudent.

Tests have been added to ensure all the required tools can function when
a container is started from the `phylum-ci` image with a user that is
unknown to that image and therefore does not have a `$HOME` directory.
  • Loading branch information
maxrake committed Oct 14, 2023
1 parent 3241d2d commit 91627e7
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 12 deletions.
38 changes: 30 additions & 8 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -146,10 +146,31 @@ LABEL org.opencontainers.image.source="https://github.com/phylum-dev/phylum-ci"

ENV PHYLUM_VENV="/opt/venv"

ENV GRADLE_PATH="/opt/gradle/bin"
# Some tools get installed and used based on a home directory. This can cause trouble
# when using a container started from this image with a UID:GID that does not map to
# a user/group account in the container. Examples include:
#
# * The installation directory is `/root` and the container user has no access
# * The tool relies on the existence of a $HOME directory, which may not be true
# * https://medium.com/redbubble/running-a-docker-container-as-a-non-root-user-7d2e00f8ee15
#
# The following tools are susceptible to this issue and therefore have been provided
# with explicit install/home directories that allow them to be globally accessible.
#
# Corepack, Yarn, and pnpm
# Ref: https://github.com/nodejs/corepack#environment-variables
ENV COREPACK_HOME="/usr/local/corepack/.cache/node/corepack"
# Rust, Cargo, and rustup
# Ref: https://rust-lang.github.io/rustup/installation/index.html#choosing-where-to-install
ENV RUSTUP_HOME="/usr/local/rustup"
ENV CARGO_HOME="/usr/local/cargo"
# Gradle
# Ref: https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_environment_variables
ENV GRADLE_HOME="/usr/local/gradle"
ENV GRADLE_USER_HOME="${GRADLE_HOME}/.gradle"

ENV GO_PATH="/usr/local/go/bin"
ENV CARGO_PATH="/root/.cargo/bin"
ENV PATH=${PHYLUM_VENV}/bin:$PATH:${GRADLE_PATH}:${GO_PATH}:${CARGO_PATH}
ENV PATH=${PHYLUM_VENV}/bin:$PATH:${GRADLE_HOME}/bin:${GO_PATH}:${CARGO_HOME}/bin

ENV PYTHONDONTWRITEBYTECODE=1

Expand Down Expand Up @@ -199,10 +220,11 @@ RUN \
GRADLE_DL_SHA256=$(curls "${GRADLE_DL}.sha256"); \
curls -o gradle.zip "${GRADLE_DL}"; \
printf "%s *gradle.zip" "${GRADLE_DL_SHA256}" | sha256sum -c -; \
mkdir /opt/gradle; \
unzip -d /opt/gradle gradle.zip; \
mv /opt/gradle/gradle-"${GRADLE_VERSION}"/* /opt/gradle/; \
rm gradle.zip && rmdir "/opt/gradle/gradle-${GRADLE_VERSION}/"; \
mkdir "${GRADLE_HOME}"; \
unzip -d "${GRADLE_HOME}" gradle.zip; \
mv "${GRADLE_HOME}/gradle-${GRADLE_VERSION}"/* "${GRADLE_HOME}/"; \
rm gradle.zip && rmdir "${GRADLE_HOME}/gradle-${GRADLE_VERSION}/"; \
mkdir --mode=777 "${GRADLE_USER_HOME}"; \
#
# Manual install of `go`
# Ref: https://pkg.go.dev/golang.org/x/website/internal/dl
Expand All @@ -217,7 +239,7 @@ RUN \
rm go.tgz; \
#
# Manual install of Rust to get `cargo` tool
curls https://sh.rustup.rs | sh -s -- -y --profile minimal; \
curls https://sh.rustup.rs | sh -s -- -v -y --default-toolchain stable --profile minimal; \
#
# Install .NET SDK to get `dotnet` tool
# Ref: https://github.com/dotnet/core/tree/main/release-notes
Expand Down
24 changes: 20 additions & 4 deletions scripts/docker_tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,34 @@ type pipenv && pipenv --version || false
type poetry && poetry --version || false
type bundle && bundle --version || false
type mvn && mvn --version || false
type gradle && gradle -version || false
type gradle && gradle --version || false
type go && go version || false
type cargo && cargo --version || false
type rustup && rustup --version && rustup show || false
type dotnet && dotnet --info || false
EOF
)

# The tests are meant to run as a non-root user/group to ensure all the required
# tools are available globally. This is an attempt to use the current user/group
# like when provided via `--user $(id -u):$(id -g)` and fall back to hard coded
# values when it happens to be run as `root:root`.
USER=$(id -u)
if [ "${USER}" -eq "0" ]; then
USER=1000;
fi
GROUP=$(id -g)
if [ "${GROUP}" -eq "0" ]; then
GROUP=1000;
fi

echo " [+] Running with UID:GID of: ${USER}:${GROUP}"

if [ -z "${SLIM:-}" ]; then
echo " [+] \`--slim\` option not specified. Running all tests ..."
docker run --rm --entrypoint entrypoint.sh "${IMAGE}" "${SLIM_COMMANDS}"
docker run --rm --entrypoint entrypoint.sh "${IMAGE}" "${MANIFEST_COMMANDS}"
docker run --rm --entrypoint entrypoint.sh --user "${USER}:${GROUP}" "${IMAGE}" "${SLIM_COMMANDS}"
docker run --rm --entrypoint entrypoint.sh --user "${USER}:${GROUP}" "${IMAGE}" "${MANIFEST_COMMANDS}"
else
echo " [+] \`--slim\` option specified. Skipping the lockfile generation tests ..."
docker run --rm --entrypoint entrypoint.sh "${IMAGE}" "${SLIM_COMMANDS}"
docker run --rm --entrypoint entrypoint.sh --user "${USER}:${GROUP}" "${IMAGE}" "${SLIM_COMMANDS}"
fi

3 comments on commit 91627e7

@marvin-hansen
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@maxrake I think the officially accepted solution is to add a non-root user during the build stage, copy the guid/uuis to the final stage, and switch to the non-root user for binary execution.
At least that's what we did for production Kubernetes deployments.

Docker docs
https://www.geeksforgeeks.org/docker-user-instruction/

Example docker file
https://github.com/bjornmolin/rust-minimal-docker/blob/master/Dockerfile

Note, the user is added in line 5

RUN groupadd -g 10001 -r dockergrp && useradd -r -g dockergrp -u 10001 dockeruser

and the config is copied over in line 29 with the user switch in line 30.

COPY --from=0 /etc/passwd /etc/passwd
USER dockeruser

That way, you prevent the problem of missing guuid/uuid altogether while running the final binary as non-root.

@kylewillmon
Copy link
Contributor

@kylewillmon kylewillmon commented on 91627e7 Oct 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

USER dockeruser

That way, you prevent the problem of missing guuid/uuid altogether while running the final binary as non-root.

This unfortunately does not completely solve the problem for our usage. Because we support things like Azure pipelines which creates a new user in the container at runtime (an annoying design decision... but we cannot change it). See #122 and related for some history.

For this reason, phylum-ci attempts to support running as any arbitrary user.

@marvin-hansen
Copy link

@marvin-hansen marvin-hansen commented on 91627e7 Oct 17, 2023 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.