From cf1e3b976c1582a5d9ce89f676a4cec23087c91f Mon Sep 17 00:00:00 2001 From: CLJ2006 Date: Fri, 3 Oct 2025 11:39:13 +0100 Subject: [PATCH] Added readme and sbom grype packages --- .github/README.md | 58 +++++++++++++++++++ .github/scripts/grype_json_to_csv.py | 28 ++++++++++ .github/scripts/sbom_json_to_csv.py | 74 +++++++++++++++++++++++++ .github/scripts/sbom_packages_to_csv.py | 28 ++++++++++ .github/workflows/sbom.yml | 52 +++++++++++++++-- 5 files changed, 235 insertions(+), 5 deletions(-) create mode 100644 .github/README.md create mode 100644 .github/scripts/grype_json_to_csv.py create mode 100644 .github/scripts/sbom_json_to_csv.py create mode 100644 .github/scripts/sbom_packages_to_csv.py diff --git a/.github/README.md b/.github/README.md new file mode 100644 index 0000000..123c756 --- /dev/null +++ b/.github/README.md @@ -0,0 +1,58 @@ +# SBOM & Vulnerability Scanning Automation + +This repository uses GitHub Actions to automatically generate a Software Bill of Materials (SBOM), scan for vulnerabilities, and produce package inventory reports. + +All reports are named with the repository name for easy identification. + +## Features + +SBOM Generation: Uses Syft to generate an SPDX JSON SBOM. +SBOM Merging: Merges SBOMs for multiple tools if needed. +SBOM to CSV: Converts SBOM JSON to a CSV report. +Vulnerability Scanning: Uses Grype to scan the SBOM for vulnerabilities and outputs a CSV report. +Package Inventory: Extracts a simple package list (name, type, version) as a CSV. +Artifacts: All reports are uploaded as workflow artifacts with the repository name in the filename. + +## Workflow Overview + +The main workflow is defined in .github/workflows/sbom.yml + +## Scripts + +scripts/create-sbom.sh +Generates an SBOM for the repo and for specified tools, merging them as needed. +scripts/update-sbom.py +Merges additional SBOMs into the main SBOM. +.github/scripts/sbom_json_to_csv.py +Converts the SBOM JSON to a detailed CSV report. +.github/scripts/grype_json_to_csv.py +Converts Grype’s vulnerability scan JSON output to a CSV report. +Output columns: REPO, NAME, INSTALLED, FIXED-IN, TYPE, VULNERABILITY, SEVERITY +.github/scripts/sbom_packages_to_csv.py +Extracts a simple package inventory from the SBOM. +Output columns: name, type, version + +## Example Reports + +Vulnerability Report +grype-report-[RepoName].csv +REPO,NAME,INSTALLED,FIXED-IN,TYPE,VULNERABILITY,SEVERITY +my-repo,Flask,2.1.2,,library,CVE-2022-12345,High +... + +Package Inventory +sbom-packages-[RepoName].csv +name,type,version +Flask,library,2.1.2 +Jinja2,library,3.1.2 +... + +## Usage + +Push to main branch or run the workflow manually. +Download artifacts from the workflow run summary. + +## Customization + +Add more tools to scripts/create-sbom.sh as needed. +Modify scripts to adjust report formats or add more metadata. diff --git a/.github/scripts/grype_json_to_csv.py b/.github/scripts/grype_json_to_csv.py new file mode 100644 index 0000000..6225455 --- /dev/null +++ b/.github/scripts/grype_json_to_csv.py @@ -0,0 +1,28 @@ +import json +import csv +import sys + +input_file = sys.argv[1] if len(sys.argv) > 1 else "grype-report.json" +output_file = sys.argv[2] if len(sys.argv) > 2 else "grype-report.csv" + +with open(input_file, "r", encoding="utf-8") as f: + data = json.load(f) + +columns = ["NAME", "INSTALLED", "FIXED-IN", "TYPE", "VULNERABILITY", "SEVERITY"] + +with open(output_file, "w", newline="", encoding="utf-8") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=columns) + writer.writeheader() + for match in data.get("matches", []): + pkg = match.get("artifact", {}) + vuln = match.get("vulnerability", {}) + row = { + "NAME": pkg.get("name", ""), + "INSTALLED": pkg.get("version", ""), + "FIXED-IN": vuln.get("fix", {}).get("versions", [""])[0] if vuln.get("fix", {}).get("versions") else "", + "TYPE": pkg.get("type", ""), + "VULNERABILITY": vuln.get("id", ""), + "SEVERITY": vuln.get("severity", ""), + } + writer.writerow(row) +print(f"CSV export complete: {output_file}") diff --git a/.github/scripts/sbom_json_to_csv.py b/.github/scripts/sbom_json_to_csv.py new file mode 100644 index 0000000..a3d054d --- /dev/null +++ b/.github/scripts/sbom_json_to_csv.py @@ -0,0 +1,74 @@ +import json +import csv +import sys +from pathlib import Path +from tabulate import tabulate + +input_file = sys.argv[1] if len(sys.argv) > 1 else "sbom.json" +output_file = sys.argv[2] if len(sys.argv) > 2 else "sbom.csv" + +with open(input_file, "r", encoding="utf-8") as f: + sbom = json.load(f) + +packages = sbom.get("packages", []) + +columns = [ + "name", + "versionInfo", + "type", + "supplier", + "downloadLocation", + "licenseConcluded", + "licenseDeclared", + "externalRefs" +] + +def get_type(pkg): + spdxid = pkg.get("SPDXID", "") + if "-" in spdxid: + parts = spdxid.split("-") + if len(parts) > 2: + return parts[2] + refs = pkg.get("externalRefs", []) + for ref in refs: + if ref.get("referenceType") == "purl": + return ref.get("referenceLocator", "").split("/")[0] + return "" + +def get_external_refs(pkg): + refs = pkg.get("externalRefs", []) + return ";".join([ref.get("referenceLocator", "") for ref in refs]) + +with open(output_file, "w", newline="", encoding="utf-8") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=columns) + writer.writeheader() + for pkg in packages: + row = { + "name": pkg.get("name", ""), + "versionInfo": pkg.get("versionInfo", ""), + "type": get_type(pkg), + "supplier": pkg.get("supplier", ""), + "downloadLocation": pkg.get("downloadLocation", ""), + "licenseConcluded": pkg.get("licenseConcluded", ""), + "licenseDeclared": pkg.get("licenseDeclared", ""), + "externalRefs": get_external_refs(pkg) + } + writer.writerow(row) + +print(f"CSV export complete: {output_file}") + +with open("sbom_table.txt", "w", encoding="utf-8") as f: + table = [] + for pkg in packages: + row = [ + pkg.get("name", ""), + pkg.get("versionInfo", ""), + get_type(pkg), + pkg.get("supplier", ""), + pkg.get("downloadLocation", ""), + pkg.get("licenseConcluded", ""), + pkg.get("licenseDeclared", ""), + get_external_refs(pkg) + ] + table.append(row) + f.write(tabulate(table, columns, tablefmt="grid")) \ No newline at end of file diff --git a/.github/scripts/sbom_packages_to_csv.py b/.github/scripts/sbom_packages_to_csv.py new file mode 100644 index 0000000..a14e4ad --- /dev/null +++ b/.github/scripts/sbom_packages_to_csv.py @@ -0,0 +1,28 @@ +import json +import csv +import sys +import os + +input_file = sys.argv[1] if len(sys.argv) > 1 else "sbom.json" +repo_name = sys.argv[2] if len(sys.argv) > 2 else os.getenv("GITHUB_REPOSITORY", "unknown-repo").split("/")[-1] +output_file = f"sbom-packages-{repo_name}.csv" + +with open(input_file, "r", encoding="utf-8") as f: + sbom = json.load(f) + +packages = sbom.get("packages", []) + +columns = ["name", "type", "version"] + +with open(output_file, "w", newline="", encoding="utf-8") as csvfile: + writer = csv.DictWriter(csvfile, fieldnames=columns) + writer.writeheader() + for pkg in packages: + row = { + "name": pkg.get("name", ""), + "type": pkg.get("type", ""), + "version": pkg.get("versionInfo", "") + } + writer.writerow(row) + +print(f"Package list CSV generated: {output_file}") \ No newline at end of file diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 46a7915..7b57a5d 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -1,4 +1,4 @@ -name: SBOM Check +name: SBOM Vulnerability Scanning on: workflow_dispatch: @@ -56,13 +56,55 @@ jobs: chmod +x syft # Add to PATH for subsequent steps - echo "$(pwd)" >> $GITHUB_PATH + echo "$(pwd)" >> $GITHUB_PATH - name: Create SBOM run: bash scripts/create-sbom.sh terraform python tflint - - name: Upload SBOM as artifact + - name: Convert SBOM JSON to CSV + run: | + pip install --upgrade pip + pip install tabulate + REPO_NAME=$(basename $GITHUB_REPOSITORY) + python .github/scripts/sbom_json_to_csv.py sbom.json SBOM_${REPO_NAME}.csv + + - name: Upload SBOM CSV as artifact + uses: actions/upload-artifact@v4 + with: + name: sbom-csv + path: SBOM_${{ github.event.repository.name }}.csv + + - name: Install Grype + run: | + curl -sSfL https://raw.githubusercontent.com/anchore/grype/main/install.sh | sh -s -- -b /usr/local/bin + + - name: Scan SBOM for Vulnerabilities (JSON) + run: | + grype sbom:sbom.json -o json > grype-report.json + + + + - name: Convert Grype JSON to CSV + run: | + pip install --upgrade pip + REPO_NAME=$(basename $GITHUB_REPOSITORY) + python .github/scripts/grype_json_to_csv.py grype-report.json grype-report-${REPO_NAME}.csv + + + - name: Upload Vulnerability Report + uses: actions/upload-artifact@v4 + with: + name: grype-report + path: grype-report-${{ github.event.repository.name }}.csv + + - name: Generate Package Inventory CSV + run: | + pip install --upgrade pip + REPO_NAME=$(basename $GITHUB_REPOSITORY) + python .github/scripts/sbom_packages_to_csv.py sbom.json $REPO_NAME + + - name: Upload Package Inventory CSV uses: actions/upload-artifact@v4 with: - name: sbom - path: sbom.json \ No newline at end of file + name: sbom-packages + path: sbom-packages-${{ github.event.repository.name }}.csv \ No newline at end of file