-
Notifications
You must be signed in to change notification settings - Fork 0
Fix SBOM root component: replace synthetic closure name with OCI image metadata #3
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "max_added": 50, | ||
| "max_removed": 50, | ||
| "deny_licenses": [], | ||
| "require_licenses": true, | ||
| "deny_duplicates": true | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,38 +1,34 @@ | ||
| name: Generate and upload SBOMs | ||
| name: Generate SBOMs | ||
|
|
||
| # Runs after OCI images are built, or on direct pushes that touch image sources. | ||
| # | ||
| # Prerequisites (one-time setup): | ||
| # 1. Add SBOMIFY_TOKEN secret to the repository (Settings → Secrets → Actions). | ||
| # Generate the token at https://app.sbomify.com. | ||
| # 2. Create one sbomify component per image and replace the placeholder | ||
| # sbomify_component_id values in the matrix below with the real IDs. | ||
| # Generates and enriches CycloneDX SBOMs for all Nix-built OCI images. | ||
| # Enriched SBOMs are uploaded as artifacts for downstream workflows | ||
| # (quality gate on PRs, upload to sbomify on merge). | ||
|
|
||
| on: | ||
| workflow_run: | ||
| workflows: ["Build and publish OCI images"] | ||
| types: [completed] | ||
| pull_request: | ||
| branches: [main] | ||
| paths: | ||
| - images/** | ||
| - deployments/sbomify/** | ||
| - flake.nix | ||
| - flake.lock | ||
| - bin/patch-sbom-root | ||
| push: | ||
| branches: | ||
| - main | ||
| branches: [main] | ||
| paths: | ||
| - images/** | ||
| - deployments/sbomify/** | ||
| - flake.nix | ||
| - flake.lock | ||
| - bin/patch-sbom-root | ||
| workflow_dispatch: | ||
|
|
||
| permissions: | ||
| actions: write | ||
| contents: read | ||
|
|
||
| jobs: | ||
| generate-and-upload: | ||
| # Skip if triggered by a failed workflow_run | ||
| if: >- | ||
| github.event_name != 'workflow_run' || | ||
| github.event.workflow_run.conclusion == 'success' | ||
| generate: | ||
| runs-on: blacksmith-4vcpu-ubuntu-2404 | ||
| strategy: | ||
| fail-fast: false | ||
|
|
@@ -41,34 +37,42 @@ jobs: | |
| - name: postgres | ||
| sbom_package: postgres-sbom | ||
| nixpkg: postgresql_17 | ||
| license: PostgreSQL | ||
| sbomify_component_id: M8rixM6mMEPe | ||
| - name: redis | ||
| sbom_package: redis-sbom | ||
| nixpkg: redis | ||
| license: AGPL-3.0-only | ||
| sbomify_component_id: ABBCcw2YiYrG | ||
| - name: minio | ||
| sbom_package: minio-sbom | ||
| nixpkg: minio | ||
| license: AGPL-3.0-or-later | ||
| sbomify_component_id: PLACEHOLDER_MINIO | ||
| - name: minio-client | ||
| sbom_package: minio-client-sbom | ||
| nixpkg: minio-client | ||
| license: Apache-2.0 | ||
| sbomify_component_id: PLACEHOLDER_MINIO_CLIENT | ||
| - name: sbomify-app | ||
| sbom_package: sbomify-app-sbom | ||
| nixpkg: python313 | ||
| license: Apache-2.0 | ||
| sbomify_component_id: VP42I4XQgpDE | ||
| - name: sbomify-keycloak | ||
| sbom_package: sbomify-keycloak-sbom | ||
| nixpkg: keycloak | ||
| license: Apache-2.0 | ||
| sbomify_component_id: N4agQD8pvej8 | ||
| - name: sbomify-caddy-dev | ||
| sbom_package: sbomify-caddy-dev-sbom | ||
| nixpkg: caddy | ||
| license: Apache-2.0 | ||
| sbomify_component_id: zYDq6NtrOBuo | ||
| - name: sbomify-minio-init | ||
| sbom_package: sbomify-minio-init-sbom | ||
| nixpkg: minio-client | ||
| license: Apache-2.0 | ||
| sbomify_component_id: PLACEHOLDER_SBOMIFY_MINIO_INIT | ||
|
|
||
| steps: | ||
|
|
@@ -90,7 +94,6 @@ jobs: | |
| id: version | ||
| run: | | ||
| if [[ "${{ matrix.image.name }}" == sbomify-* ]]; then | ||
| # sbomify images use the pinned sbomify source version | ||
| VERSION=$(grep 'github:sbomify/sbomify/' flake.nix | grep -oP '/v\K[0-9.]+') | ||
| else | ||
| VERSION=$(nix eval --raw nixpkgs#${{ matrix.image.nixpkg }}.version) | ||
|
|
@@ -102,7 +105,19 @@ jobs: | |
| nix build .#${{ matrix.image.sbom_package }} | ||
| cp -L result ${{ matrix.image.name }}.cdx.json | ||
|
|
||
| - name: Generate and Upload SBOM to sbomify | ||
| - name: Patch root component metadata | ||
| run: | | ||
| VERSION="${{ steps.version.outputs.upstream }}" | ||
| SBOM="${{ matrix.image.name }}.cdx.json" | ||
|
|
||
| bin/patch-sbom-root \ | ||
| --name "wellmaintained/packages/${{ matrix.image.name }}-image" \ | ||
| --version "$VERSION" \ | ||
| --purl "pkg:docker/wellmaintained/packages/${{ matrix.image.name }}@${VERSION}" \ | ||
| --license "${{ matrix.image.license }}" \ | ||
| < "$SBOM" > "${SBOM}.tmp" && mv "${SBOM}.tmp" "$SBOM" | ||
|
Comment on lines
+113
to
+118
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same argument here - can we it get the data we need for this patch from the nix expression used to describe the image? |
||
|
|
||
| - name: Enrich SBOM | ||
| if: ${{ !startsWith(matrix.image.sbomify_component_id, 'PLACEHOLDER') }} | ||
| uses: sbomify/sbomify-action@master | ||
| continue-on-error: true | ||
|
|
@@ -115,12 +130,17 @@ jobs: | |
| SBOM_FORMAT: cyclonedx | ||
| AUGMENT: true | ||
| ENRICH: true | ||
| UPLOAD: true | ||
| UPLOAD: false | ||
| OUTPUT_FILE: ${{ matrix.image.name }}.enriched.cdx.json | ||
|
|
||
| - name: Upload enriched SBOM as artifact | ||
| - name: Fall back to raw SBOM if enrichment skipped | ||
| run: | | ||
| if [ ! -f "${{ matrix.image.name }}.enriched.cdx.json" ]; then | ||
| cp "${{ matrix.image.name }}.cdx.json" "${{ matrix.image.name }}.enriched.cdx.json" | ||
| fi | ||
|
|
||
| - name: Upload enriched SBOM | ||
| uses: actions/upload-artifact@v6 | ||
| with: | ||
| name: sbom-${{ matrix.image.name }} | ||
| path: ${{ matrix.image.name }}.enriched.cdx.json | ||
| if-no-files-found: warn | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,174 @@ | ||
| name: SBOM quality gate | ||
|
|
||
| # Scores the PR's SBOMs and compares against the baseline from main. | ||
| # Blocks merging if any image's quality score regresses. | ||
| # | ||
| # Requires the "Generate SBOMs" workflow to have run first on this PR | ||
| # (it produces the enriched SBOM artifacts we score here). | ||
|
|
||
| on: | ||
| workflow_run: | ||
| workflows: ["Generate SBOMs"] | ||
| types: [completed] | ||
|
|
||
| permissions: | ||
| actions: read | ||
| contents: read | ||
| pull-requests: write | ||
|
|
||
| jobs: | ||
| score: | ||
| if: >- | ||
| github.event.workflow_run.event == 'pull_request' && | ||
| github.event.workflow_run.conclusion == 'success' | ||
| runs-on: blacksmith-4vcpu-ubuntu-2404 | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a cheaper blacksmith image type we can use for these jobs where we aren't doing compilation or image building |
||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| image: | ||
| - name: postgres | ||
| - name: redis | ||
| - name: minio | ||
| - name: minio-client | ||
| - name: sbomify-app | ||
| - name: sbomify-keycloak | ||
| - name: sbomify-caddy-dev | ||
| - name: sbomify-minio-init | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v5 | ||
|
|
||
| - name: Install sbomqs | ||
| run: | | ||
| curl -sSfL https://github.com/interlynk-io/sbomqs/releases/latest/download/sbomqs-linux-amd64 \ | ||
| -o /usr/local/bin/sbomqs | ||
| chmod +x /usr/local/bin/sbomqs | ||
|
|
||
| - name: Install sbomlyze | ||
| run: | | ||
| curl -sSfL https://raw.githubusercontent.com/rezmoss/sbomlyze/main/install.sh | sh | ||
| mv ./sbomlyze /usr/local/bin/sbomlyze | ||
|
Comment on lines
+42
to
+51
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we rather make custom nix packages for these so we can pin to specific versions |
||
|
|
||
| - name: Download PR SBOM | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: sbom-${{ matrix.image.name }} | ||
| path: current/ | ||
| run-id: ${{ github.event.workflow_run.id }} | ||
| github-token: ${{ github.token }} | ||
|
|
||
| - name: Score current SBOM | ||
| run: | | ||
| mkdir -p results | ||
| SBOM=$(ls current/*.cdx.json | head -1) | ||
| bin/sbom-score --image "${{ matrix.image.name }}" "$SBOM" \ | ||
| > results/score-${{ matrix.image.name }}.json | ||
| env: | ||
| PATH: /usr/local/bin:$PATH | ||
|
|
||
| - name: Fetch baseline SBOM | ||
| id: baseline | ||
| continue-on-error: true | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| # Find the latest successful Generate SBOMs run on main | ||
| RUN_ID=$(gh run list \ | ||
| --workflow="Generate SBOMs" \ | ||
| --branch=main \ | ||
| --status=success \ | ||
| --limit=1 \ | ||
| --json databaseId \ | ||
| --jq '.[0].databaseId') | ||
|
|
||
| if [ -z "$RUN_ID" ] || [ "$RUN_ID" = "null" ]; then | ||
| echo "has_baseline=false" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
|
|
||
| gh run download "$RUN_ID" \ | ||
| --name "sbom-${{ matrix.image.name }}" \ | ||
| --dir baseline/ || { | ||
| echo "has_baseline=false" >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| } | ||
|
|
||
| echo "has_baseline=true" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Score baseline SBOM | ||
| if: steps.baseline.outputs.has_baseline == 'true' | ||
| run: | | ||
| SBOM=$(ls baseline/*.cdx.json | head -1) | ||
| bin/sbom-score --image "${{ matrix.image.name }}" "$SBOM" \ | ||
| > results/baseline-${{ matrix.image.name }}.json | ||
|
|
||
| - name: Compare SBOMs | ||
| if: steps.baseline.outputs.has_baseline == 'true' | ||
| run: | | ||
| BASELINE=$(ls baseline/*.cdx.json | head -1) | ||
| CURRENT=$(ls current/*.cdx.json | head -1) | ||
| bin/sbom-compare \ | ||
| --baseline "$BASELINE" \ | ||
| --current "$CURRENT" \ | ||
| --image "${{ matrix.image.name }}" \ | ||
| --policy .github/sbom-policy.json \ | ||
| > results/compare-${{ matrix.image.name }}.json | ||
|
|
||
| - name: Upload results | ||
| uses: actions/upload-artifact@v6 | ||
| with: | ||
| name: sbom-qg-${{ matrix.image.name }} | ||
| path: results/ | ||
|
|
||
| report: | ||
| needs: score | ||
| if: always() && needs.score.result != 'skipped' | ||
| runs-on: blacksmith-4vcpu-ubuntu-2404 | ||
| permissions: | ||
| pull-requests: write | ||
| actions: read | ||
|
|
||
| steps: | ||
| - name: Checkout | ||
| uses: actions/checkout@v5 | ||
|
|
||
| - name: Download all results | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| pattern: sbom-qg-* | ||
| merge-multiple: true | ||
| path: results/ | ||
|
|
||
| - name: Generate report | ||
| id: report | ||
| run: | | ||
| bin/sbom-report --scores-dir results/ > pr-comment.md 2>report-stderr.txt || { | ||
| cat report-stderr.txt >&2 | ||
| echo "failed=true" >> "$GITHUB_OUTPUT" | ||
| } | ||
|
|
||
| - name: Resolve PR number | ||
| id: pr | ||
| env: | ||
| GH_TOKEN: ${{ github.token }} | ||
| run: | | ||
| # The workflow_run event doesn't directly give us the PR number. | ||
| # Look it up from the head branch. | ||
| HEAD_BRANCH="${{ github.event.workflow_run.head_branch }}" | ||
| PR_NUMBER=$(gh pr list --head "$HEAD_BRANCH" --json number --jq '.[0].number') | ||
| echo "number=${PR_NUMBER}" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Post PR comment | ||
| if: steps.pr.outputs.number | ||
| uses: marocchino/sticky-pull-request-comment@v2 | ||
| with: | ||
| header: sbom-quality-gate | ||
| number: ${{ steps.pr.outputs.number }} | ||
| path: pr-comment.md | ||
|
|
||
| - name: Fail on regression | ||
| if: steps.report.outputs.failed == 'true' | ||
| run: | | ||
| echo "SBOM quality regression detected. See PR comment for details." | ||
| exit 1 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| name: Upload SBOMs to sbomify | ||
|
|
||
| # Uploads enriched SBOMs to sbomify after they are generated on main. | ||
| # Only runs when the Generate SBOMs workflow completes successfully on main. | ||
|
|
||
| on: | ||
| workflow_run: | ||
| workflows: ["Generate SBOMs"] | ||
| types: [completed] | ||
| branches: [main] | ||
|
|
||
| permissions: | ||
| actions: read | ||
| contents: read | ||
|
|
||
| jobs: | ||
| upload: | ||
| if: github.event.workflow_run.conclusion == 'success' | ||
| runs-on: blacksmith-4vcpu-ubuntu-2404 | ||
| strategy: | ||
| fail-fast: false | ||
| matrix: | ||
| image: | ||
| - name: postgres | ||
| sbomify_component_id: M8rixM6mMEPe | ||
| - name: redis | ||
| sbomify_component_id: ABBCcw2YiYrG | ||
| - name: minio | ||
| sbomify_component_id: PLACEHOLDER_MINIO | ||
| - name: minio-client | ||
| sbomify_component_id: PLACEHOLDER_MINIO_CLIENT | ||
| - name: sbomify-app | ||
| sbomify_component_id: VP42I4XQgpDE | ||
| - name: sbomify-keycloak | ||
| sbomify_component_id: N4agQD8pvej8 | ||
| - name: sbomify-caddy-dev | ||
| sbomify_component_id: zYDq6NtrOBuo | ||
| - name: sbomify-minio-init | ||
| sbomify_component_id: PLACEHOLDER_SBOMIFY_MINIO_INIT | ||
|
|
||
| steps: | ||
| - name: Download enriched SBOM | ||
| if: ${{ !startsWith(matrix.image.sbomify_component_id, 'PLACEHOLDER') }} | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: sbom-${{ matrix.image.name }} | ||
| path: sbom/ | ||
| run-id: ${{ github.event.workflow_run.id }} | ||
| github-token: ${{ github.token }} | ||
|
|
||
| - name: Upload to sbomify | ||
| if: ${{ !startsWith(matrix.image.sbomify_component_id, 'PLACEHOLDER') }} | ||
| uses: sbomify/sbomify-action@master | ||
| env: | ||
| TOKEN: ${{ secrets.SBOMIFY_TOKEN }} | ||
| COMPONENT_ID: ${{ matrix.image.sbomify_component_id }} | ||
| SBOM_FILE: sbom/${{ matrix.image.name }}.enriched.cdx.json | ||
| SBOM_FORMAT: cyclonedx | ||
| UPLOAD: true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we rather get the license data from the nix package / derivation that we're making for the image?
Ideally by lining the image license to the license of its primary package; in the same way the we do for version