Skip to content
165 changes: 161 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ on:
publish:
description: 'Publish images to registries'
required: false
default: true
default: false
Comment thread
bluvulture marked this conversation as resolved.
type: boolean
Comment thread
bluvulture marked this conversation as resolved.
fail_on_severity:
description: 'Comma-separated list of severities that fail the build if post-patch CVEs remain (e.g. CRITICAL,HIGH). Valid values: CRITICAL, HIGH, MEDIUM, LOW. Use NONE to disable the gate entirely.'
required: false
default: 'CRITICAL,HIGH'
Comment thread
bluvulture marked this conversation as resolved.
type: string
push:
tags:
- 'v*.*'
Expand All @@ -16,6 +21,9 @@ on:

env:
IMAGE_NAME: pimcore/pimcore
COPA_VERSION: "0.14.1"
BUILDKIT_VERSION: "0.30.0"
TRIVY_DB_REPOSITORY: "ghcr.io/aquasecurity/trivy-db:2"

jobs:
build-php:
Expand Down Expand Up @@ -55,16 +63,66 @@ jobs:
- name: Login to GitHub Container Registry
run: echo ${{ secrets.IMAGES_REPO_TOKEN }} | docker login ghcr.io -u ${{ secrets.IMAGES_REPO_USERNAME }} --password-stdin

- name: Install Copa and Trivy
run: |
set -eux
# Install Trivy
sudo apt-get update
sudo apt-get install -y wget curl apt-transport-https gnupg lsb-release jq
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | gpg --dearmor | sudo tee /usr/share/keyrings/trivy.gpg > /dev/null
echo "deb [signed-by=/usr/share/keyrings/trivy.gpg] https://aquasecurity.github.io/trivy-repo/deb generic main" | sudo tee /etc/apt/sources.list.d/trivy.list
Comment thread
bluvulture marked this conversation as resolved.
sudo apt-get update
Comment thread
bluvulture marked this conversation as resolved.
sudo apt-get install -y trivy
Comment thread
bluvulture marked this conversation as resolved.

# Install Copa
COPA_ARCH="$(dpkg --print-architecture)"
curl -fsSL -o copa.tar.gz "https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copa_${COPA_VERSION}_linux_${COPA_ARCH}.tar.gz"
curl -fsSL -o copacetic_checksums.txt "https://github.com/project-copacetic/copacetic/releases/download/v${COPA_VERSION}/copacetic_checksums.txt"
# Verify checksum before extracting
EXPECTED_SHA=$(grep "copa_${COPA_VERSION}_linux_${COPA_ARCH}.tar.gz" copacetic_checksums.txt | awk '{print $1}')
ACTUAL_SHA=$(sha256sum copa.tar.gz | awk '{print $1}')
if [ "$EXPECTED_SHA" != "$ACTUAL_SHA" ]; then
echo "::error::Copa checksum mismatch! Expected ${EXPECTED_SHA}, got ${ACTUAL_SHA}"
exit 1
fi
tar -xzf copa.tar.gz copa
Comment thread
bluvulture marked this conversation as resolved.
sudo mv copa /usr/local/bin/copa
rm copa.tar.gz copacetic_checksums.txt

- name: Start buildkit daemon
run: |
docker run --detach --rm --privileged \
-p 127.0.0.1:8888:8888/tcp \
--name buildkitd \
--entrypoint buildkitd \
moby/buildkit:v${{ env.BUILDKIT_VERSION }} \
--addr tcp://0.0.0.0:8888
Comment on lines +94 to +99

# Wait for buildkit to be ready
for i in $(seq 1 60); do
if docker exec buildkitd buildctl --addr tcp://127.0.0.1:8888 debug workers >/dev/null 2>&1; then
echo "BuildKit is ready"
break
fi
if [ "$i" -eq 60 ]; then
echo "::error::BuildKit failed to start within 60 seconds"
exit 1
Comment thread
bluvulture marked this conversation as resolved.
fi
sleep 1
done
Comment thread
bluvulture marked this conversation as resolved.

- name: Configure and build images
id: vars
env:
VERSION_OVERRIDE: "${{ matrix.build.version-override }}"
ARCH_TAG: ${{ contains(matrix.runner, 'arm') && 'arm64' || 'amd64' }}
PUSH: ${{ github.event_name != 'workflow_dispatch' || inputs.publish }}
FAIL_ON_SEVERITY: ${{ inputs.fail_on_severity || 'CRITICAL,HIGH' }}
TRIVY_DB_REPOSITORY: ${{ env.TRIVY_DB_REPOSITORY }}
run: |
set -eux;
sudo apt-get update

mkdir -p trivy-reports
echo ${{ matrix.runner}}

if [[ "${{ matrix.build.tag }}" =~ ^v?1.[0-9x]+$ ]]; then
Expand Down Expand Up @@ -121,13 +179,94 @@ jobs:
TAGS="$TAGS --tag $GHCR_TAG_MAJOR"
fi

docker build --output "type=image,push=$PUSH" \
# Build and load image locally
docker build --load \
Comment thread
bluvulture marked this conversation as resolved.
--provenance=false \
--platform "linux/${ARCH_TAG}" \
--target="pimcore_php_$imageVariant" \
--build-arg PHP_VERSION="${PHP_VERSION}" \
--build-arg DEBIAN_VERSION="${DEBIAN_VERSION}" \
${TAGS} .
--tag "${IMAGE_NAME}:${TAG}" .

# Patch OS-level vulnerabilities with Copa
echo "Scanning and patching image ${IMAGE_NAME}:${TAG}"
trivy image --pkg-types os --ignore-unfixed --format json \
-o /tmp/trivy-report.json "${IMAGE_NAME}:${TAG}"

if [ -s /tmp/trivy-report.json ] && jq -e '.Results[]? | select(.Vulnerabilities != null and (.Vulnerabilities | length > 0))' /tmp/trivy-report.json > /dev/null 2>&1; then
copa patch -i "${IMAGE_NAME}:${TAG}" \
Comment thread
bluvulture marked this conversation as resolved.
-r /tmp/trivy-report.json \
-t "${TAG}-patched" \
-a tcp://127.0.0.1:8888

# Verify the patched image exists
if ! docker image inspect "${IMAGE_NAME}:${TAG}-patched" > /dev/null 2>&1; then
Comment thread
bluvulture marked this conversation as resolved.
Comment thread
bluvulture marked this conversation as resolved.
echo "::error::Patched image not found for ${IMAGE_NAME}:${TAG}"
exit 1
Comment thread
bluvulture marked this conversation as resolved.
fi

docker rmi "${IMAGE_NAME}:${TAG}"
docker tag "${IMAGE_NAME}:${TAG}-patched" "${IMAGE_NAME}:${TAG}"
docker rmi "${IMAGE_NAME}:${TAG}-patched"
echo "Successfully patched ${IMAGE_NAME}:${TAG}"
else
echo "No fixable OS vulnerabilities found, skipping Copa patch"
fi
rm -f /tmp/trivy-report.json

# Post-patch vulnerability gate
FAIL_SEVERITY="$FAIL_ON_SEVERITY"
if [ "$FAIL_SEVERITY" != "NONE" ]; then
echo "Running post-patch scan (fail on ${FAIL_SEVERITY}+)"

# Get the image hash for report naming
IMAGE_HASH=$(docker image inspect "${IMAGE_NAME}:${TAG}" --format '{{.Id}}' | sed 's/sha256://' | head -c 12)

trivy image --pkg-types os --ignore-unfixed \
--severity "$FAIL_SEVERITY" \
--format table \
-o /tmp/trivy-os-${TAG}.txt \
"${IMAGE_NAME}:${TAG}" || true

# Save report with image hash for artifact upload
trivy image --pkg-types os --ignore-unfixed \
--severity "$FAIL_SEVERITY" \
--format json \
-o "trivy-reports/${TAG}_${IMAGE_HASH}.json" \
"${IMAGE_NAME}:${TAG}" || true
cp /tmp/trivy-os-${TAG}.txt "trivy-reports/${TAG}_${IMAGE_HASH}.txt" 2>/dev/null || true

trivy image --pkg-types os --ignore-unfixed \
--exit-code 1 \
--severity "$FAIL_SEVERITY" \
"${IMAGE_NAME}:${TAG}"
Comment on lines +218 to +242

# Attach scan results to GitHub Actions job summary
{
echo "## Trivy Scan: ${IMAGE_NAME}:${TAG}"
echo ""
echo "### OS Vulnerabilities (${FAIL_SEVERITY}+)"
echo '```'
cat /tmp/trivy-os-${TAG}.txt 2>/dev/null || echo "No results"
echo '```'
echo ""
} >> "$GITHUB_STEP_SUMMARY"
Comment on lines +239 to +253
rm -f /tmp/trivy-os-${TAG}.txt
fi
Comment thread
bluvulture marked this conversation as resolved.

# Apply all tags to the (patched) image
CLEAN_TAGS_FOR_TAGGING="${TAGS//--tag /}"
read -r -a ALL_TAGS <<< "$CLEAN_TAGS_FOR_TAGGING"
for additional_tag in "${ALL_TAGS[@]}"; do
if [ "$additional_tag" != "${IMAGE_NAME}:${TAG}" ]; then
docker tag "${IMAGE_NAME}:${TAG}" "$additional_tag"
fi
done

# Push if publishing (parallel for speed)
if [[ "$PUSH" == "true" ]]; then
printf '%s\n' "${ALL_TAGS[@]}" | xargs -P 4 -I {} docker push "{}"
fi
Comment thread
bluvulture marked this conversation as resolved.

docker inspect ${IMAGE_NAME}:${TAG} || true;

Expand All @@ -145,8 +284,26 @@ jobs:
done
fi

# Clean up to save disk space
docker rmi "${IMAGE_NAME}:${TAG}" || true
for additional_tag in "${ALL_TAGS[@]}"; do
docker rmi "$additional_tag" 2>/dev/null || true
done

done

- name: Stop buildkit daemon
if: always()
run: docker stop buildkitd || true

- name: Upload trivy reports
if: always()
uses: actions/upload-artifact@v7
with:
name: trivy-reports_${{ matrix.runner }}_${{ matrix.build.tag }}_${{ matrix.build.php }}_${{ matrix.build.distro }}_${{ matrix.build.version-override }}_${{ matrix.build.latest-tag }}
path: trivy-reports/
if-no-files-found: ignore

- name: Upload aggregated tags
if: github.event_name != 'workflow_dispatch' || inputs.publish
uses: actions/upload-artifact@v7
Expand Down
Loading