Skip to content

Security

Security #850

Workflow file for this run

name: Security
on:
schedule:
- cron: "30 5 * * MON-FRI" # Every weekday at 05:30 UTC
workflow_dispatch:
push:
branches:
- main
paths:
- '**/.trivyignore'
jobs:
get-projects:
runs-on: ubuntu-latest
outputs:
projects: ${{ steps.get-projects.outputs.projects }}
steps:
- uses: actions/checkout@v4
- id: get-projects
run: echo "projects=$(find projects -mindepth 1 -maxdepth 1 -printf "%f\n" | jq --raw-input . | jq --slurp --compact-output .)" | tee -a "$GITHUB_OUTPUT"
trivy-scan:
runs-on: ubuntu-latest
needs:
- get-projects
strategy:
fail-fast: false
matrix:
project: ${{ fromJson(needs.get-projects.outputs.projects) }}
steps:
- uses: actions/checkout@v4
- name: Add new line to .trivyignore files
# to ensure files exist, and they can be merged correctly by Trivy
run: |
echo >> .trivyignore
echo >> projects/${{ matrix.project }}/.trivyignore
- name: Scan image
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # v0.29.0
with:
image-ref: 'ghcr.io/ministryofjustice/hmpps-probation-integration-services/${{ matrix.project }}:latest'
ignore-unfixed: true
severity: 'CRITICAL,HIGH'
exit-code: '0'
format: 'sarif'
output: 'trivy-results.sarif'
trivyignores: '.trivyignore,projects/${{ matrix.project }}/.trivyignore'
limit-severities-for-sarif: true
env:
TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2,ghcr.io/aquasecurity/trivy-db:2
TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
- name: Get Trivy results
uses: aquasecurity/trivy-action@18f2510ee396bbf400402947b394f2dd8c87dbb0 # v0.29.0
with:
image-ref: 'ghcr.io/ministryofjustice/hmpps-probation-integration-services/${{ matrix.project }}:latest'
ignore-unfixed: true
severity: 'CRITICAL,HIGH'
format: 'json'
output: 'trivy.json'
trivyignores: '.trivyignore,projects/${{ matrix.project }}/.trivyignore'
env:
TRIVY_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-db:2,ghcr.io/aquasecurity/trivy-db:2
TRIVY_JAVA_DB_REPOSITORY: public.ecr.aws/aquasecurity/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1
- name: Output results
run: |
jq -c '{"${{ matrix.project }}": .Results[].Vulnerabilities | select(. != null) | flatten}' trivy.json | tee results.json
env:
GITHUB_TOKEN: ${{ github.token }}
- uses: actions/upload-artifact@v4
with:
name: trivy-results-${{ matrix.project }}
path: results.json
trivy-merge:
runs-on: ubuntu-latest
needs:
- trivy-scan
steps:
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
pattern: trivy-results-*
path: results
- name: Merge results
run: |
find results -maxdepth 2 -name results.json -exec cat {} \; | jq -c --slurp 'map(to_entries | map(.key as $matrix_key | .value | map_values({($matrix_key): .}))) | flatten | reduce .[] as $item ({}; . * $item)' | tee results.json
- name: Create GitHub issues
run: |
jq -c 'to_entries | map((.value // empty) + {Projects: [.key]})
| flatten
| group_by(.VulnerabilityID)
| map(reduce .[] as $vuln (.[0] + {Locations:[]}; .Projects += $vuln.Projects | .Locations += [$vuln.PkgName + ":" + $vuln.InstalledVersion + " (" + $vuln.PkgPath + ")"]))
| map_values({Title: .VulnerabilityID, Body: ("### " + .Title + "\n" + .PrimaryURL + "\n>" + .Description + "\n#### Projects:\n* " + (.Projects | sort | unique | join("\n* ")) + "\n#### Locations:\n* `" + (.Locations | sort | unique | join("`\n* `")) + "`\n#### References:\n* " + (.References | sort | unique | join("\n* ")))})
| .[]' < results.json \
| while read -r vulnerability; do
title=$(jq -r '.Title' <<< "$vulnerability")
jq -r '.Body' <<< "$vulnerability" | tee "${title}-body.json"
existing=$(gh issue list --state open --label dependencies --label security --search "$title" --json 'number' --jq '.[].number' | head -n1)
if [ -n "$existing" ]; then
echo "Issue '$title' already exists, updating body..."
gh issue edit "$existing" --body-file "${title}-body.json"
else
gh issue create --title "$title" --body-file "${title}-body.json" --label 'dependencies,security'
fi
done
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Close GitHub issues
run: |
openissues="$(gh issue list --state open --label dependencies --label security | awk '{print $3}')"
scanresults="$(jq -r -c 'with_entries(select(.value != null)) | .[].VulnerabilityID' < results.json | sort -u)"
issuestoclose="$(comm -23 <(echo "$openissues" | sort -u) <(echo "$scanresults" | sort -u))" #print lines only present in first file
echo "openissues=$openissues"
echo "scanresults=$scanresults"
echo "issuestoclose=$issuestoclose"
for cve in $issuestoclose; do
echo "$cve is already resolved, removing matching issue..."
issuenumber=$(gh issue list --state open --label dependencies --label security --search "$cve" | awk '{print $1}')
echo "$issuenumber" | xargs -n1 gh issue close
done
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Fail job if any vulnerabilities are found
run: if [ "$(jq '. | with_entries(select(.value != null)) | length' < results.json)" != 0 ]; then exit 1; fi
veracode-scan:
runs-on: ubuntu-latest
needs:
- get-projects
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/setup-gradle
with:
cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }}
cache-read-only: true
- name: Build jars
run: ./gradlew jar
- name: Package jars
run: find . -name '*.jar' | zip -r package.zip -@
- name: Upload to Veracode
uses: veracode/[email protected]
with:
appname: hmpps-probation-integration-services
createprofile: false
deleteincompletescan: 2 # force delete any incomplete scans
filepath: package.zip
vid: ${{ secrets.CYBERSECURITY_VERACODE_API_ID }}
vkey: ${{ secrets.CYBERSECURITY_VERACODE_API_KEY }}