Skip to content
Open
Show file tree
Hide file tree
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
71 changes: 65 additions & 6 deletions .github/actions/scan-dependencies/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,52 +19,111 @@ inputs:
idp_aws_report_upload_bucket_endpoint:
description: "IDP AWS report upload endpoint to upload the report to"
required: false
skip_if_pr_has_label:
description: "Skip dependency scanning when the triggering PR has this label"
required: false
default: "skip-dependencies-check"
runs:
using: "composite"
steps:
- name: "Check if dependency scan should be skipped"
id: skip-check
shell: bash
run: |
SHOULD_SKIP="false"

if [[ -n "${{ inputs.skip_if_pr_has_label }}" ]] && [[ -f "${GITHUB_EVENT_PATH}" ]]; then
PR_LABELS=$(jq -r '.pull_request.labels[]?.name' "${GITHUB_EVENT_PATH}")
if echo "${PR_LABELS}" | grep -Fxq "${{ inputs.skip_if_pr_has_label }}"; then
SHOULD_SKIP="true"
fi
Comment on lines +35 to +39
fi

echo "should_skip=${SHOULD_SKIP}" >> "$GITHUB_OUTPUT"
- name: "Skip dependency scan"
if: steps.skip-check.outputs.should_skip == 'true'
shell: bash
run: |
echo "Dependency scan skipped because PR has label '${{ inputs.skip_if_pr_has_label }}'."
- name: "Generate SBOM"
if: steps.skip-check.outputs.should_skip != 'true'
shell: bash
run: |
ACTION_ROOT="$(cd "${GITHUB_ACTION_PATH}/../../.." && pwd)"
export TOOLING_ROOT="${ACTION_ROOT}"
export BUILD_DATETIME=${{ inputs.build_datetime }}
./scripts/reports/create-sbom-report.sh
"${ACTION_ROOT}/scripts/reports/create-sbom-report.sh"
- name: "Compress SBOM report"
if: steps.skip-check.outputs.should_skip != 'true'
shell: bash
run: zip sbom-repository-report.json.zip sbom-repository-report.json
- name: "Upload SBOM report as an artefact"
if: ${{ !env.ACT }}
if: ${{ !env.ACT && steps.skip-check.outputs.should_skip != 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: sbom-repository-report.json.zip
path: ./sbom-repository-report.json.zip
retention-days: 21
- name: "Scan vulnerabilities"
if: steps.skip-check.outputs.should_skip != 'true'
shell: bash
run: |
ACTION_ROOT="$(cd "${GITHUB_ACTION_PATH}/../../.." && pwd)"
export TOOLING_ROOT="${ACTION_ROOT}"
export BUILD_DATETIME=${{ inputs.build_datetime }}
./scripts/reports/scan-vulnerabilities.sh
"${ACTION_ROOT}/scripts/reports/scan-vulnerabilities.sh"
- name: "Generate vulnerabilities summary"
if: steps.skip-check.outputs.should_skip != 'true'
shell: bash
run: |
ACTION_ROOT="$(cd "${GITHUB_ACTION_PATH}/../../.." && pwd)"
"${ACTION_ROOT}/scripts/reports/parse-vulnerabilities.sh" vulnerabilities-repository-report.json | tee vulnerabilities-summary.md
if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
cat vulnerabilities-summary.md >> "$GITHUB_STEP_SUMMARY"
fi
- name: "Compress vulnerabilities report"
if: steps.skip-check.outputs.should_skip != 'true'
shell: bash
run: zip vulnerabilities-repository-report.json.zip vulnerabilities-repository-report.json
- name: "Upload vulnerabilities report as an artefact"
if: ${{ !env.ACT }}
if: ${{ !env.ACT && steps.skip-check.outputs.should_skip != 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vulnerabilities-repository-report.json.zip
path: ./vulnerabilities-repository-report.json.zip
retention-days: 21
- name: "Upload vulnerabilities summary as an artefact"
if: ${{ !env.ACT && steps.skip-check.outputs.should_skip != 'true' }}
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: vulnerabilities-summary.md
path: ./vulnerabilities-summary.md
retention-days: 21
- name: "Fail if Critical or High vulnerabilities found"
if: steps.skip-check.outputs.should_skip != 'true'
shell: bash
run: |
CRITICAL_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length' vulnerabilities-repository-report.json)
HIGH_COUNT=$(jq '[.matches[] | select(.vulnerability.severity == "High")] | length' vulnerabilities-repository-report.json)
Comment on lines +106 to +107
echo "Critical: $CRITICAL_COUNT, High: $HIGH_COUNT"
if [[ "$CRITICAL_COUNT" -gt 0 || "$HIGH_COUNT" -gt 0 ]]; then
echo "::error::Found $CRITICAL_COUNT Critical and $HIGH_COUNT High severity vulnerabilities"
exit 1
fi
- name: "Check prerequisites for sending the reports"
if: steps.skip-check.outputs.should_skip != 'true'
shell: bash
id: check
run: echo "secrets_exist=${{ inputs.idp_aws_report_upload_role_name != '' && inputs.idp_aws_report_upload_bucket_endpoint != '' }}" >> $GITHUB_OUTPUT
- name: "Authenticate to send the reports"
if: steps.check.outputs.secrets_exist == 'true'
if: steps.skip-check.outputs.should_skip != 'true' && steps.check.outputs.secrets_exist == 'true'
uses: aws-actions/configure-aws-credentials@acca2b1b2070338fb9fd1ca27ecee81d687e58e5 # v6.1.2
with:
role-to-assume: arn:aws:iam::${{ inputs.idp_aws_report_upload_account_id }}:role/${{ inputs.idp_aws_report_upload_role_name }}
aws-region: ${{ inputs.idp_aws_report_upload_region }}
- name: "Send the SBOM and vulnerabilities reports to the central location"
shell: bash
if: steps.check.outputs.secrets_exist == 'true'
if: steps.skip-check.outputs.should_skip != 'true' && steps.check.outputs.secrets_exist == 'true'
run: |
aws s3 cp \
./sbom-repository-report.json.zip \
Expand Down
30 changes: 15 additions & 15 deletions .github/workflows/stage-1-commit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -151,21 +151,21 @@ jobs:
uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1
- name: "Lint Terraform"
uses: ./.github/actions/lint-terraform
trivy:
name: "Trivy Scan"
runs-on: ubuntu-latest
timeout-minutes: 5
needs: detect-terraform-changes
if: needs.detect-terraform-changes.outputs.terraform_changed == 'true'
steps:
- name: "Checkout code"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: "Setup ASDF"
uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1
- name: "Perform Setup"
uses: ./.github/actions/setup
- name: "Trivy Scan"
uses: ./.github/actions/trivy
# trivy:
# name: "Trivy Scan"
# runs-on: ubuntu-latest
# timeout-minutes: 5
# needs: detect-terraform-changes
# if: needs.detect-terraform-changes.outputs.terraform_changed == 'true'
# steps:
# - name: "Checkout code"
# uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
# - name: "Setup ASDF"
# uses: asdf-vm/actions/setup@b7bcd026f18772e44fe1026d729e1611cc435d47 # v4.0.1
# - name: "Perform Setup"
# uses: ./.github/actions/setup
# - name: "Trivy Scan"
# uses: ./.github/actions/trivy
count-lines-of-code:
name: "Count lines of code"
runs-on: ubuntu-latest
Expand Down
11 changes: 8 additions & 3 deletions scripts/reports/create-sbom-report.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_TOOLING_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
TOOLING_ROOT="${TOOLING_ROOT:-${DEFAULT_TOOLING_ROOT}}"

# Script to generate SBOM (Software Bill of Materials) for the repository
# content and any artefact created by the CI/CD pipeline. This is a syft command
# wrapper. It will run syft natively if it is installed, otherwise it will run
Expand Down Expand Up @@ -39,22 +43,23 @@ function create-report() {
function run-syft-natively() {

syft packages dir:"$PWD" \
--config "$PWD/scripts/config/syft.yaml" \
--config "$TOOLING_ROOT/scripts/config/syft.yaml" \
--output spdx-json="$PWD/sbom-repository-report.tmp.json"
}

function run-syft-in-docker() {

# shellcheck disable=SC1091
source ./scripts/docker/docker.lib.sh
source "$TOOLING_ROOT/scripts/docker/docker.lib.sh"

# shellcheck disable=SC2155
local image=$(name=ghcr.io/anchore/syft docker-get-image-version-and-pull)
docker run --rm --platform linux/amd64 \
--volume "$PWD":/workdir \
--volume "$TOOLING_ROOT":/tooling \
"$image" \
packages dir:/workdir \
--config /workdir/scripts/config/syft.yaml \
--config /tooling/scripts/config/syft.yaml \
--output spdx-json=/workdir/sbom-repository-report.tmp.json
}

Expand Down
107 changes: 107 additions & 0 deletions scripts/reports/parse-vulnerabilities.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/bin/bash
#
Comment thread
timireland marked this conversation as resolved.
# WARNING: This file is managed via the repository template.
# Local changes may diverge from the template source of truth.
#
# Parse vulnerability report JSON and output a human-readable summary
# Usage: ./parse-vulnerabilities.sh <path-to-report.json>
#

set -euo pipefail

if [[ $# -lt 1 ]]; then
echo "Usage: $0 <vulnerability-report.json>"
exit 1
fi

REPORT_FILE="$1"

if [[ ! -f "$REPORT_FILE" ]]; then
echo "Error: File not found: $REPORT_FILE"
exit 1
fi

if ! command -v jq &> /dev/null; then
echo "Error: jq is required but not installed"
exit 1
fi

# Get counts by severity
count_unique_severity() {
local severity="$1"

jq -r --arg sev "$severity" '
[.matches[] | select(.vulnerability.severity == $sev) | {
id: .vulnerability.id,
package: .artifact.name,
version: .artifact.version
}]
| unique_by(.id + .package + .version)
| length
' "$REPORT_FILE"
}

echo "## Vulnerability Report Summary"
echo ""

CRITICAL_COUNT=$(count_unique_severity "Critical")
HIGH_COUNT=$(count_unique_severity "High")
MEDIUM_COUNT=$(count_unique_severity "Medium")
LOW_COUNT=$(count_unique_severity "Low")
TOTAL=$((CRITICAL_COUNT + HIGH_COUNT + MEDIUM_COUNT + LOW_COUNT))

echo "**Total: $TOTAL vulnerabilities** ($CRITICAL_COUNT Critical, $HIGH_COUNT High, $MEDIUM_COUNT Medium, $LOW_COUNT Low)"
echo ""

# Function to print vulnerabilities for a given severity
print_severity_section() {
local severity="$1"
local count="$2"

if [[ "$count" -eq 0 ]]; then
return
fi

echo "### $severity ($count)"
echo ""
echo "| Package | Language | Version | Fix | Description |"
echo "|---------|---------|---------|-----|-------------|"

jq -r --arg sev "$severity" '
[.matches[] | select(.vulnerability.severity == $sev) | {
id: .vulnerability.id,
severity: .vulnerability.severity,
package: .artifact.name,
language: .artifact.language,
version: .artifact.version,
fix: (.vulnerability.fix.versions[0] // "N/A"),
description: .vulnerability.description
}]
| unique_by(.id + .package + .version)
| sort_by(.package)
| .[]
| "| \(.package) | \(.language) | \(.version) | \(.fix) | \(.description[0:70])... |"
' "$REPORT_FILE"

echo ""
}

print_severity_section "Critical" "$CRITICAL_COUNT"
print_severity_section "High" "$HIGH_COUNT"
print_severity_section "Medium" "$MEDIUM_COUNT"
print_severity_section "Low" "$LOW_COUNT"

# Priority packages summary
echo "---"
echo ""
echo "### Priority Packages to Update"
echo ""

jq -r '
[.matches[] | select(.vulnerability.severity == "Critical" or .vulnerability.severity == "High") | .artifact.name]
| unique
| sort
| join(", ")
' "$REPORT_FILE" | fold -s -w 80

echo ""
11 changes: 8 additions & 3 deletions scripts/reports/scan-vulnerabilities.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DEFAULT_TOOLING_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
TOOLING_ROOT="${TOOLING_ROOT:-${DEFAULT_TOOLING_ROOT}}"

# Script to scan an SBOM file for CVEs (Common Vulnerabilities and Exposures).
# This is a grype command wrapper. It will run grype natively if it is
# installed, otherwise it will run it in a Docker container.
Expand Down Expand Up @@ -42,24 +46,25 @@ function run-grype-natively() {

grype \
sbom:"$PWD/sbom-repository-report.json" \
--config "$PWD/scripts/config/grype.yaml" \
--config "$TOOLING_ROOT/scripts/config/grype.yaml" \
--output json \
--file "$PWD/vulnerabilities-repository-report.tmp.json"
}

function run-grype-in-docker() {

# shellcheck disable=SC1091
source ./scripts/docker/docker.lib.sh
source "$TOOLING_ROOT/scripts/docker/docker.lib.sh"

# shellcheck disable=SC2155
local image=$(name=ghcr.io/anchore/grype docker-get-image-version-and-pull)
docker run --rm --platform linux/amd64 \
--volume "$PWD":/workdir \
--volume "$TOOLING_ROOT":/tooling \
--volume /tmp/grype/db:/.cache/grype/db \
"$image" \
sbom:/workdir/sbom-repository-report.json \
--config /workdir/scripts/config/grype.yaml \
--config /tooling/scripts/config/grype.yaml \
--output json \
--file /workdir/vulnerabilities-repository-report.tmp.json
}
Expand Down