Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support for buildx #37

Merged
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 44 additions & 12 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

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

this is fine for now, but we'll need to add a trivy ignore list like copa in the future as there's no way to avoid false positives

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created issue to add that: #38


- name: Get latest copa version
run: |
Expand All @@ -37,19 +40,48 @@ 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'

docker -c "$CONTEXT" save -o patched.tar openpolicyagent/opa:0.46.0-patched
Copy link
Member

@sozercan sozercan Feb 16, 2024

Choose a reason for hiding this comment

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

why are we saving to a tarball? because trivy can't scan it? if so, can you add a note

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes I did some research but I couldn't find a way to get trivy access without saving to tarball. Added a note for that!


bats --print-output-on-failure ./test/test.bats
2 changes: 1 addition & 1 deletion .github/workflows/patch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand Down
69 changes: 58 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,70 @@ 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]
> Copacetic released version's features do not align with Copa Action versions.
Copy link
Member

Choose a reason for hiding this comment

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

This is a bit confusing. Are we saying copa may have features that action might not?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I meant to say that if a feature was released in Copa version v0.6.0 (like patching local images), it wont necessarily be supported in Copa-Action version v0.6.0. In this case it will supported starting with Copa-Action v0.6.1. I changed the wording a bit to try to make it more clear.


## Outputs

| Name | Type | Description |
| --------------- | ------ | ------------------------------------ |
| `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: |
{
Copy link
Member

Choose a reason for hiding this comment

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

nit: indention is off in this json

"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
https://github.com/project-copacetic/copa-action/blob/941743581b0da5e581ca5a575f9316228c2f6c00/.github/workflows/patch.yaml#L1-L77
30 changes: 20 additions & 10 deletions action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,29 +25,39 @@ 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'
value: ${{ steps.copa-action.outputs.patched-image }}
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 }}
31 changes: 25 additions & 6 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -16,16 +18,33 @@ 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"
else
echo "Error patching image $image with copa"
exit 1
fi

10 changes: 3 additions & 7 deletions test/test.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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" ""
}

Expand All @@ -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"
}
Loading