diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index e018659..4436348 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -9,6 +9,9 @@ jobs: build: name: build runs-on: ubuntu-latest + strategy: + matrix: + test-type: ["buildx", "buildkit-container", "custom-socket"] steps: - name: Setup BATS uses: mig4/setup-bats@af9a00deb21b5d795cabfeaa8d9060410377686d # v1.2.0 @@ -20,13 +23,13 @@ jobs: - name: Install Trivy run: | - curl -fsSL -o trivy.tar.gz https://github.com/aquasecurity/trivy/releases/download/v${{ env.TRIVY_VERSION }}/trivy_${{ env.TRIVY_VERSION }}_Linux-64bit.tar.gz - tar -zxvf trivy.tar.gz - cp trivy /usr/local/bin/ + curl -fsSL -o trivy.tar.gz https://github.com/aquasecurity/trivy/releases/download/v${{ env.TRIVY_VERSION }}/trivy_${{ env.TRIVY_VERSION }}_Linux-64bit.tar.gz + tar -zxvf trivy.tar.gz + cp trivy /usr/local/bin/ - - name: Generate trivy vuln report for nginx image + - name: Generate trivy vuln report for opa image run: | - trivy image --vuln-type os --ignore-unfixed -f json -o /tmp/nginx.1.21.6.json docker.io/library/nginx:1.21.6 + trivy image --vuln-type os --ignore-unfixed -f json -o /tmp/opa.0.46.0.json docker.io/openpolicyagent/opa:0.46.0 - name: Get latest copa version run: | @@ -37,19 +40,49 @@ jobs: - name: Install Copa run: | - curl --retry 5 -fsSL -o copa.tar.gz https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copa_${COPA_VERSION}_linux_amd64.tar.gz - tar -zxvf copa.tar.gz - cp copa /usr/local/bin/ + curl --retry 5 -fsSL -o copa.tar.gz https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copa_${COPA_VERSION}_linux_amd64.tar.gz + tar -zxvf copa.tar.gz + cp copa /usr/local/bin/ + + - name: Run Buildkit container + if: matrix.test-type == 'buildkit-container' + run : | + docker run --net=host --detach --rm --privileged -p 127.0.0.1:8888:8888 --name buildkitd --entrypoint buildkitd moby/buildkit:v${{ env.BUILDKIT_VERSION }} --addr tcp://0.0.0.0:8888 + + - name: Set up Docker + if: matrix.test-type == 'custom-socket' + uses: crazy-max/ghaction-setup-docker@v3 + with: + daemon-config: | + { + "debug": true, + "experimental": true, + "features": { + "containerd-snapshotter": true + } + } - name: Bats Test run: | - docker run --net=host --detach --rm --privileged -p 127.0.0.1:8888:8888 --name buildkitd --entrypoint buildkitd moby/buildkit:v${{ env.BUILDKIT_VERSION }} --addr tcp://0.0.0.0:8888 + set -ex + export SOCKET="/var/run/docker.sock" + export CONTEXT="default" + + if [ "${{ matrix.test-type }}" = "custom-socket" ]; then + url=$(docker context inspect | jq -r .[0].Endpoints.docker.Host) + SOCKET=$(echo "$url" | awk -F// '{print $2}') + CONTEXT="setup-docker-action" + fi + docker build --build-arg copa_version=${COPA_VERSION} -t copa-action . docker run --net=host \ --mount=type=bind,source=/tmp,target=/data \ - --mount=type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock \ + --mount=type=bind,source="$SOCKET",target=/var/run/docker.sock \ --mount=type=bind,source=$GITHUB_OUTPUT,target=$GITHUB_OUTPUT -e GITHUB_OUTPUT \ --name=copa-action \ - copa-action 'docker.io/library/nginx:1.21.6' 'nginx.1.21.6.json' '1.21.6-patched' '10m' 'output.json' 'openvex' - docker images + copa-action 'docker.io/openpolicyagent/opa:0.46.0' 'opa.0.46.0.json' '0.46.0-patched' '10m' "${{ matrix.test-type }}" 'openvex' 'output.json' + + # saving patched image to give trivy access when using a custom socket + docker -c "$CONTEXT" save -o patched.tar openpolicyagent/opa:0.46.0-patched + bats --print-output-on-failure ./test/test.bats diff --git a/.github/workflows/patch.yaml b/.github/workflows/patch.yaml index d40c353..6116dd7 100644 --- a/.github/workflows/patch.yaml +++ b/.github/workflows/patch.yaml @@ -20,7 +20,7 @@ jobs: matrix: # provide relevant list of images to scan on each run images: - - "docker.io/library/nginx:1.21.6" + - "docker.io/library/alpine:3.18.4" - "docker.io/openpolicyagent/opa:0.46.0" - "docker.io/library/hello-world:latest" steps: diff --git a/Dockerfile b/Dockerfile index 558d723..2e9d16c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN apt-get update && \ tee /etc/apt/sources.list.d/docker.list > /dev/null && \ # Install Docker apt-get update && \ - apt-get install -y docker-ce docker-ce-cli containerd.io --no-install-recommends + apt-get install -y docker-ce docker-ce-cli docker-buildx-plugin containerd.io --no-install-recommends # Install Copa RUN curl --retry 5 -fsSL -o copa.tar.gz https://github.com/project-copacetic/copacetic/releases/download/v${copa_version}/copa_${copa_version}_linux_amd64.tar.gz && \ diff --git a/README.md b/README.md index 8610f14..8a82b29 100644 --- a/README.md +++ b/README.md @@ -7,16 +7,21 @@ Copacetic Action is supported with Copa version 0.3.0 and later. ## Inputs -| Name | Type | Required | Default | Description | -| ------------------ | ------ | -------- | --------- | ------------------------------------------------------ | -| `image` | String | True | | Image reference to patch | -| `image-report` | String | True | | Trivy JSON vulnerability report of the image to patch | -| `patched-tag` | String | True | | Patched image tag | -| `timeout` | String | False | `5m` | Timeout for `copa patch` | -| `buildkit-version` | String | False | `latest` | Buildkit version | -| `copa-version` | String | False | `latest` | Copa version | -| `output` | String | False | | Output filename (available with copa v0.5.0 and later) | -| `format` | String | False | `openvex` | Output format (available with copa v0.5.0 and later) | +| Name | Type | Required | Default | Description | +| ------------------ | ------ | -------- | --------- | -------------------------------------------------------------------------- | +| `image` | String | True | | Image reference to patch | +| `image-report` | String | True | | Trivy JSON vulnerability report of the image to patch | +| `patched-tag` | String | True | | Patched image tag | +| `timeout` | String | False | `5m` | Timeout for `copa patch` | +| `buildkit-version` | String | False | `latest` | Buildkit version | +| `copa-version` | String | False | `latest` | Copa version | +| `output` | String | False | | Output filename (available with copa-action v0.6.1 and later) | +| `format` | String | False | `openvex` | Output format (available with copa-action v0.6.1 and later) | +| `custom-socket` | String | False | | Custom Docker socket address (available with copa-action v0.6.1 and later) | + +> [!NOTE] +> Features that are supported with new Copacetic releases will not neccessarily align with what is supported with Copa Action versions. +> For example, the `output` file feature was released in Copa v0.5.0, but is supported with Copa Action v0.6.1 and later. ## Outputs @@ -24,6 +29,49 @@ Copacetic Action is supported with Copa version 0.3.0 and later. | --------------- | ------ | ------------------------------------ | | `patched-image` | String | Image reference of the patched image | +## Ways to connect to Buildkit +> [!NOTE] +Custom Buildkit connection to patch local or private images is only available with Copa-Action versions 0.6.1 and later. For all earlier Copa-Action versions, Buildkit in a container is the default approach, and a version must be supplied as input. + +### Option 1: Connect to buildx instance (default) +By default, Copa Action creates its own Buildx instance to connect to for patching public and private images. + +### Option 2: Connect using defaults through a custom socket +To patch local images, `copa` is limited to using `docker`'s built-in buildkit service, and must use the [`containerd image store`](https://docs.docker.com/storage/containerd/) feature. To enable this in your Github workflow, use `ghaction-setup-docker`'s [daemon-configuration](https://github.com/crazy-max/ghaction-setup-docker#daemon-configuration) to set `"containerd-snapshotter": true`. + +Example: +``` yaml + - name: Set up Docker + uses: crazy-max/ghaction-setup-docker@v3 + with: + daemon-config: | + { + "debug": true, + "experimental": true, + "features": { + "containerd-snapshotter": true + } + } + - name: Get socket path + run: | + url=$(docker context inspect | jq -r .[0].Endpoints.docker.Host) + socket_path=$(echo "$url" | awk -F// '{print $2}') + echo "$socket_path" + echo "SOCKET=$socket_path" >> $GITHUB_ENV +``` + +Then, supply the resulting socket path (`$SOCKET`) as the input `custom-socket` for the Copa Action to connect to. +> [!NOTE] +> Copa Action will load the image to the default docker context, not the "setup-docker-action" context. + +### Option 3: Buildkit in a container +To connect via buildkit in a container, provide the input `buildkit-version`. Copa Action will create a buildkit container with that version to connect to. +> [!NOTE] +> This approach does not allow for patching of local or private images. + + +Refer to [Copacetic documentation](https://project-copacetic.github.io/copacetic/website/custom-address) to learn more about connecting Copa to Buildkit. + ## Example usage -https://github.com/project-copacetic/copa-action/blob/941743581b0da5e581ca5a575f9316228c2f6c00/.github/workflows/patch.yaml#L1-L77 \ No newline at end of file +https://github.com/project-copacetic/copa-action/blob/941743581b0da5e581ca5a575f9316228c2f6c00/.github/workflows/patch.yaml#L1-L77 diff --git a/action.yaml b/action.yaml index 0bc767a..55ebd54 100644 --- a/action.yaml +++ b/action.yaml @@ -25,6 +25,8 @@ inputs: format: description: "Output format" default: "openvex" + custom-socket: + description: "Custom socket address if setting up containerd image store" outputs: patched-image: description: 'Image reference of patched image' @@ -32,22 +34,30 @@ outputs: runs: using: "composite" steps: - - name: docker run buildkitd - shell: bash - run: | - if [ -z "${{ inputs.buildkit-version }}" ]; then - docker run --net=host --detach --rm --privileged -p 127.0.0.1:8888:8888 --name buildkitd --entrypoint buildkitd moby/buildkit:latest --addr tcp://0.0.0.0:8888 - else - docker run --net=host --detach --rm --privileged -p 127.0.0.1:8888:8888 --name buildkitd --entrypoint buildkitd moby/buildkit:${{ inputs.buildkit-version }} --addr tcp://0.0.0.0:8888 - fi - - name: docker run copa-action + - name: Docker run copa-action id: copa-action shell: bash run : | + # check for copa version input, else use latest if [ -z "${{ inputs.copa-version }}" ]; then latest_tag=$(curl --retry 5 -s "https://api.github.com/repos/project-copacetic/copacetic/releases/latest" | jq -r '.tag_name') version=${latest_tag:1} else version="${{ inputs.copa-version }}" fi - docker run --net=host --mount=type=bind,source=$(pwd),target=/data --mount=type=bind,source=/var/run/docker.sock,target=/var/run/docker.sock --mount=type=bind,source=$GITHUB_OUTPUT,target=$GITHUB_OUTPUT -e GITHUB_OUTPUT --name=copa-action "ghcr.io/project-copacetic/copa-action:v$version" ${{ inputs.image }} ${{ inputs.image-report }} ${{ inputs.patched-tag }} ${{ inputs.timeout }} ${{ inputs.output }} ${{ inputs.format }} + + # default docker socket and connection type + socket="/var/run/docker.sock" + connection="buildx" + + # check for other methods of connection + if [ -n "{{ inputs.custom-socket }}"]; then + socket="${{ inputs.custom-socket }}" + connection="custom-socket" + elif [ -n "${{ inputs.buildkit-version }}" ]; then + docker run --net=host --detach --rm --privileged -p 127.0.0.1:8888:8888 --name buildkitd --entrypoint buildkitd moby/buildkit:${{ inputs.buildkit-version }} --addr tcp://0.0.0.0:8888 + connection="buildkit-container" + fi + + # run copa-action based on inputs + docker run --net=host --mount=type=bind,source=$(pwd),target=/data --mount=type=bind,source="$socket",target="/var/run/docker.sock" --mount=type=bind,source=$GITHUB_OUTPUT,target=$GITHUB_OUTPUT -e GITHUB_OUTPUT --name=copa-action "ghcr.io/project-copacetic/copa-action:v$version" ${{ inputs.image }} ${{ inputs.image-report }} ${{ inputs.patched-tag }} ${{ inputs.timeout }} "$connection" ${{ inputs.format }} {{ inputs.output }} diff --git a/entrypoint.sh b/entrypoint.sh index bb3b784..45930e4 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,12 +1,14 @@ -#!/bin/sh +#!/bin/sh + +set -ex; image=$1 report=$2 patched_tag=$3 timeout=$4 -output_file=$5 +connection_format=$5 format=$6 - +output_file=$7 # parse image into image name image_no_tag=$(echo "$image" | cut -d':' -f1) @@ -16,11 +18,29 @@ if [ -z "$output_file" ] then output="" else - output="--format $format --output ./data/"$output_file"" + output="--format $format --output ./data/$output_file" fi +# check selected method of buildkit connection +case "$connection_format" in + # through a buildx instance + "buildx") + docker buildx create --name=copa-action + docker buildx use --default copa-action + connection="--addr buildx://copa-action" + ;; + # through a running buildkit container over tcp + "buildkit-container") + connection="--addr tcp://127.0.0.1:8888" + ;; + # through the default docker buildkit endpoint enabled with a custom socket + "custom-socket") + connection="" + ;; +esac + # run copa to patch image -if copa patch -i "$image" -r ./data/"$report" -t "$patched_tag" --addr tcp://127.0.0.1:8888 --timeout $timeout $output; +if copa patch -i "$image" -r ./data/"$report" -t "$patched_tag" $connection --timeout $timeout $output; then patched_image="$image_no_tag:$patched_tag" echo "patched-image=$patched_image" >> "$GITHUB_OUTPUT" @@ -28,4 +48,3 @@ else echo "Error patching image $image with copa" exit 1 fi - diff --git a/test/test.bats b/test/test.bats index be4310f..e5fd970 100755 --- a/test/test.bats +++ b/test/test.bats @@ -2,13 +2,9 @@ load helpers -teardown_file() { - docker stop buildkitd -} - @test "Check patched image exists" { docker images - id=$(docker images --quiet 'nginx:1.21.6-patched') + id=$(docker images --quiet 'openpolicyagent/opa:0.46.0-patched') assert_not_equal "$id" "" } @@ -18,8 +14,8 @@ teardown_file() { } @test "Run trivy on patched image" { - run trivy image --exit-code 1 --vuln-type os --ignore-unfixed -f json -o nginx.1.21.6-patched.json 'docker.io/library/nginx:1.21.6-patched' + run trivy image --exit-code 1 --vuln-type os --ignore-unfixed -f json -o opa.0.46.0-patched.json --input patched.tar [ "$status" -eq 0 ] - vulns=$(jq 'if .Results then [.Results[] | select(.Class=="os-pkgs" and .Vulnerabilities!=null) | .Vulnerabilities[]] | length else 0 end' nginx.1.21.6-patched.json) + vulns=$(jq 'if .Results then [.Results[] | select(.Class=="os-pkgs" and .Vulnerabilities!=null) | .Vulnerabilities[]] | length else 0 end' opa.0.46.0-patched.json) assert_equal "$vulns" "0" }