From abc2570e42d3b01b56d34a474eedbf13063d3c31 Mon Sep 17 00:00:00 2001 From: John Schutz <328434+tofupup@users.noreply.github.com> Date: Wed, 7 Sep 2022 07:19:52 -0500 Subject: [PATCH] feat: Allow running container as non-root UID/GID for ownership issues (docker) (#433) Co-authored-by: George L. Yermulnik Co-authored-by: MaxymVlasov Co-authored-by: Anton Babenko --- .dockerignore | 1 + .github/.container-structure-test-config.yaml | 13 +++ .github/workflows/build-image-test.yaml | 2 + Dockerfile | 11 ++- README.md | 37 ++++++++- hooks/terraform_wrapper_module_for_each.sh | 3 + tools/entrypoint.sh | 82 +++++++++++++++++++ 7 files changed, 143 insertions(+), 6 deletions(-) create mode 100755 tools/entrypoint.sh diff --git a/.dockerignore b/.dockerignore index 50c8ea340..a78e675d2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,4 @@ * !.dockerignore !Dockerfile +!tools/entrypoint.sh diff --git a/.github/.container-structure-test-config.yaml b/.github/.container-structure-test-config.yaml index a860febe4..c485db00f 100644 --- a/.github/.container-structure-test-config.yaml +++ b/.github/.container-structure-test-config.yaml @@ -60,6 +60,19 @@ commandTests: args: [ "version" ] expectedOutput: [ "([0-9]+\\.){2}[0-9]+\\n$" ] + - name: "entrypoint.sh" + envVars: + - key: "USERID" + value: "1000:1000" + command: "/entrypoint.sh" + args: [ "-V" ] + expectedError: ["^ERROR: uid:gid 1000:1000 lacks permissions to //\\n$"] + exitCode: 1 + + - name: "su-exec" + command: "su-exec" + expectedOutput: ["^Usage: su-exec user-spec command \\[args\\]\\n$"] + fileExistenceTests: - name: 'terrascan init' path: '/root/.terrascan/pkg/policies/opa/rego/github/github_repository/privateRepoEnabled.rego' diff --git a/.github/workflows/build-image-test.yaml b/.github/workflows/build-image-test.yaml index 9ba0282b4..451de9606 100644 --- a/.github/workflows/build-image-test.yaml +++ b/.github/workflows/build-image-test.yaml @@ -19,6 +19,8 @@ jobs: with: files: | Dockerfile + .dockerignore + tools/entrypoint.sh - name: Build if Dockerfile changed if: steps.changed-files-specific.outputs.any_changed == 'true' diff --git a/Dockerfile b/Dockerfile index 809a9059a..bb598ad5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,8 +5,7 @@ WORKDIR /bin_dir RUN apk add --no-cache \ # Builder deps - curl=~7 \ - unzip=~6 && \ + curl=~7 && \ # Upgrade pip for be able get latest Checkov python3 -m pip install --no-cache-dir --upgrade pip @@ -177,7 +176,9 @@ RUN apk add --no-cache \ bash=~5 \ # pre-commit-hooks deps: https://github.com/pre-commit/pre-commit-hooks musl-dev=~1 \ - gcc=~10 + gcc=~10 \ + # entrypoint wrapper deps + su-exec=~0.2 # Copy tools COPY --from=builder \ @@ -203,9 +204,11 @@ RUN if [ "$(grep -o '^terraform-docs SKIPPED$' /usr/bin/tools_versions_info)" = # unsafe repository ('/lint' is owned by someone else) git config --global --add safe.directory /lint +COPY tools/entrypoint.sh /entrypoint.sh + ENV PRE_COMMIT_COLOR=${PRE_COMMIT_COLOR:-always} ENV INFRACOST_API_KEY=${INFRACOST_API_KEY:-} ENV INFRACOST_SKIP_UPDATE_CHECK=${INFRACOST_SKIP_UPDATE_CHECK:-false} -ENTRYPOINT [ "pre-commit" ] +ENTRYPOINT [ "/entrypoint.sh" ] diff --git a/README.md b/README.md index a7e820f33..82c04dad6 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,7 @@ If you are using `pre-commit-terraform` already or want to support its developme * [terraform_wrapper_module_for_each](#terraform_wrapper_module_for_each) * [terrascan](#terrascan) * [tfupdate](#tfupdate) +* [Docker Usage: File Permissions](#docker-usage-file-permissions) * [Authors](#authors) * [License](#license) * [Additional information for users from Russia and Belarus](#additional-information-for-users-from-russia-and-belarus) @@ -227,16 +228,18 @@ pre-commit run -a Or, using Docker ([available tags](https://github.com/antonbabenko/pre-commit-terraform/pkgs/container/pre-commit-terraform/versions)): +**NOTE:** This command uses your user id and group id for the docker container to use to access the local files. If the files are owned by another user, update the `USERID` environment variable. See [File Permissions section](#docker-usage-file-permissions) for more information. + ```bash TAG=latest -docker run -v $(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:$TAG run -a +docker run -e "USERID=$(id -u):$(id -g)" -v $(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:$TAG run -a ``` Execute this command to list the versions of the tools in Docker: ```bash TAG=latest -docker run --entrypoint cat ghcr.io/antonbabenko/pre-commit-terraform:$TAG /usr/bin/tools_versions_info +docker run --rm --entrypoint cat ghcr.io/antonbabenko/pre-commit-terraform:$TAG /usr/bin/tools_versions_info ``` ## Available Hooks @@ -735,6 +738,16 @@ Sample configuration: - --args=--verbose # Verbose output ``` +**If you use hook inside Docker:** +The `terraform_wrapper_module_for_each` hook attempts to determine the module's short name to be inserted into the generated `README.md` files for the `source` URLs. Since the container uses a bind mount at a static location, it can cause this short name to be incorrect. +If the generated name is incorrect, set them by providing the `module-repo-shortname` option to the hook: + +```yaml +- id: terraform_wrapper_module_for_each + args: + - '--args=--module-repo-shortname=ec2-instance' +``` + ### terrascan 1. `terrascan` supports custom arguments so you can pass supported flags like `--non-recursive` and `--policy-type` to disable recursive inspection and set the policy type respectively: @@ -779,6 +792,26 @@ Sample configuration: Check [`tfupdate` usage instructions](https://github.com/minamijoyo/tfupdate#usage) for other available options and usage examples. No need to pass `--recursive .` as it is added automatically. +## Docker Usage: File Permissions + +A mismatch between the Docker container's user and the local repository file ownership can cause permission issues in the repository where `pre-commit` is run. The container runs as the `root` user by default, and uses a `tools/entrypoint.sh` script to assume a user ID and group ID if specified by the environment variable `USERID`. + +The [recommended command](#4-run) to run the Docker container is: + +```bash +TAG=latest +docker run -e "USERID=$(id -u):$(id -g)" -v $(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:$TAG run -a +``` + +which uses your current session's user ID and group ID to set the variable in the run command. Without this setting, you may find files and directories owned by `root` in your local repository. + +If the local repository is using a different user or group for permissions, you can modify the `USERID` to the user ID and group ID needed. **Do not use the username or groupname in the environment variable, as it has no meaning in the container.** You can get the current directory's owner user ID and group ID from the 3rd (user) and 4th (group) columns in `ls` output: + +```bash +$ ls -aldn . +drwxr-xr-x 9 1000 1000 4096 Sep 1 16:23 . +``` + ## Authors This repository is managed by [Anton Babenko](https://github.com/antonbabenko) with help from these awesome contributors: diff --git a/hooks/terraform_wrapper_module_for_each.sh b/hooks/terraform_wrapper_module_for_each.sh index 6b1e6da1e..c15c9e885 100755 --- a/hooks/terraform_wrapper_module_for_each.sh +++ b/hooks/terraform_wrapper_module_for_each.sh @@ -414,6 +414,9 @@ function create_tmp_file_tf { mv "$tmp_file" "$tmp_file.tf" tmp_file_tf="$tmp_file.tf" + # mktemp creates with no group/other read permissions + chmod a+r "$tmp_file_tf" + echo "$CONTENT_MAIN_TF" > "$tmp_file_tf" } diff --git a/tools/entrypoint.sh b/tools/entrypoint.sh new file mode 100755 index 000000000..86d5e369a --- /dev/null +++ b/tools/entrypoint.sh @@ -0,0 +1,82 @@ +#!/usr/bin/env bash +#exit on error +set -e + +readonly USERBASE="run" +readonly BASHPATH="/bin/bash" +readonly HOMEPATH="/home" + +function echo_error_and_exit { + echo -e "ERROR: " "$@" >&2 + exit 1 +} + +# make sure entrypoint is running as root +if [[ $(id -u) -ne 0 ]]; then + echo_error_and_exit "Container must run as root. Use environment variable USERID to set user.\n" \ + "Example: \"TAG=latest && " \ + "docker run -e USERID=$(id -u):$(id -g) -v $(pwd):/lint -w /lint ghcr.io/antonbabenko/pre-commit-terraform:$TAG run -a\"" +fi + +# make sure USERID makes sense as UID:GID +# it looks like the alpine distro limits UID and GID to 256000, but +# could be more, so we accept any valid integers +USERID=${USERID:-"0:0"} +if [[ ! $USERID =~ ^[0-9]+:[0-9]+$ ]]; then + echo_error_and_exit "USERID environment variable invalid, format is userid:groupid. Received: \"$USERID\"" +fi + +# separate uid and gid +uid=${USERID%%:*} +gid=${USERID##*:} + +# if requested UID:GID is root, go ahead and run without other processing +[[ $USERID == "0:0" ]] && exec su-exec "$USERID" pre-commit "$@" + +# make sure workdir and some files are readable/writable by the provided UID/GID +# combo, otherwise will have errors when processing hooks +wdir="$(pwd)" +if ! su-exec "$USERID" "$BASHPATH" -c "test -w $wdir && test -r $wdir"; then + echo_error_and_exit "uid:gid $USERID lacks permissions to $wdir/" +fi +wdirgitindex="$wdir/.git/index" +if ! su-exec "$USERID" "$BASHPATH" -c "test -w $wdirgitindex && test -r $wdirgitindex"; then + echo_error_and_exit "uid:gid $USERID cannot write to $wdirgitindex" +fi + +# check if group by this GID already exists, if so get the name since adduser +# only accepts names +if groupinfo="$(getent group "$gid")"; then + groupname="${groupinfo%%:*}" +else + # create group in advance in case GID is different than UID + groupname="$USERBASE$gid" + if ! err="$(addgroup -g "$gid" "$groupname" 2>&1)"; then + echo_error_and_exit "failed to create gid \"$gid\" with name \"$groupname\"\ncommand output: \"$err\"" + fi +fi + +# check if user by this UID already exists, if so get the name since id +# only accepts names +if userinfo="$(getent passwd "$uid")"; then + username="${userinfo%%:*}" +else + username="$USERBASE$uid" + if ! err="$(adduser -h "$HOMEPATH$username" -s "$BASHPATH" -G "$groupname" -D -u "$uid" -k "$HOME" "$username" 2>&1)"; then + echo_error_and_exit "failed to create uid \"$uid\" with name \"$username\" and group \"$groupname\"\ncommand output: \"$err\"" + fi +fi + +# it's possible it was not in the group specified, add it +if ! idgroupinfo="$(id -G "$username" 2>&1)"; then + echo_error_and_exit "failed to get group list for username \"$username\"\ncommand output: \"$idgroupinfo\"" +fi +if [[ ! " $idgroupinfo " =~ [[:blank:]]${gid}[[:blank:]] ]]; then + if ! err="$(addgroup "$username" "$groupname" 2>&1)"; then + echo_error_and_exit "failed to add user \"$username\" to group \"$groupname\"\ncommand output: \"$err\"" + fi +fi + +# user and group of specified UID/GID should exist now, and user should be +# a member of group, so execute pre-commit +exec su-exec "$USERID" pre-commit "$@"