172 lines
7.7 KiB
YAML
172 lines
7.7 KiB
YAML
name: 'SBOM + triage report'
|
|
description: >
|
|
Source SBOM to Dependency-Track, osv-scanner over the repo, and a report to
|
|
dtrack-panel (the images this repo builds + its osv-scanner.toml decisions)
|
|
so the panel's auto-triage can apply them to the CI project and the running
|
|
images.
|
|
author: 'janhouse'
|
|
|
|
# Usage (a repo's .gitea/workflows/sbom.yml):
|
|
# - uses: actions/checkout@v4
|
|
# - uses: https://git.janhouse.lv/janhouse/sbom-tools-action/scan@v2
|
|
# with:
|
|
# project: Janhouse-Web
|
|
# images: git.janhouse.lv/private-docker/janhouse-cms
|
|
# dtrack-url: ${{ secrets.DEPENDENCY_TRACK_URL }}
|
|
# dtrack-api-key: ${{ secrets.DEPENDENCY_TRACK_API_KEY }}
|
|
#
|
|
# osv-scanner.toml [[IgnoredVulns]] are NOT_AFFECTED decisions: dtrack-panel
|
|
# writes them to Dependency-Track for this repo's own dependencies (the CI
|
|
# project, and the app's dependency tree in the images). A reason may start
|
|
# with a CycloneDX justification: "code_not_reachable: we never parse XML".
|
|
# They don't expire unless ignoreUntil says so — re-check them when the code
|
|
# that uses the package changes (see the repo's AGENTS.md).
|
|
|
|
inputs:
|
|
project:
|
|
description: 'Dependency-Track project name for this repo (the CI project)'
|
|
required: true
|
|
project-version:
|
|
description: 'Dependency-Track project version'
|
|
default: 'latest'
|
|
images:
|
|
description: 'Images this repo builds, comma or newline separated, without tag (git.janhouse.lv/janhouse/app)'
|
|
default: ''
|
|
container-image:
|
|
description: 'Optional: also include this image''s OS packages in the CI SBOM (merged)'
|
|
default: ''
|
|
container-skip-files:
|
|
description: 'With container-image: trivy --skip-files glob for the app dir already covered by the lockfile scan'
|
|
default: '/app/**'
|
|
path:
|
|
description: 'Directory to scan'
|
|
default: '.'
|
|
dtrack-url:
|
|
description: 'Dependency-Track base URL (secrets.DEPENDENCY_TRACK_URL)'
|
|
required: true
|
|
dtrack-api-key:
|
|
description: 'Dependency-Track API key with BOM upload (secrets.DEPENDENCY_TRACK_API_KEY)'
|
|
required: true
|
|
panel-url:
|
|
description: 'dtrack-panel CI report URL (default: <dtrack-url>/panel-ci/report)'
|
|
default: ''
|
|
trivy-version:
|
|
# 0.70.0 is the newest Trivy emitting CycloneDX 1.6 (see dtrack-docker-scanner's Dockerfile)
|
|
default: 'v0.70.0'
|
|
cyclonedx-version:
|
|
default: 'v0.27.2'
|
|
osv-scanner-version:
|
|
default: 'v2.7.0'
|
|
trivy-timeout:
|
|
description: 'Trivy scan timeout (large images can need more than 20m)'
|
|
default: '20m'
|
|
|
|
runs:
|
|
using: composite
|
|
steps:
|
|
- name: Cache SBOM tools
|
|
id: cache
|
|
uses: actions/cache@v4
|
|
with:
|
|
path: ~/.local/sbom-tools
|
|
key: sbom-scan-${{ runner.os }}-trivy-${{ inputs.trivy-version }}-cdx-${{ inputs.cyclonedx-version }}-osv-${{ inputs.osv-scanner-version }}
|
|
|
|
- name: Install Trivy, CycloneDX CLI, osv-scanner
|
|
if: steps.cache.outputs.cache-hit != 'true'
|
|
shell: bash
|
|
env:
|
|
TRIVY_VERSION: ${{ inputs.trivy-version }}
|
|
CDX_VERSION: ${{ inputs.cyclonedx-version }}
|
|
OSV_VERSION: ${{ inputs.osv-scanner-version }}
|
|
run: |
|
|
set -euo pipefail
|
|
d=~/.local/sbom-tools; mkdir -p "$d"
|
|
# Bounded downloads: a stalled transfer hung a run for 10+ minutes.
|
|
get() { curl -sSfL --connect-timeout 20 --max-time 300 --retry 3 --retry-all-errors "$@"; }
|
|
get "https://raw.githubusercontent.com/aquasecurity/trivy/${TRIVY_VERSION}/contrib/install.sh" -o "$d/trivy-install.sh"
|
|
sh "$d/trivy-install.sh" -b "$d" "${TRIVY_VERSION}"; rm -f "$d/trivy-install.sh"
|
|
get "https://github.com/CycloneDX/cyclonedx-cli/releases/download/${CDX_VERSION}/cyclonedx-linux-x64" -o "$d/cyclonedx-cli"
|
|
get "https://github.com/google/osv-scanner/releases/download/${OSV_VERSION}/osv-scanner_linux_amd64" -o "$d/osv-scanner"
|
|
chmod +x "$d"/*
|
|
|
|
- name: Add tools to PATH
|
|
shell: bash
|
|
run: echo "$HOME/.local/sbom-tools" >> "$GITHUB_PATH"
|
|
|
|
- name: Source SBOM (lockfiles)
|
|
shell: bash
|
|
env:
|
|
SCAN_PATH: ${{ inputs.path }}
|
|
CONTAINER_IMAGE: ${{ inputs.container-image }}
|
|
CONTAINER_SKIP: ${{ inputs.container-skip-files }}
|
|
TRIVY_TIMEOUT: ${{ inputs.trivy-timeout }}
|
|
run: |
|
|
set -euo pipefail
|
|
trivy fs "$SCAN_PATH" --quiet --timeout "$TRIVY_TIMEOUT" --format cyclonedx --output source-sbom.json
|
|
if [ -n "$CONTAINER_IMAGE" ]; then
|
|
trivy image "$CONTAINER_IMAGE" --quiet --timeout "$TRIVY_TIMEOUT" --skip-files "$CONTAINER_SKIP" \
|
|
--format cyclonedx --output container-sbom.json
|
|
cyclonedx-cli merge --input-files container-sbom.json source-sbom.json \
|
|
--output-file sbom.json --output-format json
|
|
else
|
|
cp source-sbom.json sbom.json
|
|
fi
|
|
echo "SBOM: $(jq '.components | length' sbom.json) components"
|
|
|
|
- name: Upload SBOM to Dependency-Track
|
|
shell: bash
|
|
env:
|
|
DTRACK_URL: ${{ inputs.dtrack-url }}
|
|
DTRACK_API_KEY: ${{ inputs.dtrack-api-key }}
|
|
PROJECT: ${{ inputs.project }}
|
|
PROJECT_VERSION: ${{ inputs.project-version }}
|
|
run: |
|
|
set -euo pipefail
|
|
tag=$(git describe --exact-match --tags 2>/dev/null || git rev-parse --short HEAD)
|
|
jq -n --arg n "$PROJECT" --arg v "$PROJECT_VERSION" --arg t "$tag" --rawfile bom <(base64 -w0 sbom.json) \
|
|
'{projectName: $n, projectVersion: $v, autoCreate: true, isLatestProjectVersion: true,
|
|
projectTags: [{name: $t}], bom: $bom}' > payload.json
|
|
curl -fsS -X PUT "${DTRACK_URL%/}/api/v1/bom" \
|
|
-H "X-Api-Key: ${DTRACK_API_KEY}" -H "Content-Type: application/json" \
|
|
--data-binary @payload.json
|
|
echo; echo "Uploaded ${PROJECT} @ ${PROJECT_VERSION} (${tag})"
|
|
|
|
- name: osv-scanner (informational)
|
|
shell: bash
|
|
continue-on-error: true
|
|
env:
|
|
SCAN_PATH: ${{ inputs.path }}
|
|
run: |
|
|
# Exit 1 = vulnerabilities found; this step only reports. Go call
|
|
# analysis runs when the Go toolchain is on the runner.
|
|
osv-scanner scan source -r "$SCAN_PATH" --format table || true
|
|
|
|
- name: Report to dtrack-panel (images + osv-scanner.toml)
|
|
shell: bash
|
|
continue-on-error: true
|
|
env:
|
|
DTRACK_URL: ${{ inputs.dtrack-url }}
|
|
DTRACK_API_KEY: ${{ inputs.dtrack-api-key }}
|
|
PANEL_URL: ${{ inputs.panel-url }}
|
|
PROJECT: ${{ inputs.project }}
|
|
IMAGES: ${{ inputs.images }}
|
|
SCAN_PATH: ${{ inputs.path }}
|
|
REPO: ${{ github.repository }}
|
|
COMMIT: ${{ github.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
url=${PANEL_URL:-${DTRACK_URL%/}/panel-ci/report}
|
|
images=$(printf '%s' "$IMAGES" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | { grep -v '^$' || true; } | jq -R . | jq -s .) # no images is fine
|
|
tomls='[]'
|
|
while IFS= read -r f; do
|
|
tomls=$(jq --arg p "$f" --rawfile c "$f" '. + [{path: $p, content: $c}]' <<<"$tomls")
|
|
done < <(find "$SCAN_PATH" -name osv-scanner.toml -not -path '*/node_modules/*' -not -path '*/.git/*' | head -20)
|
|
jq -n --arg repo "$REPO" --arg commit "$COMMIT" --arg project "$PROJECT" \
|
|
--argjson images "$images" --argjson tomls "$tomls" \
|
|
'{repo: $repo, commit: $commit, project: $project, images: $images, osvScannerToml: $tomls}' > report.json
|
|
echo "Reporting $(jq '.images | length' report.json) image(s), $(jq '.osvScannerToml | length' report.json) osv-scanner.toml file(s) to ${url}"
|
|
curl -fsS -X POST "$url" -H "X-Api-Key: ${DTRACK_API_KEY}" -H "Content-Type: application/json" \
|
|
--data-binary @report.json -o response.json
|
|
echo "Applied on the panel's next sync. Still open (not triaged):"
|
|
jq -r '.open[] | " \(.project): \(.n) \(.severity)"' response.json || true
|