Skip to content
Open
Changes from all 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
103 changes: 64 additions & 39 deletions .github/workflows/trivy_image_scan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ on:
description: 'Docker image tag to scan'
required: false
type: string
default: 'latest'
default: 'latest'
# registry mode, when pulling a pre built image from a remote registry
# is kept as an avatar of previous tests but most of the workflow uses build
scan-mode:
Expand Down Expand Up @@ -45,7 +45,7 @@ on:
description: 'Create a GitHub issue when vulnerabilities are found'
required: false
type: boolean
default: false
default: false
secrets:
composer-github-token:
description: 'GitHub token for Composer authentication'
Expand All @@ -68,7 +68,7 @@ on:
value: ${{ jobs.security-scan.outputs.medium }}
low-count:
description: 'Number of LOW vulnerabilities found'
value: ${{ jobs.security-scan.outputs.low }}
value: ${{ jobs.security-scan.outputs.low }}
scan-passed:
description: 'Whether scan completed without any vulnerability'
value: ${{ jobs.security-scan.outputs.scan-passed }}
Expand All @@ -95,7 +95,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v4
# We checkout the entire security-tooling repo because we'll need some files in it
# trivy.yaml conf file and the python script parsing findings
# trivy.yaml conf file and the python script parsing findings
- name: Checkout security-tooling repo
uses: actions/checkout@v4
with:
Expand All @@ -111,7 +111,7 @@ jobs:
uses: actions/setup-python@v5
with:
python-version: '3.11'

- name: Login to Docker registry
if: inputs.scan-mode == 'build'
uses: docker/login-action@v3
Expand Down Expand Up @@ -140,14 +140,17 @@ jobs:

- name: Pull Docker image from registry
if: inputs.scan-mode == 'registry'
run: docker pull ${{ inputs.image-name }}:${{ inputs.image-tag }}
env:
IMAGE_NAME: ${{ inputs.image-name }}
IMAGE_TAG: ${{ inputs.image-tag }}
run: docker pull "${IMAGE_NAME}:${IMAGE_TAG}"

- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@0.33.1
uses: aquasecurity/trivy-action@97e0b3872f55f89b95b2f65b3dbab56962816478 # 0.34.2
with:
trivy-config: 'security-tooling/.github/workflows/security/trivy-config.yaml'
image-ref: '${{ inputs.image-name }}:${{ inputs.image-tag }}'

- name: Generate Dockerfile suggestions
if: always()
run: |
Expand All @@ -173,12 +176,12 @@ jobs:
MEDIUM=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "MEDIUM")] | length' trivy-findings.json)
LOW=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "LOW")] | length' trivy-findings.json)
TOTAL=$((CRITICAL + HIGH + MEDIUM + LOW))

echo "critical=$CRITICAL" >> $GITHUB_OUTPUT
echo "high=$HIGH" >> $GITHUB_OUTPUT
echo "medium=$MEDIUM" >> $GITHUB_OUTPUT
echo "low=$LOW" >> $GITHUB_OUTPUT

if [ "$TOTAL" -eq 0 ]; then
echo "scan-passed=true" >> $GITHUB_OUTPUT
else
Expand All @@ -194,10 +197,13 @@ jobs:

- name: Add Trivy summary to run
if: always()
env:
IMAGE_NAME: ${{ inputs.image-name }}
IMAGE_TAG: ${{ inputs.image-tag }}
run: |
echo "## 🔍 Trivy Security Scan Results" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "**Image:** \`${{ inputs.image-name }}:${{ inputs.image-tag }}\`" >> $GITHUB_STEP_SUMMARY
echo "**Image:** \`${IMAGE_NAME}:${IMAGE_TAG}\`" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

if [ -f trivy-findings.json ]; then
Expand All @@ -206,10 +212,10 @@ jobs:
MEDIUM=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "MEDIUM")] | length' trivy-findings.json)
LOW=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity == "LOW")] | length' trivy-findings.json)
TOTAL=$(jq '[.Results[]?.Vulnerabilities[]?] | length' trivy-findings.json)

echo "**Total: $TOTAL** (🔴 CRITICAL: $CRITICAL, 🟠 HIGH: $HIGH, 🟡 MEDIUM: $MEDIUM, 🟢 LOW: $LOW)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY

if [ $TOTAL -gt 0 ]; then
jq -r '
def severity_order:
Expand All @@ -219,12 +225,12 @@ jobs:
elif . == "LOW" then 3
else 4
end;

.Results[]? |
if .Vulnerabilities and (.Vulnerabilities | length > 0) then
.Target as $target |
.Type as $type |

"\n" + $target + " (" + $type + ")",
"=" + ("=" * (($target + " (" + $type + ")") | length)),
"",
Expand All @@ -248,9 +254,12 @@ jobs:
- name: Sanitize image name for artifact
if: always()
id: sanitize
env:
IMAGE_NAME: ${{ inputs.image-name }}
IMAGE_TAG: ${{ inputs.image-tag }}
run: |
SANITIZED_NAME=$(echo "${{ inputs.image-name }}" | tr '/' '-')
echo "artifact-name=trivy-results-${SANITIZED_NAME}-${{ inputs.image-tag }}" >> $GITHUB_OUTPUT
SANITIZED_NAME=$(echo "${IMAGE_NAME}" | tr '/' '-')
echo "artifact-name=trivy-results-${SANITIZED_NAME}-${IMAGE_TAG}" >> $GITHUB_OUTPUT

- name: Upload Trivy results
uses: actions/upload-artifact@v4
Expand All @@ -263,24 +272,29 @@ jobs:
- name: Check vulnerabilities and determine actions
id: vuln-check
if: always()
env:
CRITICAL: ${{ steps.count-vulns.outputs.critical }}
HIGH: ${{ steps.count-vulns.outputs.high }}
MEDIUM: ${{ steps.count-vulns.outputs.medium }}
LOW: ${{ steps.count-vulns.outputs.low }}
SCAN_MODE: ${{ inputs.scan-mode }}
PUSH_IMAGE: ${{ inputs.push-image }}
PUSH_WITH_VULNS: ${{ inputs.push-with-vulns }}
CREATE_ISSUE_ON_VULNS: ${{ inputs.create-issue-on-vulns }}
run: |
CRITICAL=${{ steps.count-vulns.outputs.critical }}
HIGH=${{ steps.count-vulns.outputs.high }}
MEDIUM=${{ steps.count-vulns.outputs.medium }}
LOW=${{ steps.count-vulns.outputs.low }}
TOTAL=$((CRITICAL + HIGH + MEDIUM + LOW))

if [ "$TOTAL" -gt 0 ]; then
echo "has-vulns=true" >> $GITHUB_OUTPUT

# build mode AND attempting to push
#
if [ "${{ inputs.scan-mode }}" == "build" ] && [ "${{ inputs.push-image }}" == "true" ]; then
if [ "${{ inputs.push-with-vulns }}" == "false" ]; then
#
if [ "$SCAN_MODE" == "build" ] && [ "$PUSH_IMAGE" == "true" ]; then
if [ "$PUSH_WITH_VULNS" == "false" ]; then
echo "should-push=false" >> $GITHUB_OUTPUT
echo "should-create-issue=true" >> $GITHUB_OUTPUT
echo "⚠️ Found $TOTAL vulnerabilities ($CRITICAL CRITICAL, $HIGH HIGH, $MEDIUM MEDIUM, $LOW LOW). Creating issue for tracking." >> $GITHUB_STEP_SUMMARY
# push-with-vulns != false
# push-with-vulns != false
else
echo "should-push=true" >> $GITHUB_OUTPUT
echo "should-create-issue=false" >> $GITHUB_OUTPUT
Expand All @@ -290,7 +304,7 @@ jobs:

# registry mode OR build mode without attempting to push
echo "should-push=false" >> $GITHUB_OUTPUT
if [ "${{ inputs.create-issue-on-vulns }}" == "true" ]; then
if [ "$CREATE_ISSUE_ON_VULNS" == "true" ]; then
echo "should-create-issue=true" >> $GITHUB_OUTPUT
echo "⚠️ Found $TOTAL vulnerabilities ($CRITICAL CRITICAL, $HIGH HIGH, $MEDIUM MEDIUM, $LOW LOW). Creating issue for tracking." >> $GITHUB_STEP_SUMMARY
else
Expand Down Expand Up @@ -323,19 +337,30 @@ jobs:
- name: Create issue for vulnerabilities
if: steps.vuln-check.outputs.should-create-issue == 'true'
uses: actions/github-script@v7
env:
CRITICAL_COUNT: ${{ steps.count-vulns.outputs.critical }}
HIGH_COUNT: ${{ steps.count-vulns.outputs.high }}
MEDIUM_COUNT: ${{ steps.count-vulns.outputs.medium }}
LOW_COUNT: ${{ steps.count-vulns.outputs.low }}
IMAGE_NAME: ${{ inputs.image-name }}
IMAGE_TAG: ${{ inputs.image-tag }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
SCAN_MODE: ${{ inputs.scan-mode }}
PUSH_IMAGE: ${{ inputs.push-image }}
HAS_SUGGESTIONS: ${{ steps.read-suggestions.outputs.has-suggestions }}
with:
script: |
const fs = require('fs');
const critical = ${{ steps.count-vulns.outputs.critical }};
const high = ${{ steps.count-vulns.outputs.high }};
const medium = ${{ steps.count-vulns.outputs.medium }};
const low = ${{ steps.count-vulns.outputs.low }};
const imageName = '${{ inputs.image-name }}';
const imageTag = '${{ inputs.image-tag }}';
const runUrl = '${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}';
const scanMode = '${{ inputs.scan-mode }}';
const pushImage = '${{ inputs.push-image }}';
const hasSuggestions = '${{ steps.read-suggestions.outputs.has-suggestions }}' === 'true';
const critical = parseInt(process.env.CRITICAL_COUNT);
const high = parseInt(process.env.HIGH_COUNT);
const medium = parseInt(process.env.MEDIUM_COUNT);
const low = parseInt(process.env.LOW_COUNT);
const imageName = process.env.IMAGE_NAME;
const imageTag = process.env.IMAGE_TAG;
const runUrl = process.env.RUN_URL;
const scanMode = process.env.SCAN_MODE;
const pushImage = process.env.PUSH_IMAGE;
const hasSuggestions = process.env.HAS_SUGGESTIONS === 'true';

// Read suggestions file directly to preserve formatting
let suggestions = '';
Expand Down Expand Up @@ -458,11 +483,11 @@ jobs:
cache-to: ${{ inputs.cache-to || 'type=gha,mode=max' }}
secrets: |
composer_auth={"github-oauth": {"github.com": "${{ secrets.composer-github-token || secrets.GITHUB_TOKEN }}"}}

- name: Push skipped due to vulnerabilities
if: inputs.scan-mode == 'build' && inputs.push-image && steps.vuln-check.outputs.should-push == 'false'
run: |
echo "## 🚫 Image was **not** pushed" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "The Docker image was not pushed to the registry due to security vulnerabilities." >> $GITHUB_STEP_SUMMARY
echo "An issue has been created to track the vulnerabilities." >> $GITHUB_STEP_SUMMARY
echo "An issue has been created to track the vulnerabilities." >> $GITHUB_STEP_SUMMARY