diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 59f753f7..5a9ad789 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,6 +100,16 @@ jobs: --build-arg PULUMI_VERSION=${{ env.PULUMI_VERSION }} \ --load \ docker/pulumi + - name: Build nonroot variant + run: | + docker build \ + -f docker/pulumi/Dockerfile \ + --platform linux/amd64 \ + -t ${{ env.DOCKER_ORG }}/pulumi:${{ env.PULUMI_VERSION }}-nonroot \ + --target nonroot \ + --build-arg PULUMI_VERSION=${{ env.PULUMI_VERSION }} \ + --load \ + docker/pulumi - name: Install go uses: actions/setup-go@v5 with: @@ -152,6 +162,29 @@ jobs: --entrypoint /src/pulumi-test-containers \ ${{ env.DOCKER_ORG }}/pulumi:${{ env.PULUMI_VERSION }} \ -test.timeout=1h -test.v + - name: Tests for nonroot variant + run: | + docker run \ + -e RUN_CONTAINER_TESTS=true \ + -e IMAGE_VARIANT=pulumi-nonroot \ + -e PULUMI_ACCESS_TOKEN=${PULUMI_ACCESS_TOKEN} \ + -e PULUMI_ORG=${PULUMI_ORG} \ + -e ARM_CLIENT_ID=${ARM_CLIENT_ID} \ + -e ARM_CLIENT_SECRET=${ARM_CLIENT_SECRET} \ + -e ARM_TENANT_ID=${ARM_TENANT_ID} \ + -e ARM_SUBSCRIPTION_ID=${ARM_SUBSCRIPTION_ID} \ + -e AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \ + -e AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \ + -e AWS_SESSION_TOKEN=${AWS_SESSION_TOKEN} \ + -e AWS_REGION=${AWS_REGION} \ + -e GCP_PROJECT_NAME=${GCP_PROJECT_NAME} \ + -e GCP_PROJECT_NUMBER=${GCP_PROJECT_NUMBER} \ + -e GOOGLE_APPLICATION_CREDENTIALS=/src/creds.json \ + --mount type=bind,source=$GOOGLE_APPLICATION_CREDENTIALS,target=/src/creds.json \ + --volume /tmp:/src \ + --entrypoint /src/pulumi-test-containers \ + ${{ env.DOCKER_ORG }}/pulumi:${{ env.PULUMI_VERSION }}-nonroot \ + -test.timeout=1h -test.v provider-build-environment: name: Provider Build Environment image diff --git a/.github/workflows/snyk-scan.yml b/.github/workflows/snyk-scan.yml index ccd1cdc8..b69423c7 100644 --- a/.github/workflows/snyk-scan.yml +++ b/.github/workflows/snyk-scan.yml @@ -19,6 +19,10 @@ jobs: strategy: matrix: image: ["pulumi", "pulumi-provider-build-environment"] + include: + # For the pulumi image add a the nonroot variant + - suffix: -nonroot + image: pulumi steps: - uses: actions/checkout@master - name: Set version @@ -30,7 +34,7 @@ jobs: env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} with: - image: ${{ env.DOCKER_ORG }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }} + image: ${{ env.DOCKER_ORG }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }}${{ matrix.suffix }} args: --severity-threshold=high --file=docker/pulumi/Dockerfile base: diff --git a/.github/workflows/sync-ecr.yml b/.github/workflows/sync-ecr.yml index 98566e9b..607be2fc 100644 --- a/.github/workflows/sync-ecr.yml +++ b/.github/workflows/sync-ecr.yml @@ -28,6 +28,10 @@ jobs: strategy: matrix: image: ["pulumi", "pulumi-provider-build-environment"] + include: + # For the pulumi image add a the nonroot variant + - suffix: -nonroot + image: pulumi steps: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v1 @@ -47,15 +51,15 @@ jobs: docker login -u AWS --password-stdin https://public.ecr.aws - name: Tag ${{ env.PULUMI_VERSION }} and push to AWS Public ECR run: | - docker pull docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }} - docker tag docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }} public.ecr.aws/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }} - docker push public.ecr.aws/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }} + docker pull docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }}${{ matrix.suffix }} + docker tag docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }}${{ matrix.suffix }} public.ecr.aws/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }}${{ matrix.suffix }} + docker push public.ecr.aws/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }}${{ matrix.suffix }} - name: Tag latest and push to AWS Public ECR if: ${{ github.event.inputs.tag_latest || github.event_name == 'repository_dispatch' }} run: | - docker pull docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest - docker tag docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest public.ecr.aws/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest - docker push public.ecr.aws/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest + docker pull docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest${{ matrix.suffix }} + docker tag docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest${{ matrix.suffix }} public.ecr.aws/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest${{ matrix.suffix }} + docker push public.ecr.aws/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest${{ matrix.suffix }} define-debian-matrix: runs-on: ubuntu-latest diff --git a/.github/workflows/sync-ghcr.yml b/.github/workflows/sync-ghcr.yml index 8b87391a..c3015925 100644 --- a/.github/workflows/sync-ghcr.yml +++ b/.github/workflows/sync-ghcr.yml @@ -28,6 +28,10 @@ jobs: strategy: matrix: image: ["pulumi", "pulumi-provider-build-environment"] + include: + # For the pulumi image add a the nonroot variant + - suffix: -nonroot + image: pulumi steps: - name: Login to GitHub Container Registry uses: docker/login-action@v1 @@ -37,15 +41,15 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Tag ${{ env.PULUMI_VERSION }} and push to GHCR run: | - docker pull docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }} - docker tag docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }} ghcr.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }} - docker push ghcr.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }} + docker pull docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }}${{ matrix.suffix }} + docker tag docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }}${{ matrix.suffix }} ghcr.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }}${{ matrix.suffix }} + docker push ghcr.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:${{ env.PULUMI_VERSION }}${{ matrix.suffix }} - name: Tag latest and push to GHCR if: ${{ github.event.inputs.tag_latest || github.event_name == 'repository_dispatch' }} run: | - docker pull docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest - docker tag docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest ghcr.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest - docker push ghcr.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest + docker pull docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest${{ matrix.suffix }} + docker tag docker.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest${{ matrix.suffix }} ghcr.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest${{ matrix.suffix }} + docker push ghcr.io/${{ env.DOCKER_USERNAME }}/${{ matrix.image }}:latest${{ matrix.suffix }} define-debian-matrix: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 636ad29e..6580b779 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Unreleased +- Add nonroot variant for the kitchen sink image +([#277](https://github.com/pulumi/pulumi-docker-containers/pull/277) + - Add per language versions for ubi images ([#260](https://github.com/pulumi/pulumi-docker-containers/pull/260)) diff --git a/docker/pulumi/Dockerfile b/docker/pulumi/Dockerfile index bc6db889..a640fae2 100644 --- a/docker/pulumi/Dockerfile +++ b/docker/pulumi/Dockerfile @@ -146,6 +146,24 @@ ENV PATH="/pulumi/bin:${PATH}" # I think it's safe to say if we're using this mega image, we want pulumi ENTRYPOINT ["pulumi"] +# Nonroot variant of the image +# +# This sets up a non-root user and uses that user for the image. +######################################################################## + +FROM base AS nonroot + +LABEL "repository"="https://github.com/pulumi/pulumi" +LABEL "homepage"="https://pulumi.com" +LABEL "maintainer"="Pulumi Team " +LABEL org.opencontainers.image.description="The Pulumi CLI, in a Docker container." + +ARG UID=1000 +ARG GID=1000 +RUN addgroup --gid $GID pulumi && \ + adduser --uid $UID --gid $GID --disabled-password --gecos "" pulumi +USER pulumi:pulumi + # Pulumi Bridged Terraform Provider Build Environment # # Bundles together everything needed to build a Terraform-based diff --git a/tests/containers_test.go b/tests/containers_test.go index 07df9fc4..ec5ff245 100644 --- a/tests/containers_test.go +++ b/tests/containers_test.go @@ -368,6 +368,7 @@ func TestEnvironment(t *testing.T) { // to ~/.bashrc. This test ensures that we notice such modifications. expectedPaths := map[string]string{ "pulumi": "/pulumi/bin:/usr/local/share/fnm/aliases/default/bin:/usr/local/share/pyenv/shims:/usr/local/share/pyenv/bin:/usr/local/share/dotnet:/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "pulumi-nonroot": "/pulumi/bin:/usr/local/share/fnm/aliases/default/bin:/usr/local/share/pyenv/shims:/usr/local/share/pyenv/bin:/usr/local/share/dotnet:/go/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "pulumi-debian-dotnet": "/root/.dotnet:/pulumi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "pulumi-debian-go": "/pulumi/bin:/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "pulumi-debian-java": "/pulumi/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", @@ -409,7 +410,7 @@ func TestEnvironment(t *testing.T) { t.Run("Workdir", func(t *testing.T) { t.Parallel() // Kitchen sink does not set `WORKDIR`. - if imageVariant == "pulumi" { + if imageVariant == "pulumi" || imageVariant == "pulumi-nonroot" { requireOutput(t, "/", "pwd") requireOutputWithBash(t, "/", "pwd") } else { @@ -420,14 +421,24 @@ func TestEnvironment(t *testing.T) { t.Run("User", func(t *testing.T) { t.Parallel() - requireOutput(t, "root", "whoami") - requireOutputWithBash(t, "root", "whoami") + if isNonRoot(t) { + requireOutput(t, "pulumi", "whoami") + requireOutputWithBash(t, "pulumi", "whoami") + } else { + requireOutput(t, "root", "whoami") + requireOutputWithBash(t, "root", "whoami") + } }) t.Run("Home", func(t *testing.T) { t.Parallel() - requireOutput(t, "/root", "printenv", "HOME") - requireOutputWithBash(t, "/root", "printenv", "HOME") + if isNonRoot(t) { + requireOutput(t, "/home/pulumi", "printenv", "HOME") + requireOutputWithBash(t, "/home/pulumi", "printenv", "HOME") + } else { + requireOutput(t, "/root", "printenv", "HOME") + requireOutputWithBash(t, "/root", "printenv", "HOME") + } }) } @@ -485,6 +496,11 @@ func isUBI(t *testing.T) bool { return strings.HasPrefix(imageVariant, "pulumi-ubi") } +func isNonRoot(t *testing.T) bool { + imageVariant := mustEnv(t, "IMAGE_VARIANT") + return strings.HasSuffix(imageVariant, "-nonroot") +} + func RandomStackName(t *testing.T) string { t.Helper() b := make([]byte, 4)