From 63ae1f44c9b91640c2d0c3576702d3f41cca0e85 Mon Sep 17 00:00:00 2001 From: saptarshimandal1 Date: Tue, 30 Sep 2025 16:31:39 +0100 Subject: [PATCH 1/2] Setup Pythonv6, checksum verification & Shallow merge --- .github/workflows/sbom.yml | 63 ++++++++++++-------------------------- 1 file changed, 19 insertions(+), 44 deletions(-) diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 46a7915..12ce044 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -1,68 +1,43 @@ +# .github/workflows/sbom.yml name: SBOM Check on: workflow_dispatch: inputs: - environment: + run: description: "Run SBOM check" required: true - type: choice - options: - - yes - - no - -env: - SYFT_VERSION: "1.27.1" - TF_VERSION: "1.12.2" + type: boolean + default: true jobs: - deploy: - name: Software Bill of Materials + sbom: + if: ${{ inputs.run }} runs-on: ubuntu-latest permissions: actions: read - contents: write + contents: write # needed if you want SBOMs uploaded to Releases steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v5 - name: Setup Python 3.13 - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.13" - name: Setup Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd + with: + terraform_version: "1.12.2" - - uses: terraform-linters/setup-tflint@ae78205cfffec9e8d93fd2b3115c7e9d3166d4b6 - name: Setup TFLint - - - name: Set architecture variable - id: os-arch - run: | - case "${{ runner.arch }}" in - X64) ARCH="amd64" ;; - ARM64) ARCH="arm64" ;; - esac - echo "arch=${ARCH}" >> $GITHUB_OUTPUT - - - name: Download and setup Syft - run: | - DOWNLOAD_URL="https://github.com/anchore/syft/releases/download/v${{ env.SYFT_VERSION }}/syft_${{ env.SYFT_VERSION }}_linux_${{ steps.os-arch.outputs.arch }}.tar.gz" - echo "Downloading: ${DOWNLOAD_URL}" - - curl -L -o syft.tar.gz "${DOWNLOAD_URL}" - tar -xzf syft.tar.gz - chmod +x syft - - # Add to PATH for subsequent steps - echo "$(pwd)" >> $GITHUB_PATH - - - name: Create SBOM - run: bash scripts/create-sbom.sh terraform python tflint + - name: Setup TFLint + uses: terraform-linters/setup-tflint@ae78205cfffec9e8d93fd2b3115c7e9d3166d4b6 - - name: Upload SBOM as artifact - uses: actions/upload-artifact@v4 + # Base SBOM for repo (SPDX JSON). Also uploaded as a workflow artifact. + - name: Generate SBOM (repo) + uses: anchore/sbom-action@v0 with: - name: sbom - path: sbom.json \ No newline at end of file + path: . + format: spdx-json + output-file: sbom.spdx.json \ No newline at end of file From b58d698e9e82dfc84be9e369f7c8632635aed53a Mon Sep 17 00:00:00 2001 From: saptarshimandal1 Date: Thu, 2 Oct 2025 16:32:15 +0100 Subject: [PATCH 2/2] Added new SBOM config for generation all 3 reports --- .github/README.md | 58 +++++++++++++ .github/scripts/grype_json_to_csv.py | 28 +++++++ .github/scripts/sbom_json_to_csv.py | 78 ++++++++++++++++++ .github/scripts/sbom_packages_to_csv.py | 28 +++++++ .github/workflows/sbom.yml | 105 +++++++++++++++++++----- scripts/update-sbom.py | 2 +- 6 files changed, 279 insertions(+), 20 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..b11dfea --- /dev/null +++ b/.github/scripts/sbom_json_to_csv.py @@ -0,0 +1,78 @@ +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")) diff --git a/.github/scripts/sbom_packages_to_csv.py b/.github/scripts/sbom_packages_to_csv.py new file mode 100644 index 0000000..a7df2f4 --- /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}") diff --git a/.github/workflows/sbom.yml b/.github/workflows/sbom.yml index 12ce044..7b57a5d 100644 --- a/.github/workflows/sbom.yml +++ b/.github/workflows/sbom.yml @@ -1,43 +1,110 @@ -# .github/workflows/sbom.yml -name: SBOM Check +name: SBOM Vulnerability Scanning on: workflow_dispatch: inputs: - run: + environment: description: "Run SBOM check" required: true - type: boolean - default: true + type: choice + options: + - yes + - no + +env: + SYFT_VERSION: "1.27.1" + TF_VERSION: "1.12.2" jobs: - sbom: - if: ${{ inputs.run }} + deploy: + name: Software Bill of Materials runs-on: ubuntu-latest permissions: actions: read - contents: write # needed if you want SBOMs uploaded to Releases + contents: write steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v5 - name: Setup Python 3.13 - uses: actions/setup-python@v6 + uses: actions/setup-python@v5 with: python-version: "3.13" - name: Setup Terraform uses: hashicorp/setup-terraform@b9cd54a3c349d3f38e8881555d616ced269862dd + + - uses: terraform-linters/setup-tflint@ae78205cfffec9e8d93fd2b3115c7e9d3166d4b6 + name: Setup TFLint + + - name: Set architecture variable + id: os-arch + run: | + case "${{ runner.arch }}" in + X64) ARCH="amd64" ;; + ARM64) ARCH="arm64" ;; + esac + echo "arch=${ARCH}" >> $GITHUB_OUTPUT + + - name: Download and setup Syft + run: | + DOWNLOAD_URL="https://github.com/anchore/syft/releases/download/v${{ env.SYFT_VERSION }}/syft_${{ env.SYFT_VERSION }}_linux_${{ steps.os-arch.outputs.arch }}.tar.gz" + echo "Downloading: ${DOWNLOAD_URL}" + + curl -L -o syft.tar.gz "${DOWNLOAD_URL}" + tar -xzf syft.tar.gz + chmod +x syft + + # Add to PATH for subsequent steps + echo "$(pwd)" >> $GITHUB_PATH + + - name: Create SBOM + run: bash scripts/create-sbom.sh terraform python tflint + + - 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: - terraform_version: "1.12.2" + name: grype-report + path: grype-report-${{ github.event.repository.name }}.csv - - name: Setup TFLint - uses: terraform-linters/setup-tflint@ae78205cfffec9e8d93fd2b3115c7e9d3166d4b6 + - 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 - # Base SBOM for repo (SPDX JSON). Also uploaded as a workflow artifact. - - name: Generate SBOM (repo) - uses: anchore/sbom-action@v0 + - name: Upload Package Inventory CSV + uses: actions/upload-artifact@v4 with: - path: . - format: spdx-json - output-file: sbom.spdx.json \ No newline at end of file + name: sbom-packages + path: sbom-packages-${{ github.event.repository.name }}.csv \ No newline at end of file diff --git a/scripts/update-sbom.py b/scripts/update-sbom.py index 31042d1..d804485 100644 --- a/scripts/update-sbom.py +++ b/scripts/update-sbom.py @@ -18,4 +18,4 @@ def main() -> None: if __name__ == "__main__": - main() + main() \ No newline at end of file