Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add trivy and grype image scans #73

Open
wants to merge 26 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
da85294
chore: add trivy and grype image scans
gibaros Feb 28, 2025
eeeea89
chore: update trivy scan image name
gibaros Feb 28, 2025
f882291
chore: update trivy image name conf
gibaros Feb 28, 2025
30a606c
chore: configure trivy-scan to use image-ref
gibaros Feb 28, 2025
8b6077c
chore: add trivy and grype scan in build-local
gibaros Feb 28, 2025
c5f3bfa
chore: test grype and trivy on all branches
gibaros Feb 28, 2025
98934fe
chore: run grype and trivy on all branches
gibaros Feb 28, 2025
916d578
chore: update grype output format
gibaros Feb 28, 2025
e36f053
chore: fix grype output format
gibaros Feb 28, 2025
770bb26
chore: set only table format for grype scan output
gibaros Feb 28, 2025
f73d6a4
chore: don't use grype orb, install grom github
gibaros Feb 28, 2025
dbc02a5
chore: update grype isntall
gibaros Mar 3, 2025
0492b08
chore: add grype ignore cross-spawn
gibaros Mar 4, 2025
f81701c
chore: update grype-scan-orb jobs
gibaros Mar 4, 2025
391a60d
chore: comment grype-scan-orb
gibaros Mar 4, 2025
dae000e
chore: update grype-scan confg
gibaros Mar 4, 2025
f4d2140
chore: align grype-scan correctly
gibaros Mar 4, 2025
4f7dcaa
chore: configure grype ignore
gibaros Mar 4, 2025
61ff740
chore: add grype ignore by cve and GHSA ID
gibaros Mar 4, 2025
5fc4223
chore: add trivy ignore conf
gibaros Mar 4, 2025
166d476
chore: config trivy ignore and exit 0
gibaros Mar 4, 2025
978ea60
chore: update trivy job
gibaros Mar 4, 2025
0c96c7b
chore: use node 18.20.6 and ubuntu 2404:2024.11.1
gibaros Mar 4, 2025
fc6f699
chore: update grype config and add ignore reason
gibaros Mar 4, 2025
cc62d07
chore: update ignore config
gibaros Mar 4, 2025
d09e399
chore: update grype config
gibaros Mar 4, 2025
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
258 changes: 185 additions & 73 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ orbs:
slack: circleci/[email protected] # Ref: https://github.com/mojaloop/ci-config/tree/master/slack-templates
pr-tools: mojaloop/[email protected] # Ref: https://github.com/mojaloop/ci-config/
gh: circleci/[email protected]
anchore: anchore/[email protected]
grype: anchore/[email protected]
# trivy: cci-labs/[email protected]

##
# defaults
Expand Down Expand Up @@ -148,7 +149,7 @@ executors:
working_directory: *WORKING_DIR
shell: "/bin/bash -leo pipefail"
machine:
image: ubuntu-2204:2023.04.2 # Ref: https://circleci.com/developer/machine/image/ubuntu-2204
image: ubuntu-2404:2024.11.1 # Ref: https://circleci.com/developer/machine/image/ubuntu-2404

##
# Jobs
Expand Down Expand Up @@ -360,81 +361,179 @@ jobs:
path: /tmp/license-scanner/results
destination: licenses

image-scan:
executor: anchore/anchore_engine
shell: /bin/sh -leo pipefail ## Ref: https://circleci.com/docs/env-vars/#alpine-linux
# grype-scan-orb:
# executor: grype/default
# environment:
# <<: *defaults_environment
# steps:
# - attach_workspace:
# at: /tmp
# - run:
# name: Load the pre-built docker image from workspace
# command: docker load -i /tmp/docker-image.tar
# - grype/scan-image:
# image-name: ${DOCKER_ORG:-mojaloop}/$CIRCLE_PROJECT_REPONAME:local
# output-format: json
# output-file: "grype-vulnerabilities-orb.json"
# # You can uncomment to fail on severity levels if desired
# # fail-on-severity: critical
# - run:
# name: Check critical vulnerabilities count
# command: |
# CRITICAL_COUNT=$(cat grype-vulnerabilities.json | jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length')
# echo "Critical vulnerabilities found: $CRITICAL_COUNT"
# - store_artifacts:
# path: grype-vulnerabilities.json
# destination: grype-scan-orb/scan-results-orb.json
# - store_artifacts:
# path: grype-vulnerabilities.txt
# destination: grype-scan-orb/scan-results-orb.txt

# grype-scan:
# executor: default-machine
# environment:
# <<: *defaults_environment
# steps:
# - attach_workspace:
# at: /tmp
# - run:
# name: Load the pre-built docker image from workspace
# command: docker load -i /tmp/docker-image.tar
# - run:
# name: Install Grype
# command: |
# curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin
# - run:
# name: Run Grype scan
# command: |
# IMAGE_NAME="${DOCKER_ORG:-mojaloop}/$CIRCLE_PROJECT_REPONAME:local"
# echo "Scanning image: $IMAGE_NAME"
# grype $IMAGE_NAME -o table > grype-results.txt
# grype $IMAGE_NAME -o json > grype-results.json
# cat grype-results.txt
# cat grype-results.json
# - run:
# name: Check critical vulnerabilities count
# command: |
# CRITICAL_COUNT=$(cat grype-results.json | jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length')
# echo "Critical vulnerabilities found: $CRITICAL_COUNT"
# - store_artifacts:
# path: grype-results.json
# destination: grype-scan/scan-results.json
# - store_artifacts:
# path: grype-results.txt
# destination: grype-scan/scan-results.txt

grype-scan:
executor: default-machine
environment:
<<: *defaults_environment
BASH_ENV: /etc/profile ## Ref: https://circleci.com/docs/env-vars/#alpine-linux
ENV: ~/.profile
NVM_ARCH_UNOFFICIAL_OVERRIDE: x64-musl ## Ref: https://github.com/nvm-sh/nvm/issues/1102#issuecomment-550572252
working_directory: *WORKING_DIR
steps:
- setup_remote_docker
- checkout # Add this to get your repo code including the .grype.yaml config
- attach_workspace:
at: /tmp
- run:
name: Install docker dependencies for anchore
command: |
apk add --update py-pip docker python3-dev libffi-dev openssl-dev gcc libc-dev make jq npm curl bash
- run:
name: Install AWS CLI dependencies
command: *defaults_awsCliDependencies
- checkout
- run:
name: Setup Slack config
command: |
echo "export SLACK_PROJECT_NAME=${CIRCLE_PROJECT_REPONAME}" >> $BASH_ENV
echo "export SLACK_RELEASE_TYPE='GitHub Release'" >> $BASH_ENV
echo "export SLACK_RELEASE_TAG='${RELEASE_TAG} on ${CIRCLE_BRANCH} branch'" >> $BASH_ENV
echo "export SLACK_BUILD_ID=${CIRCLE_BUILD_NUM}" >> $BASH_ENV
echo "export SLACK_CI_URL=${CIRCLE_BUILD_URL}" >> $BASH_ENV
echo "export SLACK_CUSTOM_MSG='Anchore Image Scan failed for: \`${DOCKER_ORG}/${CIRCLE_PROJECT_REPONAME}:${CIRCLE_TAG}\`'" >> $BASH_ENV
- run:
<<: *defaults_configure_nvm
- run:
<<: *defaults_display_versions
- run:
name: Install general dependencies
command: *defaults_docker_Dependencies
- run:
name: Load the pre-built docker image from workspace
command: docker load -i /tmp/docker-image.tar
- run:
name: Download the mojaloop/ci-config repo
name: Install Grype
command: |
git clone https://github.com/mojaloop/ci-config /tmp/ci-config
# Generate the mojaloop anchore-policy
cd /tmp/ci-config/container-scanning && ./mojaloop-policy-generator.js /tmp/mojaloop-policy.json
curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sudo sh -s -- -b /usr/local/bin
- run:
name: Pull base image locally
name: Run Grype scan with custom config
command: |
echo "Pulling docker image: node:$NVMRC_VERSION-alpine"
docker pull node:$NVMRC_VERSION-alpine
## Analyze the base and derived image
## Note: It seems images are scanned in parallel, so preloading the base image result doesn't give us any real performance gain
- anchore/analyze_local_image:
# Force the older version, version 0.7.0 was just published, and is broken
anchore_version: v0.6.1
image_name: "docker.io/node:${NVMRC_VERSION}-alpine ${DOCKER_ORG:-mojaloop}/$CIRCLE_PROJECT_REPONAME:local"
policy_failure: false
timeout: '500'
# Note: if the generated policy is invalid, this will fallback to the default policy, which we don't want!
policy_bundle_file_path: /tmp/mojaloop-policy.json
- run:
name: Upload Anchore reports to s3
IMAGE_NAME="${DOCKER_ORG:-mojaloop}/$CIRCLE_PROJECT_REPONAME:local"
echo "Scanning image: $IMAGE_NAME"
# Use the config file in your repo
grype $IMAGE_NAME -c .grype.yaml -o table > grype-results.txt
grype $IMAGE_NAME -c .grype.yaml -o json > grype-results.json
cat grype-results.txt
cat grype-results.json
- run:
name: Check for critical, high and medium vulnerabilities
command: |
aws s3 cp anchore-reports ${AWS_S3_DIR_ANCHORE_REPORTS}/${CIRCLE_PROJECT_REPONAME}/ --recursive
aws s3 rm ${AWS_S3_DIR_ANCHORE_REPORTS}/latest/ --recursive --exclude "*" --include "${CIRCLE_PROJECT_REPONAME}*"
aws s3 cp anchore-reports ${AWS_S3_DIR_ANCHORE_REPORTS}/latest/ --recursive
- run:
name: Evaluate failures
command: /tmp/ci-config/container-scanning/anchore-result-diff.js anchore-reports/node_${NVMRC_VERSION}-alpine-policy.json anchore-reports/${CIRCLE_PROJECT_REPONAME}*-policy.json
# Count vulnerabilities in the filtered results
CRITICAL_COUNT=$(cat grype-results.json | jq '[.matches[] | select(.vulnerability.severity == "Critical")] | length')
HIGH_COUNT=$(cat grype-results.json | jq '[.matches[] | select(.vulnerability.severity == "High")] | length')
MEDIUM_COUNT=$(cat grype-results.json | jq '[.matches[] | select(.vulnerability.severity == "Medium")] | length')

echo "Critical vulnerabilities found: $CRITICAL_COUNT"
echo "High vulnerabilities found: $HIGH_COUNT"
echo "Medium vulnerabilities found: $MEDIUM_COUNT"

# List remaining critical, high and medium vulnerabilities for awareness
echo "Critical severity vulnerabilities:"
cat grype-results.json | jq -r '.matches[] | select(.vulnerability.severity == "Critical") | "- \(.artifact.name) \(.artifact.version): \(.vulnerability.id)"'

echo "High severity vulnerabilities:"
cat grype-results.json | jq -r '.matches[] | select(.vulnerability.severity == "High") | "- \(.artifact.name) \(.artifact.version): \(.vulnerability.id)"'

echo "Medium severity vulnerabilities:"
cat grype-results.json | jq -r '.matches[] | select(.vulnerability.severity == "Medium") | "- \(.artifact.name) \(.artifact.version): \(.vulnerability.id)"'

# Fail if any critical, high, or medium vulnerabilities are found
if [ "$CRITICAL_COUNT" -gt 0 ] || [ "$HIGH_COUNT" -gt 0 ] || [ "$MEDIUM_COUNT" -gt 0 ]; then
echo "Critical, High, or Medium vulnerabilities found. Failing the build."
exit 1
fi
- store_artifacts:
path: anchore-reports
- slack/notify:
event: fail
template: SLACK_TEMP_RELEASE_FAILURE
path: grype-results.json
destination: grype-scan/scan-results.json
- store_artifacts:
path: grype-results.txt
destination: grype-scan/scan-results.txt
- store_artifacts:
path: grype-results.json
destination: grype-scan/grype-results.json

# trivy-scan:
# executor: default-machine
# environment:
# <<: *defaults_environment
# steps:
# - checkout # Add this to get your repo code including the .trivyignore.yaml config
# - attach_workspace:
# at: /tmp
# - run:
# name: Load the pre-built docker image from workspace
# command: docker load -i /tmp/docker-image.tar
# - trivy/scan:
# image-ref: ${DOCKER_ORG:-mojaloop}/$CIRCLE_PROJECT_REPONAME:local
# # You can uncomment to fail on severity levels if desired
# # security-checks: vuln
# # severity: 'CRITICAL,HIGH'
# # exit-code: '1'
# exit-code: '0'
# format: 'table'
# output: 'trivy-results.txt'
# # ignore-policy: '.trivyignore.yaml'
# additional-args: '--ignorefile .trivyignore.yaml'
# - trivy/scan:
# image-ref: ${DOCKER_ORG:-mojaloop}/$CIRCLE_PROJECT_REPONAME:local
# format: 'json'
# output: 'trivy-results.json'
# #ignore-policy: '.trivyignore.yaml'
# additional-args: '--ignorefile .trivyignore.yaml'
# exit-code: '0'
# - run:
# name: Check critical vulnerabilities count
# command: |
# CRITICAL_COUNT=$(cat trivy-results.json | jq '[.Results[] | .Vulnerabilities // [] | .[] | select(.Severity == "CRITICAL")] | length')
# echo "Critical vulnerabilities found: $CRITICAL_COUNT"
#
# # Optional: Verify cross-spawn vulnerabilities are excluded
# CROSS_SPAWN_COUNT=$(cat trivy-results.json | jq '[.Results[] | .Vulnerabilities // [] | .[] | select(.PkgName == "cross-spawn")] | length')
# echo "Cross-spawn vulnerabilities found after filtering: $CROSS_SPAWN_COUNT"
#
# # List remaining high vulnerabilities for awareness
# echo "High severity vulnerabilities:"
# cat trivy-results.json | jq -r '.Results[] | .Vulnerabilities // [] | .[] | select(.Severity == "HIGH") | "- \(.PkgName) \(.InstalledVersion): \(.VulnerabilityID)"'
# - store_artifacts:
# path: trivy-results.json
# destination: trivy-scan/scan-results.json
# - store_artifacts:
# path: trivy-results.txt
# destination: trivy-scan/scan-results.txt

release:
executor: default-docker
Expand Down Expand Up @@ -541,7 +640,7 @@ jobs:
at: /tmp
- run:
name: Load the pre-built docker image from workspace
command: |
command: |
docker load -i /tmp/docker-image.tar
- run:
name: Login to Docker Hub
Expand Down Expand Up @@ -714,8 +813,7 @@ workflows:
tags:
only: /v[0-9]+(\.[0-9]+)*(\-snapshot(\.[0-9]+)?)?(\-hotfix(\.[0-9]+)?)?(\-perf(\.[0-9]+)?)?/
branches:
ignore:
- /.*/
only: /.*/
- license-scan:
context: org-global
requires:
Expand All @@ -726,16 +824,24 @@ workflows:
branches:
ignore:
- /.*/
- image-scan:
- grype-scan:
context: org-global
requires:
- build-local
filters:
tags:
only: /v[0-9]+(\.[0-9]+)*(\-snapshot(\.[0-9]+)?)?(\-hotfix(\.[0-9]+)?)?(\-perf(\.[0-9]+)?)?/
branches:
ignore:
- /.*/
only: /.*/
# - trivy-scan:
# context: org-global
# requires:
# - build-local
# filters:
# tags:
# only: /v[0-9]+(\.[0-9]+)*(\-snapshot(\.[0-9]+)?)?(\-hotfix(\.[0-9]+)?)?(\-perf(\.[0-9]+)?)?/
# branches:
# only: /.*/
# New commits to main release automatically
- release:
context: org-global
Expand All @@ -749,7 +855,9 @@ workflows:
- vulnerability-check
- audit-licenses
- license-scan
- image-scan
- grype-scan
# - trivy-scan
# - grype-scan-orb
filters:
branches:
only:
Expand Down Expand Up @@ -777,7 +885,9 @@ workflows:
- audit-licenses
# - test-integration
- license-scan
- image-scan
- grype-scan
# - trivy-scan
# - grype-scan-orb
filters:
tags:
only: /v[0-9]+(\.[0-9]+)*/
Expand All @@ -797,10 +907,12 @@ workflows:
- audit-licenses
# - test-integration
- license-scan
- image-scan
- grype-scan
# - trivy-scan
# - grype-scan-orb
filters:
tags:
only: /v[0-9]+(\.[0-9]+)*\-snapshot+((\.[0-9]+)?)/
branches:
ignore:
- /.*/
- /.*/
4 changes: 0 additions & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,3 @@ typings/
# https://devspace.sh/
devspace*
.devspace/**.*

# Add ignores
*IGNORE*
*ignore*
18 changes: 18 additions & 0 deletions .grype.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
ignore:
# Ignore cross-spawn vulnerabilities by CVE ID due to false positive
# as grype looks at package-lock.json where it shows versions with
# vulnerabilities, npm ls shows only 7.0.6 verion is used
- vulnerability: "GHSA-3xgq-45jj-v275"
package:
name: "cross-spawn"

# Set output format defaults
output:
- "table"
- "json"

# Modify your CircleCI job to check critical count
search:
scope: "squashed"
quiet: false
check-for-app-update: false
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
18.20.4
18.20.6
14 changes: 14 additions & 0 deletions .trivyignore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
ignorePkgs:
- pkg:npm/cross-spawn@*

# Alternatively, can specify exact CVE/GHSA IDs
ignoreVulns:
- CVE-2024-21538
- GHSA-3xgq-45jj-v275

# can also be more specific with both package and vulnerability
# ignoreRules:
# - id: CVE-2024-21538
# pkg: pkg:npm/cross-spawn@*
# until: 2025-12-31T23:59:59Z
# reason: "We've assessed this vulnerability and determined it's not exploitable in our context"
Loading