Skip to content

ECR vulnerability detection #656

ECR vulnerability detection

ECR vulnerability detection #656

name: ECR vulnerability detection
on:
schedule:
- cron: '0 9 * * *'
workflow_call:
inputs:
trigger_source:
type: string
required: false
default: 'schedule'
outputs:
scan_type:
description: "Type of scan (scheduled or workflow)"
value: ${{ inputs.trigger_source }}
jobs:
vulnerability-detection:
if: |
inputs.trigger_source == 'workflow' ||
(github.event_name == 'schedule')
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- image_tag: "8.2-alpine3.21"
- image_tag: "8.3-alpine3.21"
- image_tag: "8.4-alpine3.21"
- image_tag: "8.2-alpine3.22"
- image_tag: "8.3-alpine3.22"
- image_tag: "8.4-alpine3.22"
- image_tag: "8.2-alpine3.23"
- image_tag: "8.3-alpine3.23"
- image_tag: "8.4-alpine3.23"
- image_tag: "8.2-alpine3.24"
- image_tag: "8.3-alpine3.24"
- image_tag: "8.4-alpine3.24"
- image_tag: "8.5-alpine3.24"
steps:
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Pull the latest image from the DockerHub
run: docker pull "spryker/php:${{ matrix.image_tag }}"
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Login to AWS ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Ensure ECR repo exists (scanOnPush on)
run: |
aws ecr describe-repositories --repository-names "${{ secrets.AWS_ECR_REPO }}" >/dev/null 2>&1 || \
aws ecr create-repository --repository-name "${{ secrets.AWS_ECR_REPO }}"
- name: Tag & push to ECR
id: push
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
run: |
ECR_URI="${ECR_REGISTRY}/${{ secrets.AWS_ECR_REPO }}:${{ matrix.image_tag }}"
docker tag "spryker/php:${{ matrix.image_tag }}" "$ECR_URI"
docker push "$ECR_URI"
echo "ecr_uri=$ECR_URI" >> $GITHUB_ENV
- name: Wait for enhanced scan results
run: |
set -euo pipefail
REPO="${{ secrets.AWS_ECR_REPO }}"
TAG="${{ matrix.image_tag }}"
REG="${{ secrets.AWS_REGION }}"
for i in {1..20}; do
echo "Attempt $i of 20: Checking scan status..."
out=$(aws ecr describe-image-scan-findings \
--repository-name "$REPO" \
--image-id imageTag="$TAG" \
--region "$REG" 2>/dev/null || true)
status=$(echo "$out" | jq -r '.imageScanStatus.status // ""')
has_obj=$(echo "$out" | jq -r '.imageScanFindings != null')
echo "Status: ${status:-<none>} | has findings obj: $has_obj"
if [[ ("$status" == "COMPLETE" || "$status" == "ACTIVE") && "$has_obj" == "true" ]]; then
echo "Scan is ready to proceed!"
break
fi
if [[ "$i" == "20" ]]; then
echo "Timeout waiting for scan to become ready"
exit 1
fi
echo "Waiting 30 seconds before next attempt..."
sleep 30
done
- name: Scan Docker image (ECR)
id: docker-scan
uses: alexjurkiewicz/ecr-scan-image@v3.0.0
with:
repository: ${{ secrets.AWS_ECR_REPO }}
tag: ${{ matrix.image_tag }}
- name: Fix vulnerability counts
id: fixed-counts
run: |
CRITICAL=${{ steps.docker-scan.outputs.critical }}
HIGH=${{ steps.docker-scan.outputs.high }}
MEDIUM=${{ steps.docker-scan.outputs.medium }}
LOW=${{ steps.docker-scan.outputs.low }}
INFO=${{ steps.docker-scan.outputs.informational }}
SCAN_RESULT=$(aws ecr describe-image-scan-findings \
--repository-name "${{ secrets.AWS_ECR_REPO }}" \
--image-id imageTag="${{ matrix.image_tag }}" \
--output json 2>/dev/null) || SCAN_RESULT="{}"
if ! echo "$SCAN_RESULT" | jq empty 2>/dev/null; then
SCAN_RESULT="{}"
fi
UNDEFINED=$(echo "$SCAN_RESULT" | jq '
[
(.imageScanFindings.enhancedFindings // [])[] |
select(
.packageVulnerabilityDetails.vendorSeverity == null or
.packageVulnerabilityDetails.vendorSeverity == "" or
(.packageVulnerabilityDetails.vendorSeverity | ascii_downcase) == "undefined"
)
] | length
')
TOTAL=$(( ${CRITICAL:-0} + ${HIGH:-0} + ${MEDIUM:-0} + ${LOW:-0} + ${INFO:-0} + ${UNDEFINED:-0} ))
echo "total=$TOTAL" >> $GITHUB_OUTPUT
echo "undefined=$UNDEFINED" >> $GITHUB_OUTPUT
- name: Report
if: always()
run: |
echo "Scan type: ${{ inputs.trigger_source }}"
echo "${{ steps.fixed-counts.outputs.total }} total vulnerabilities."
echo "Critical: ${{ steps.docker-scan.outputs.critical }}, High: ${{ steps.docker-scan.outputs.high }}, Medium: ${{ steps.docker-scan.outputs.medium }}, Low: ${{ steps.docker-scan.outputs.low }}, Informational: ${{ steps.docker-scan.outputs.informational }}, Undefined: ${{ steps.fixed-counts.outputs.undefined }}"
echo "Details of findings:"
cat <<'JSON'
${{ steps.docker-scan.outputs.findingsDetails }}
JSON
- name: Delete image from ECR (optional cleanup)
if: always()
run: |
aws ecr batch-delete-image \
--repository-name "${{ secrets.AWS_ECR_REPO }}" \
--image-ids imageTag="${{ matrix.image_tag }}" || true
- name: Set Date and Time
id: set-date
if: ${{ always() && github.event_name == 'schedule' }}
run: echo "current_datetime=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT
- name: Set Color
id: set-color
if: ${{ always() && github.event_name == 'schedule' }}
shell: bash
run: |
if [ "${{ steps.fixed-counts.outputs.total }}" = "0" ]; then
echo "color=#008000" >> $GITHUB_OUTPUT
else
echo "color=#ff0000" >> $GITHUB_OUTPUT
fi
- name: Build link to scan step
id: scan-step-link
if: ${{ always() && github.event_name == 'schedule' }}
env:
GH_TOKEN: ${{ github.token }}
RUN_ID: ${{ github.run_id }}
TAG: ${{ matrix.image_tag }}
STEP_NAME: Scan Docker image (ECR)
run: |
set -euo pipefail
RES="$(curl -sS -H "Accept: application/vnd.github+json" \
-H "Authorization: Bearer $GH_TOKEN" \
"https://api.github.com/repos/${{ github.repository }}/actions/runs/$RUN_ID/jobs?per_page=100")"
JOB="$(jq -c --arg tag "$TAG" '
.jobs
| (map(select(.name | endswith("(" + $tag + ")"))) | .[0])
// .[0]
' <<<"$RES")"
JOB_URL="$(jq -r '.html_url' <<<"$JOB")"
STEP_NUM="$(jq -r --arg n "$STEP_NAME" '
.steps | map(select(.name == $n)) | .[0].number
' <<<"$JOB")"
if [ -n "$STEP_NUM" ] && [ "$STEP_NUM" != "null" ]; then
STEP_URL="${JOB_URL}#step:${STEP_NUM}:1"
else
STEP_URL="$JOB_URL"
fi
echo "job_url=$JOB_URL" >> "$GITHUB_OUTPUT"
echo "step_url=$STEP_URL" >> "$GITHUB_OUTPUT"
- name: Send GitHub Action trigger data to Slack
id: slack
if: ${{ always() && github.event_name == 'schedule' }}
uses: slackapi/slack-github-action@v1.24.0
with:
payload: |
{
"attachments": [
{
"color": "${{ steps.set-color.outputs.color }}",
"blocks": [
{
"type": "header",
"text": { "type": "plain_text", "text": "ECR scan: ${{ matrix.image_tag }}", "emoji": true }
},
{
"type": "context",
"elements": [
{ "type": "mrkdwn", "text": "*Scan time:* ${{ steps.set-date.outputs.current_datetime }}" },
{ "type": "mrkdwn", "text": "<${{ steps.scan-step-link.outputs.step_url }}|Open scan report>" }
]
},
{ "type": "divider" },
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Total*\n*${{ steps.fixed-counts.outputs.total }}*" },
{ "type": "mrkdwn", "text": "*Critical*\n${{ steps.docker-scan.outputs.critical }}" },
{ "type": "mrkdwn", "text": "*High*\n${{ steps.docker-scan.outputs.high }}" },
{ "type": "mrkdwn", "text": "*Medium*\n${{ steps.docker-scan.outputs.medium }}" },
{ "type": "mrkdwn", "text": "*Low*\n${{ steps.docker-scan.outputs.low }}" },
{ "type": "mrkdwn", "text": "*Info*\n${{ steps.docker-scan.outputs.informational }}" },
{ "type": "mrkdwn", "text": "*Undefined*\n${{ steps.fixed-counts.outputs.undefined }}" }
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "Open scan report" },
"url": "${{ steps.scan-step-link.outputs.step_url }}"
}
]
}
]
}
]
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK