From 3a6c7e53c96e5918e12bcd4e12fac5cffb072e5a Mon Sep 17 00:00:00 2001 From: jamesthompson26-nhs Date: Wed, 17 Dec 2025 14:49:20 +0000 Subject: [PATCH] CCM-13343_Trivy_Package_and_Library_Scans --- .github/actions/trivy-iac/action.yaml | 18 ++ .github/actions/trivy-package/action.yaml | 16 ++ .github/actions/trivy/action.yaml | 17 -- .github/workflows/stage-1-commit.yaml | 42 ++++- .tool-versions | 4 +- scripts/terraform/trivy-scan.sh | 194 ++++++++++++++++++++++ scripts/terraform/trivy.sh | 96 ----------- 7 files changed, 263 insertions(+), 124 deletions(-) create mode 100644 .github/actions/trivy-iac/action.yaml create mode 100644 .github/actions/trivy-package/action.yaml delete mode 100644 .github/actions/trivy/action.yaml create mode 100755 scripts/terraform/trivy-scan.sh delete mode 100755 scripts/terraform/trivy.sh diff --git a/.github/actions/trivy-iac/action.yaml b/.github/actions/trivy-iac/action.yaml new file mode 100644 index 00000000..583f9356 --- /dev/null +++ b/.github/actions/trivy-iac/action.yaml @@ -0,0 +1,18 @@ +name: "Trivy IaC Scan" +description: "Scan Terraform IaC using Trivy" +runs: + using: "composite" + steps: + - name: "Trivy Terraform IaC Scan" + shell: bash + run: | + components_exit_code=0 + modules_exit_code=0 + + ./scripts/terraform/trivy-scan.sh --mode iac ./infrastructure/terraform/components || components_exit_code=$? + ./scripts/terraform/trivy-scan.sh --mode iac ./infrastructure/terraform/modules || modules_exit_code=$? + + if [ $components_exit_code -ne 0 ] || [ $modules_exit_code -ne 0 ]; then + echo "Trivy misconfigurations detected." + exit 1 + fi diff --git a/.github/actions/trivy-package/action.yaml b/.github/actions/trivy-package/action.yaml new file mode 100644 index 00000000..d6ee4a3f --- /dev/null +++ b/.github/actions/trivy-package/action.yaml @@ -0,0 +1,16 @@ +name: "Trivy Package Scan" +description: "Scan project packages using Trivy" +runs: + using: "composite" + steps: + - name: "Trivy Package Scan" + shell: bash + run: | + exit_code=0 + + ./scripts/terraform/trivy-scan.sh --mode package . || exit_code=$? + + if [ $exit_code -ne 0 ]; then + echo "Trivy has detected package vulnerablilites. Please refer to https://nhsd-confluence.digital.nhs.uk/spaces/RIS/pages/1257636917/PLAT-KOP-012+-+Trivy+Pipeline+Vulnerability+Scanning+Exemption" + exit 1 + fi diff --git a/.github/actions/trivy/action.yaml b/.github/actions/trivy/action.yaml deleted file mode 100644 index be940ce5..00000000 --- a/.github/actions/trivy/action.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: "Trivy Scan" -runs: - using: "composite" - steps: - - name: "Trivy Terraform IAC Scan" - shell: bash - run: | - components_exit_code=0 - modules_exit_code=0 - - ./scripts/terraform/trivy.sh ./infrastructure/terraform/components || components_exit_code=$? - ./scripts/terraform/trivy.sh ./infrastructure/terraform/modules || modules_exit_code=$? - - if [ $components_exit_code -ne 0 ] || [ $modules_exit_code -ne 0 ]; then - echo "Trivy misconfigurations detected." - exit 1 - fi diff --git a/.github/workflows/stage-1-commit.yaml b/.github/workflows/stage-1-commit.yaml index 6063d464..749f9721 100644 --- a/.github/workflows/stage-1-commit.yaml +++ b/.github/workflows/stage-1-commit.yaml @@ -146,8 +146,11 @@ jobs: uses: actions/checkout@v5 - name: "Lint Terraform" uses: ./.github/actions/lint-terraform - trivy: - name: "Trivy Scan" + trivy-iac: + name: "Trivy IaC Scan" + permissions: + contents: read + packages: read runs-on: ubuntu-latest timeout-minutes: 10 needs: detect-terraform-changes @@ -156,18 +159,39 @@ jobs: NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: "Checkout code" - uses: actions/checkout@v5 - - name: Setup NodeJS - uses: actions/setup-node@v4 + uses: actions/checkout@v4 + - name: "Setup ASDF" + uses: asdf-vm/actions/setup@1902764435ca0dd2f3388eea723a4f92a4eb8302 + - name: "Repo setup" + uses: ./.github/actions/node-install with: node-version: ${{ inputs.nodejs_version }} - registry-url: 'https://npm.pkg.github.com' + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: "Perform Setup" + uses: ./.github/actions/setup + - name: "Trivy IaC Scan" + uses: ./.github/actions/trivy-iac + trivy-package: + name: "Trivy Package Scan" + permissions: + contents: read + packages: read + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: "Checkout code" + uses: actions/checkout@v4 - name: "Setup ASDF" - uses: asdf-vm/actions/setup@v4 + uses: asdf-vm/actions/setup@1902764435ca0dd2f3388eea723a4f92a4eb8302 + - name: "Repo setup" + uses: ./.github/actions/node-install + with: + node-version: ${{ inputs.nodejs_version }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: "Perform Setup" uses: ./.github/actions/setup - - name: "Trivy Scan" - uses: ./.github/actions/trivy + - name: "Trivy Package Scan" + uses: ./.github/actions/trivy-package count-lines-of-code: name: "Count lines of code" runs-on: ubuntu-latest diff --git a/.tool-versions b/.tool-versions index 0c674acf..d4b9b0a1 100644 --- a/.tool-versions +++ b/.tool-versions @@ -15,8 +15,8 @@ java openjdk-25.0.1 # The section below is reserved for Docker image versions. # TODO: Move this section - consider using a different file for the repository template dependencies. -# docker/ghcr.io/anchore/grype v0.69.1@sha256:d41fcb371d0af59f311e72123dff46900ebd6d0482391b5a830853ee4f9d1a76 # SEE: https://github.com/anchore/grype/pkgs/container/grype -# docker/ghcr.io/anchore/syft v0.92.0@sha256:63c60f0a21efb13e80aa1359ab243e49213b6cc2d7e0f8179da38e6913b997e0 # SEE: https://github.com/anchore/syft/pkgs/container/syft +# docker/ghcr.io/anchore/grype v0.104.3@sha256:d340f4f8b3b7e6e72a6c9c0152f25402ed8a2d7375dba1dfce4e53115242feb6 # SEE: https://github.com/anchore/grype/pkgs/container/grype +# docker/ghcr.io/anchore/syft v1.39.0@sha256:6f13bb010923c33fb197047c8f88888e77071bd32596b3f605d62a133e493ce4 # SEE: https://github.com/anchore/syft/pkgs/container/syft # docker/ghcr.io/gitleaks/gitleaks v8.18.0@sha256:fd2b5cab12b563d2cc538b14631764a1c25577780e3b7dba71657d58da45d9d9 # SEE: https://github.com/gitleaks/gitleaks/pkgs/container/gitleaks # docker/ghcr.io/igorshubovych/markdownlint-cli v0.37.0@sha256:fb3e79946fce78e1cde84d6798c6c2a55f2de11fc16606a40d49411e281d950d # SEE: https://github.com/igorshubovych/markdownlint-cli/pkgs/container/markdownlint-cli # docker/ghcr.io/make-ops-tools/gocloc latest@sha256:6888e62e9ae693c4ebcfed9f1d86c70fd083868acb8815fe44b561b9a73b5032 # SEE: https://github.com/make-ops-tools/gocloc/pkgs/container/gocloc diff --git a/scripts/terraform/trivy-scan.sh b/scripts/terraform/trivy-scan.sh new file mode 100755 index 00000000..15656233 --- /dev/null +++ b/scripts/terraform/trivy-scan.sh @@ -0,0 +1,194 @@ +#!/usr/bin/env bash + +# WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/NHSDigital/nhs-notify-repository-template). Raise a PR instead. + +set -euo pipefail + +function usage() { + cat <<'EOF' +Usage: ./scripts/terraform/trivy-scan.sh --mode [directory] + +Options: + --mode, -m Scan type to run. Accepts "iac" or "package" (required). + --help, -h Show this message. + [directory] Directory to scan. Defaults to the repository root. + +Environment variables: + FORCE_USE_DOCKER=true Force execution through Docker even if Trivy is installed locally. + VERBOSE=true Enable bash -x tracing. +EOF +} + +function main() { + cd "$(git rev-parse --show-toplevel)" + + local scan_mode="" + local dir_to_scan="." + + while [[ $# -gt 0 ]]; do + case "$1" in + --mode|-m) + if [[ $# -lt 2 ]]; then + echo "Error: --mode requires an argument." >&2 + usage + exit 1 + fi + scan_mode="$2" + shift 2 + ;; + --help|-h) + usage + exit 0 + ;; + --) + shift + break + ;; + -*) + echo "Unknown option: $1" >&2 + usage + exit 1 + ;; + *) + dir_to_scan="$1" + shift + ;; + esac + done + + if [[ $# -gt 0 ]]; then + dir_to_scan="$1" + fi + + if [[ -z "$scan_mode" ]]; then + echo "Error: --mode must be provided (iac|package)." >&2 + usage + exit 1 + fi + + case "$scan_mode" in + iac|package) + ;; + *) + echo "Error: unknown mode '$scan_mode'. Expected 'iac' or 'package'." >&2 + usage + exit 1 + ;; + esac + + if command -v trivy > /dev/null 2>&1 && ! is-arg-true "${FORCE_USE_DOCKER:-false}"; then + run-trivy-natively "$scan_mode" "$dir_to_scan" + else + run-trivy-in-docker "$scan_mode" "$dir_to_scan" + fi +} + +function run-trivy-natively() { + local scan_mode="$1" + local dir_to_scan="$2" + + echo "Trivy found locally, running natively" + echo "Running Trivy ($scan_mode) on directory: $dir_to_scan" + + if execute-trivy-command "$scan_mode" "$dir_to_scan"; then + check-trivy-status 0 + else + local status=$? + check-trivy-status "$status" + fi +} + +function run-trivy-in-docker() { + # shellcheck disable=SC1091 + source ./scripts/docker/docker.lib.sh + + local scan_mode="$1" + local dir_to_scan="$2" + + # shellcheck disable=SC2155 + local image=$(name=aquasec/trivy docker-get-image-version-and-pull) + + echo "Trivy not found locally, running in Docker Container" + echo "Running Trivy ($scan_mode) on directory: $dir_to_scan" + + if execute-trivy-in-docker "$image" "$scan_mode" "$dir_to_scan"; then + check-trivy-status 0 + else + local status=$? + check-trivy-status "$status" + fi +} + +function execute-trivy-command() { + local scan_mode="$1" + local dir_to_scan="$2" + + if [[ "$scan_mode" == "iac" ]]; then + trivy config \ + --config scripts/config/trivy.yaml \ + --tf-exclude-downloaded-modules \ + "$dir_to_scan" + else + trivy \ + --config scripts/config/trivy.yaml \ + fs "$dir_to_scan" \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --include-dev-deps + fi +} + +function execute-trivy-in-docker() { + local image="$1" + local scan_mode="$2" + local dir_to_scan="$3" + + if [[ "$scan_mode" == "iac" ]]; then + docker run --rm --platform linux/amd64 \ + --volume "$PWD":/workdir \ + --workdir /workdir \ + "$image" \ + config \ + --config scripts/config/trivy.yaml \ + --tf-exclude-downloaded-modules \ + "$dir_to_scan" + else + docker run --rm --platform linux/amd64 \ + --volume "$PWD":/workdir \ + --workdir /workdir \ + "$image" \ + --config scripts/config/trivy.yaml \ + fs "$dir_to_scan" \ + --scanners vuln \ + --severity HIGH,CRITICAL \ + --include-dev-deps + fi +} + +function check-trivy-status() { + local status="$1" + + if [[ "$status" -eq 0 ]]; then + echo "Trivy completed successfully." + return 0 + fi + + echo "Trivy found issues." + exit "$status" +} + +function is-arg-true() { + if [[ "$1" =~ ^(true|yes|y|on|1|TRUE|YES|Y|ON)$ ]]; then + return 0 + else + return 1 + fi +} + +# ============================================================================== + +is-arg-true "${VERBOSE:-false}" && set -x + +main "$@" + +exit 0 diff --git a/scripts/terraform/trivy.sh b/scripts/terraform/trivy.sh deleted file mode 100755 index 93caabd8..00000000 --- a/scripts/terraform/trivy.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/usr/bin/env bash - -# WARNING: Please DO NOT edit this file! It is maintained in the Repository Template (https://github.com/NHSDigital/nhs-notify-repository-template). Raise a PR instead. - -set -euo pipefail - -# TFSec command wrapper. It will run the command natively if TFSec is -# installed, otherwise it will run it in a Docker container. -# Run tfsec for security checks on Terraform code. -# -# Usage: -# $ ./trivy.sh [directory] -# ============================================================================== - -function main() { - - cd "$(git rev-parse --show-toplevel)" - - local dir_to_scan=${1:-.} - - if command -v trivy > /dev/null 2>&1 && ! is-arg-true "${FORCE_USE_DOCKER:-false}"; then - # shellcheck disable=SC2154 - run-trivy-natively "$dir_to_scan" - else - run-trivy-in-docker "$dir_to_scan" - fi -} - -# Run trivy on the specified directory. -# Arguments: -# $1 - Directory to scan -function run-trivy-natively() { - - local dir_to_scan="$1" - - echo "Trivy found locally, running natively" - - echo "Running Trivy on directory: $dir_to_scan" - trivy config \ - --config scripts/config/trivy.yaml \ - --tf-exclude-downloaded-modules \ - "${dir_to_scan}" - - check-trivy-status -} - -# Check the exit status of tfsec. -function check-trivy-status() { - - if [ $? -eq 0 ]; then - echo "Trivy completed successfully." - else - echo "Trivy found issues." - exit 1 - fi -} - -function run-trivy-in-docker() { - - # shellcheck disable=SC1091 - source ./scripts/docker/docker.lib.sh - local dir_to_scan="$1" - - # shellcheck disable=SC2155 - local image=$(name=aquasec/trivy docker-get-image-version-and-pull) - # shellcheck disable=SC2086 - echo "Trivy not found locally, running in Docker Container" - echo "Running Trivy on directory: $dir_to_scan" - docker run --rm --platform linux/amd64 \ - --volume "$PWD":/workdir \ - --workdir /workdir \ - "$image" \ - config \ - --config scripts/config/trivy.yaml \ - --tf-exclude-downloaded-modules \ - "${dir_to_scan}" - check-trivy-status -} -# ============================================================================== - -function is-arg-true() { - - if [[ "$1" =~ ^(true|yes|y|on|1|TRUE|YES|Y|ON)$ ]]; then - return 0 - else - return 1 - fi -} - -# ============================================================================== - -is-arg-true "${VERBOSE:-false}" && set -x - -main "$@" - -exit 0