From c954298a314854e1098d3e30116fc96db1823740 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Wed, 4 Mar 2026 14:05:27 +0000 Subject: [PATCH 1/7] Add 40 Docker Hub images and TEA dedup workflow - Add SBOM configs and workflows for top 40 Docker Hub official images (postgres, python, node, mysql, mongo, httpd, rabbitmq, traefik, mariadb, golang, alpine, ubuntu, debian, ruby, wordpress, php, sonarqube, haproxy, influxdb, nextcloud, tomcat, maven, eclipse-mosquitto, telegraf, bash, ghost, solr, kong, zookeeper, neo4j, gradle, mongo-express, eclipse-temurin, perl, cassandra, drupal, memcached, registry, redis, nginx/chainguard) - Add tea-sync.yml hourly workflow to detect docker/chainguard image changes and rebuild SBOMs via the existing sbom-builder - Update sbom-builder.yml to three-phase approach: build augmented SBOM locally, check TEA for existing hash, upload only if new - Fix docker-attestation.sh multi-arch manifest handling (was using --platform which skips the index-level attestation manifests) - Skip semver validation for docker/chainguard source types in common.sh - Add product_id to dependency-track configs - Rename sbomify/github-action to sbomify/sbomify-action Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sbom-alpine.yml | 37 ++++++++++ .github/workflows/sbom-bash.yml | 37 ++++++++++ .github/workflows/sbom-builder.yml | 67 +++++++++++++++-- .github/workflows/sbom-cassandra.yml | 37 ++++++++++ .github/workflows/sbom-debian.yml | 37 ++++++++++ .github/workflows/sbom-drupal.yml | 37 ++++++++++ .github/workflows/sbom-eclipse-mosquitto.yml | 37 ++++++++++ .github/workflows/sbom-eclipse-temurin.yml | 37 ++++++++++ .github/workflows/sbom-ghost.yml | 37 ++++++++++ .github/workflows/sbom-golang.yml | 37 ++++++++++ .github/workflows/sbom-gradle.yml | 37 ++++++++++ .github/workflows/sbom-haproxy.yml | 37 ++++++++++ .github/workflows/sbom-httpd.yml | 37 ++++++++++ .github/workflows/sbom-influxdb.yml | 37 ++++++++++ .github/workflows/sbom-kong.yml | 37 ++++++++++ .github/workflows/sbom-mariadb.yml | 37 ++++++++++ .github/workflows/sbom-maven.yml | 37 ++++++++++ .github/workflows/sbom-memcached.yml | 37 ++++++++++ .github/workflows/sbom-mongo-express.yml | 37 ++++++++++ .github/workflows/sbom-mongo.yml | 37 ++++++++++ .github/workflows/sbom-mysql.yml | 37 ++++++++++ .github/workflows/sbom-neo4j.yml | 37 ++++++++++ .github/workflows/sbom-nextcloud.yml | 37 ++++++++++ .github/workflows/sbom-nginx.yml | 37 ++++++++++ .github/workflows/sbom-node.yml | 37 ++++++++++ .github/workflows/sbom-perl.yml | 37 ++++++++++ .github/workflows/sbom-php.yml | 37 ++++++++++ .github/workflows/sbom-postgres.yml | 37 ++++++++++ .github/workflows/sbom-python.yml | 37 ++++++++++ .github/workflows/sbom-rabbitmq.yml | 37 ++++++++++ .github/workflows/sbom-redis.yml | 37 ++++++++++ .github/workflows/sbom-registry.yml | 37 ++++++++++ .github/workflows/sbom-ruby.yml | 37 ++++++++++ .github/workflows/sbom-solr.yml | 37 ++++++++++ .github/workflows/sbom-sonarqube.yml | 37 ++++++++++ .github/workflows/sbom-telegraf.yml | 37 ++++++++++ .github/workflows/sbom-tomcat.yml | 37 ++++++++++ .github/workflows/sbom-traefik.yml | 37 ++++++++++ .github/workflows/sbom-ubuntu.yml | 37 ++++++++++ .github/workflows/sbom-wordpress.yml | 37 ++++++++++ .github/workflows/sbom-zookeeper.yml | 37 ++++++++++ .github/workflows/tea-sync.yml | 78 ++++++++++++++++++++ CLAUDE.md | 65 ++++++++++++++++ apps/alpine/config.yaml | 14 ++++ apps/bash/config.yaml | 14 ++++ apps/cassandra/config.yaml | 14 ++++ apps/debian/config.yaml | 14 ++++ apps/dependency-track-frontend/config.yaml | 1 + apps/dependency-track/config.yaml | 1 + apps/drupal/config.yaml | 14 ++++ apps/eclipse-mosquitto/config.yaml | 14 ++++ apps/eclipse-temurin/config.yaml | 14 ++++ apps/ghost/config.yaml | 14 ++++ apps/golang/config.yaml | 14 ++++ apps/gradle/config.yaml | 14 ++++ apps/haproxy/config.yaml | 14 ++++ apps/httpd/config.yaml | 14 ++++ apps/influxdb/config.yaml | 14 ++++ apps/kong/config.yaml | 14 ++++ apps/mariadb/config.yaml | 14 ++++ apps/maven/config.yaml | 14 ++++ apps/memcached/config.yaml | 14 ++++ apps/mongo-express/config.yaml | 14 ++++ apps/mongo/config.yaml | 14 ++++ apps/mysql/config.yaml | 14 ++++ apps/neo4j/config.yaml | 14 ++++ apps/nextcloud/config.yaml | 14 ++++ apps/nginx/config.yaml | 24 ++++++ apps/node/config.yaml | 14 ++++ apps/perl/config.yaml | 14 ++++ apps/php/config.yaml | 14 ++++ apps/postgres/config.yaml | 14 ++++ apps/python/config.yaml | 14 ++++ apps/rabbitmq/config.yaml | 14 ++++ apps/redis/config.yaml | 14 ++++ apps/registry/config.yaml | 14 ++++ apps/ruby/config.yaml | 14 ++++ apps/solr/config.yaml | 14 ++++ apps/sonarqube/config.yaml | 14 ++++ apps/telegraf/config.yaml | 14 ++++ apps/tomcat/config.yaml | 14 ++++ apps/traefik/config.yaml | 14 ++++ apps/ubuntu/config.yaml | 14 ++++ apps/wordpress/config.yaml | 14 ++++ apps/zookeeper/config.yaml | 14 ++++ scripts/lib/common.sh | 8 +- scripts/sources/docker-attestation.sh | 24 +++++- 87 files changed, 2282 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/sbom-alpine.yml create mode 100644 .github/workflows/sbom-bash.yml create mode 100644 .github/workflows/sbom-cassandra.yml create mode 100644 .github/workflows/sbom-debian.yml create mode 100644 .github/workflows/sbom-drupal.yml create mode 100644 .github/workflows/sbom-eclipse-mosquitto.yml create mode 100644 .github/workflows/sbom-eclipse-temurin.yml create mode 100644 .github/workflows/sbom-ghost.yml create mode 100644 .github/workflows/sbom-golang.yml create mode 100644 .github/workflows/sbom-gradle.yml create mode 100644 .github/workflows/sbom-haproxy.yml create mode 100644 .github/workflows/sbom-httpd.yml create mode 100644 .github/workflows/sbom-influxdb.yml create mode 100644 .github/workflows/sbom-kong.yml create mode 100644 .github/workflows/sbom-mariadb.yml create mode 100644 .github/workflows/sbom-maven.yml create mode 100644 .github/workflows/sbom-memcached.yml create mode 100644 .github/workflows/sbom-mongo-express.yml create mode 100644 .github/workflows/sbom-mongo.yml create mode 100644 .github/workflows/sbom-mysql.yml create mode 100644 .github/workflows/sbom-neo4j.yml create mode 100644 .github/workflows/sbom-nextcloud.yml create mode 100644 .github/workflows/sbom-nginx.yml create mode 100644 .github/workflows/sbom-node.yml create mode 100644 .github/workflows/sbom-perl.yml create mode 100644 .github/workflows/sbom-php.yml create mode 100644 .github/workflows/sbom-postgres.yml create mode 100644 .github/workflows/sbom-python.yml create mode 100644 .github/workflows/sbom-rabbitmq.yml create mode 100644 .github/workflows/sbom-redis.yml create mode 100644 .github/workflows/sbom-registry.yml create mode 100644 .github/workflows/sbom-ruby.yml create mode 100644 .github/workflows/sbom-solr.yml create mode 100644 .github/workflows/sbom-sonarqube.yml create mode 100644 .github/workflows/sbom-telegraf.yml create mode 100644 .github/workflows/sbom-tomcat.yml create mode 100644 .github/workflows/sbom-traefik.yml create mode 100644 .github/workflows/sbom-ubuntu.yml create mode 100644 .github/workflows/sbom-wordpress.yml create mode 100644 .github/workflows/sbom-zookeeper.yml create mode 100644 .github/workflows/tea-sync.yml create mode 100644 CLAUDE.md create mode 100644 apps/alpine/config.yaml create mode 100644 apps/bash/config.yaml create mode 100644 apps/cassandra/config.yaml create mode 100644 apps/debian/config.yaml create mode 100644 apps/drupal/config.yaml create mode 100644 apps/eclipse-mosquitto/config.yaml create mode 100644 apps/eclipse-temurin/config.yaml create mode 100644 apps/ghost/config.yaml create mode 100644 apps/golang/config.yaml create mode 100644 apps/gradle/config.yaml create mode 100644 apps/haproxy/config.yaml create mode 100644 apps/httpd/config.yaml create mode 100644 apps/influxdb/config.yaml create mode 100644 apps/kong/config.yaml create mode 100644 apps/mariadb/config.yaml create mode 100644 apps/maven/config.yaml create mode 100644 apps/memcached/config.yaml create mode 100644 apps/mongo-express/config.yaml create mode 100644 apps/mongo/config.yaml create mode 100644 apps/mysql/config.yaml create mode 100644 apps/neo4j/config.yaml create mode 100644 apps/nextcloud/config.yaml create mode 100644 apps/nginx/config.yaml create mode 100644 apps/node/config.yaml create mode 100644 apps/perl/config.yaml create mode 100644 apps/php/config.yaml create mode 100644 apps/postgres/config.yaml create mode 100644 apps/python/config.yaml create mode 100644 apps/rabbitmq/config.yaml create mode 100644 apps/redis/config.yaml create mode 100644 apps/registry/config.yaml create mode 100644 apps/ruby/config.yaml create mode 100644 apps/solr/config.yaml create mode 100644 apps/sonarqube/config.yaml create mode 100644 apps/telegraf/config.yaml create mode 100644 apps/tomcat/config.yaml create mode 100644 apps/traefik/config.yaml create mode 100644 apps/ubuntu/config.yaml create mode 100644 apps/wordpress/config.yaml create mode 100644 apps/zookeeper/config.yaml diff --git a/.github/workflows/sbom-alpine.yml b/.github/workflows/sbom-alpine.yml new file mode 100644 index 0000000..2a3f997 --- /dev/null +++ b/.github/workflows/sbom-alpine.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Alpine Linux (Docker Official) +# +# Triggers when alpine config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/alpine + +name: "SBOM: alpine" + +on: + push: + branches: + - master + paths: + - 'apps/alpine/config.yaml' + - '.github/workflows/sbom-alpine.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: alpine + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-bash.yml b/.github/workflows/sbom-bash.yml new file mode 100644 index 0000000..fc560d3 --- /dev/null +++ b/.github/workflows/sbom-bash.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Bash (Docker Official) +# +# Triggers when bash config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/bash + +name: "SBOM: bash" + +on: + push: + branches: + - master + paths: + - 'apps/bash/config.yaml' + - '.github/workflows/sbom-bash.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: bash + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-builder.yml b/.github/workflows/sbom-builder.yml index 91cabc7..4365517 100644 --- a/.github/workflows/sbom-builder.yml +++ b/.github/workflows/sbom-builder.yml @@ -111,9 +111,10 @@ jobs: name: lockfile-${{ inputs.app }}-${{ steps.config.outputs.version }} path: ${{ steps.config.outputs.lockfile_path }} + # Phase 1: Build augmented SBOM locally (no upload) - name: Build SBOM (from existing SBOM) if: steps.config.outputs.component_id != '' && steps.config.outputs.source_type != 'lockfile' - uses: sbomify/github-action@master + uses: sbomify/sbomify-action@master env: TOKEN: ${{ secrets.SBOMIFY_TOKEN }} COMPONENT_ID: ${{ steps.config.outputs.component_id }} @@ -123,12 +124,11 @@ jobs: OUTPUT_FILE: sbom-output.json AUGMENT: true ENRICH: true - UPLOAD: ${{ !inputs.dry_run }} - PRODUCT_RELEASE: ${{ steps.config.outputs.product_release }} + UPLOAD: false - name: Build SBOM (from lockfile) if: steps.config.outputs.component_id != '' && steps.config.outputs.source_type == 'lockfile' - uses: sbomify/github-action@master + uses: sbomify/sbomify-action@master env: TOKEN: ${{ secrets.SBOMIFY_TOKEN }} COMPONENT_ID: ${{ steps.config.outputs.component_id }} @@ -138,7 +138,60 @@ jobs: OUTPUT_FILE: sbom-output.json AUGMENT: true ENRICH: true - UPLOAD: ${{ !inputs.dry_run }} + UPLOAD: false + + # Phase 2: Check if this exact SBOM is already published + - name: Install uv + if: steps.config.outputs.component_id != '' && !inputs.dry_run + uses: astral-sh/setup-uv@v4 + + - name: Check TEA for existing SBOM + id: tea-check + if: steps.config.outputs.component_id != '' && !inputs.dry_run + run: | + sbom_hash=$(sha256sum sbom-output.json | cut -d' ' -f1) + echo "SBOM hash: $sbom_hash" + tei="urn:tei:hash:library.sbomify.com:sha256:${sbom_hash}" + echo "TEI: $tei" + + result=$(uvx --from 'libtea[cli]' tea-cli discover "$tei" --json 2>/dev/null || true) + if [[ -z "$result" || "$result" == "[]" ]]; then + echo "SBOM not found on TEA, will upload" + echo "should_upload=true" >> "$GITHUB_OUTPUT" + else + echo "SBOM already published on TEA, skipping upload" + echo "should_upload=false" >> "$GITHUB_OUTPUT" + fi + + # Phase 3: Upload only if SBOM is new + - name: Upload SBOM (from existing SBOM) + if: >- + steps.config.outputs.component_id != '' && steps.config.outputs.source_type != 'lockfile' + && !inputs.dry_run && steps.tea-check.outputs.should_upload == 'true' + uses: sbomify/sbomify-action@master + env: + TOKEN: ${{ secrets.SBOMIFY_TOKEN }} + COMPONENT_ID: ${{ steps.config.outputs.component_id }} + COMPONENT_NAME: ${{ steps.config.outputs.component_name }} + COMPONENT_VERSION: ${{ steps.config.outputs.version }} + SBOM_FILE: sbom-output.json + OUTPUT_FILE: sbom-final.json + UPLOAD: true + PRODUCT_RELEASE: ${{ steps.config.outputs.product_release }} + + - name: Upload SBOM (from lockfile) + if: >- + steps.config.outputs.component_id != '' && steps.config.outputs.source_type == 'lockfile' + && !inputs.dry_run && steps.tea-check.outputs.should_upload == 'true' + uses: sbomify/sbomify-action@master + env: + TOKEN: ${{ secrets.SBOMIFY_TOKEN }} + COMPONENT_ID: ${{ steps.config.outputs.component_id }} + COMPONENT_NAME: ${{ steps.config.outputs.component_name }} + COMPONENT_VERSION: ${{ steps.config.outputs.version }} + SBOM_FILE: sbom-output.json + OUTPUT_FILE: sbom-final.json + UPLOAD: true PRODUCT_RELEASE: ${{ steps.config.outputs.product_release }} - name: Upload output artifact @@ -149,7 +202,9 @@ jobs: path: sbom-output.json - name: Attest SBOM provenance - if: steps.config.outputs.component_id != '' && !inputs.dry_run + if: >- + steps.config.outputs.component_id != '' && !inputs.dry_run + && steps.tea-check.outputs.should_upload == 'true' uses: actions/attest-build-provenance@v3 with: subject-path: sbom-output.json diff --git a/.github/workflows/sbom-cassandra.yml b/.github/workflows/sbom-cassandra.yml new file mode 100644 index 0000000..3884e4d --- /dev/null +++ b/.github/workflows/sbom-cassandra.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Apache Cassandra (Docker Official) +# +# Triggers when cassandra config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/cassandra + +name: "SBOM: cassandra" + +on: + push: + branches: + - master + paths: + - 'apps/cassandra/config.yaml' + - '.github/workflows/sbom-cassandra.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: cassandra + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-debian.yml b/.github/workflows/sbom-debian.yml new file mode 100644 index 0000000..372d5fc --- /dev/null +++ b/.github/workflows/sbom-debian.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Debian (Docker Official) +# +# Triggers when debian config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/debian + +name: "SBOM: debian" + +on: + push: + branches: + - master + paths: + - 'apps/debian/config.yaml' + - '.github/workflows/sbom-debian.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: debian + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-drupal.yml b/.github/workflows/sbom-drupal.yml new file mode 100644 index 0000000..17e5af1 --- /dev/null +++ b/.github/workflows/sbom-drupal.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Drupal (Docker Official) +# +# Triggers when drupal config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/drupal + +name: "SBOM: drupal" + +on: + push: + branches: + - master + paths: + - 'apps/drupal/config.yaml' + - '.github/workflows/sbom-drupal.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: drupal + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-eclipse-mosquitto.yml b/.github/workflows/sbom-eclipse-mosquitto.yml new file mode 100644 index 0000000..92409cd --- /dev/null +++ b/.github/workflows/sbom-eclipse-mosquitto.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Eclipse Mosquitto (Docker Official) +# +# Triggers when eclipse-mosquitto config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/eclipse-mosquitto + +name: "SBOM: eclipse-mosquitto" + +on: + push: + branches: + - master + paths: + - 'apps/eclipse-mosquitto/config.yaml' + - '.github/workflows/sbom-eclipse-mosquitto.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: eclipse-mosquitto + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-eclipse-temurin.yml b/.github/workflows/sbom-eclipse-temurin.yml new file mode 100644 index 0000000..af2a989 --- /dev/null +++ b/.github/workflows/sbom-eclipse-temurin.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Eclipse Temurin (Docker Official) +# +# Triggers when eclipse-temurin config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/eclipse-temurin + +name: "SBOM: eclipse-temurin" + +on: + push: + branches: + - master + paths: + - 'apps/eclipse-temurin/config.yaml' + - '.github/workflows/sbom-eclipse-temurin.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: eclipse-temurin + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-ghost.yml b/.github/workflows/sbom-ghost.yml new file mode 100644 index 0000000..f945259 --- /dev/null +++ b/.github/workflows/sbom-ghost.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Ghost (Docker Official) +# +# Triggers when ghost config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/ghost + +name: "SBOM: ghost" + +on: + push: + branches: + - master + paths: + - 'apps/ghost/config.yaml' + - '.github/workflows/sbom-ghost.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: ghost + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-golang.yml b/.github/workflows/sbom-golang.yml new file mode 100644 index 0000000..5d19253 --- /dev/null +++ b/.github/workflows/sbom-golang.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Go (Docker Official) +# +# Triggers when golang config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/golang + +name: "SBOM: golang" + +on: + push: + branches: + - master + paths: + - 'apps/golang/config.yaml' + - '.github/workflows/sbom-golang.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: golang + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-gradle.yml b/.github/workflows/sbom-gradle.yml new file mode 100644 index 0000000..8fd0b21 --- /dev/null +++ b/.github/workflows/sbom-gradle.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Gradle (Docker Official) +# +# Triggers when gradle config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/gradle + +name: "SBOM: gradle" + +on: + push: + branches: + - master + paths: + - 'apps/gradle/config.yaml' + - '.github/workflows/sbom-gradle.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: gradle + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-haproxy.yml b/.github/workflows/sbom-haproxy.yml new file mode 100644 index 0000000..f6cd492 --- /dev/null +++ b/.github/workflows/sbom-haproxy.yml @@ -0,0 +1,37 @@ +# SBOM workflow for HAProxy (Docker Official) +# +# Triggers when haproxy config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/haproxy + +name: "SBOM: haproxy" + +on: + push: + branches: + - master + paths: + - 'apps/haproxy/config.yaml' + - '.github/workflows/sbom-haproxy.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: haproxy + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-httpd.yml b/.github/workflows/sbom-httpd.yml new file mode 100644 index 0000000..faa8701 --- /dev/null +++ b/.github/workflows/sbom-httpd.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Apache HTTP Server (Docker Official) +# +# Triggers when httpd config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/httpd + +name: "SBOM: httpd" + +on: + push: + branches: + - master + paths: + - 'apps/httpd/config.yaml' + - '.github/workflows/sbom-httpd.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: httpd + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-influxdb.yml b/.github/workflows/sbom-influxdb.yml new file mode 100644 index 0000000..f177521 --- /dev/null +++ b/.github/workflows/sbom-influxdb.yml @@ -0,0 +1,37 @@ +# SBOM workflow for InfluxDB (Docker Official) +# +# Triggers when influxdb config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/influxdb + +name: "SBOM: influxdb" + +on: + push: + branches: + - master + paths: + - 'apps/influxdb/config.yaml' + - '.github/workflows/sbom-influxdb.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: influxdb + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-kong.yml b/.github/workflows/sbom-kong.yml new file mode 100644 index 0000000..b4fb509 --- /dev/null +++ b/.github/workflows/sbom-kong.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Kong Gateway (Docker Official) +# +# Triggers when kong config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/kong + +name: "SBOM: kong" + +on: + push: + branches: + - master + paths: + - 'apps/kong/config.yaml' + - '.github/workflows/sbom-kong.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: kong + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-mariadb.yml b/.github/workflows/sbom-mariadb.yml new file mode 100644 index 0000000..84992a1 --- /dev/null +++ b/.github/workflows/sbom-mariadb.yml @@ -0,0 +1,37 @@ +# SBOM workflow for MariaDB (Docker Official) +# +# Triggers when mariadb config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/mariadb + +name: "SBOM: mariadb" + +on: + push: + branches: + - master + paths: + - 'apps/mariadb/config.yaml' + - '.github/workflows/sbom-mariadb.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: mariadb + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-maven.yml b/.github/workflows/sbom-maven.yml new file mode 100644 index 0000000..ca60735 --- /dev/null +++ b/.github/workflows/sbom-maven.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Apache Maven (Docker Official) +# +# Triggers when maven config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/maven + +name: "SBOM: maven" + +on: + push: + branches: + - master + paths: + - 'apps/maven/config.yaml' + - '.github/workflows/sbom-maven.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: maven + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-memcached.yml b/.github/workflows/sbom-memcached.yml new file mode 100644 index 0000000..429fb85 --- /dev/null +++ b/.github/workflows/sbom-memcached.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Memcached (Docker Official) +# +# Triggers when memcached config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/memcached + +name: "SBOM: memcached" + +on: + push: + branches: + - master + paths: + - 'apps/memcached/config.yaml' + - '.github/workflows/sbom-memcached.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: memcached + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-mongo-express.yml b/.github/workflows/sbom-mongo-express.yml new file mode 100644 index 0000000..5f5cb9c --- /dev/null +++ b/.github/workflows/sbom-mongo-express.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Mongo Express (Docker Official) +# +# Triggers when mongo-express config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/mongo-express + +name: "SBOM: mongo-express" + +on: + push: + branches: + - master + paths: + - 'apps/mongo-express/config.yaml' + - '.github/workflows/sbom-mongo-express.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: mongo-express + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-mongo.yml b/.github/workflows/sbom-mongo.yml new file mode 100644 index 0000000..b483a15 --- /dev/null +++ b/.github/workflows/sbom-mongo.yml @@ -0,0 +1,37 @@ +# SBOM workflow for MongoDB (Docker Official) +# +# Triggers when mongo config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/mongo + +name: "SBOM: mongo" + +on: + push: + branches: + - master + paths: + - 'apps/mongo/config.yaml' + - '.github/workflows/sbom-mongo.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: mongo + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-mysql.yml b/.github/workflows/sbom-mysql.yml new file mode 100644 index 0000000..16d2594 --- /dev/null +++ b/.github/workflows/sbom-mysql.yml @@ -0,0 +1,37 @@ +# SBOM workflow for MySQL (Docker Official) +# +# Triggers when mysql config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/mysql + +name: "SBOM: mysql" + +on: + push: + branches: + - master + paths: + - 'apps/mysql/config.yaml' + - '.github/workflows/sbom-mysql.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: mysql + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-neo4j.yml b/.github/workflows/sbom-neo4j.yml new file mode 100644 index 0000000..4b07b56 --- /dev/null +++ b/.github/workflows/sbom-neo4j.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Neo4j (Docker Official) +# +# Triggers when neo4j config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/neo4j + +name: "SBOM: neo4j" + +on: + push: + branches: + - master + paths: + - 'apps/neo4j/config.yaml' + - '.github/workflows/sbom-neo4j.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: neo4j + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-nextcloud.yml b/.github/workflows/sbom-nextcloud.yml new file mode 100644 index 0000000..625560e --- /dev/null +++ b/.github/workflows/sbom-nextcloud.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Nextcloud (Docker Official) +# +# Triggers when nextcloud config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/nextcloud + +name: "SBOM: nextcloud" + +on: + push: + branches: + - master + paths: + - 'apps/nextcloud/config.yaml' + - '.github/workflows/sbom-nextcloud.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: nextcloud + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-nginx.yml b/.github/workflows/sbom-nginx.yml new file mode 100644 index 0000000..d4120ed --- /dev/null +++ b/.github/workflows/sbom-nginx.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Nginx (Chainguard) +# +# Triggers when nginx config is updated. +# Downloads SBOM from Chainguard cosign attestation (SPDX). +# Also picked up by tea-sync hourly for rolling digest changes. +# +# https://images.chainguard.dev/directory/image/nginx/overview + +name: "SBOM: nginx" + +on: + push: + branches: + - master + paths: + - 'apps/nginx/config.yaml' + - '.github/workflows/sbom-nginx.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: nginx + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-node.yml b/.github/workflows/sbom-node.yml new file mode 100644 index 0000000..f569990 --- /dev/null +++ b/.github/workflows/sbom-node.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Node.js (Docker Official) +# +# Triggers when node config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/node + +name: "SBOM: node" + +on: + push: + branches: + - master + paths: + - 'apps/node/config.yaml' + - '.github/workflows/sbom-node.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: node + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-perl.yml b/.github/workflows/sbom-perl.yml new file mode 100644 index 0000000..3eb3b63 --- /dev/null +++ b/.github/workflows/sbom-perl.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Perl (Docker Official) +# +# Triggers when perl config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/perl + +name: "SBOM: perl" + +on: + push: + branches: + - master + paths: + - 'apps/perl/config.yaml' + - '.github/workflows/sbom-perl.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: perl + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-php.yml b/.github/workflows/sbom-php.yml new file mode 100644 index 0000000..6152e4a --- /dev/null +++ b/.github/workflows/sbom-php.yml @@ -0,0 +1,37 @@ +# SBOM workflow for PHP (Docker Official) +# +# Triggers when php config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/php + +name: "SBOM: php" + +on: + push: + branches: + - master + paths: + - 'apps/php/config.yaml' + - '.github/workflows/sbom-php.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: php + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-postgres.yml b/.github/workflows/sbom-postgres.yml new file mode 100644 index 0000000..65052ba --- /dev/null +++ b/.github/workflows/sbom-postgres.yml @@ -0,0 +1,37 @@ +# SBOM workflow for PostgreSQL (Docker Official) +# +# Triggers when postgres config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/postgres + +name: "SBOM: postgres" + +on: + push: + branches: + - master + paths: + - 'apps/postgres/config.yaml' + - '.github/workflows/sbom-postgres.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: postgres + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-python.yml b/.github/workflows/sbom-python.yml new file mode 100644 index 0000000..1477e53 --- /dev/null +++ b/.github/workflows/sbom-python.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Python (Docker Official) +# +# Triggers when python config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/python + +name: "SBOM: python" + +on: + push: + branches: + - master + paths: + - 'apps/python/config.yaml' + - '.github/workflows/sbom-python.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: python + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-rabbitmq.yml b/.github/workflows/sbom-rabbitmq.yml new file mode 100644 index 0000000..b143d67 --- /dev/null +++ b/.github/workflows/sbom-rabbitmq.yml @@ -0,0 +1,37 @@ +# SBOM workflow for RabbitMQ (Docker Official) +# +# Triggers when rabbitmq config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/rabbitmq + +name: "SBOM: rabbitmq" + +on: + push: + branches: + - master + paths: + - 'apps/rabbitmq/config.yaml' + - '.github/workflows/sbom-rabbitmq.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: rabbitmq + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-redis.yml b/.github/workflows/sbom-redis.yml new file mode 100644 index 0000000..8aa3ed3 --- /dev/null +++ b/.github/workflows/sbom-redis.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Redis (Docker Official) +# +# Triggers when redis config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/redis + +name: "SBOM: redis" + +on: + push: + branches: + - master + paths: + - 'apps/redis/config.yaml' + - '.github/workflows/sbom-redis.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: redis + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-registry.yml b/.github/workflows/sbom-registry.yml new file mode 100644 index 0000000..68d1546 --- /dev/null +++ b/.github/workflows/sbom-registry.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Docker Registry (Docker Official) +# +# Triggers when registry config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/registry + +name: "SBOM: registry" + +on: + push: + branches: + - master + paths: + - 'apps/registry/config.yaml' + - '.github/workflows/sbom-registry.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: registry + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-ruby.yml b/.github/workflows/sbom-ruby.yml new file mode 100644 index 0000000..6e36de0 --- /dev/null +++ b/.github/workflows/sbom-ruby.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Ruby (Docker Official) +# +# Triggers when ruby config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/ruby + +name: "SBOM: ruby" + +on: + push: + branches: + - master + paths: + - 'apps/ruby/config.yaml' + - '.github/workflows/sbom-ruby.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: ruby + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-solr.yml b/.github/workflows/sbom-solr.yml new file mode 100644 index 0000000..053261b --- /dev/null +++ b/.github/workflows/sbom-solr.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Apache Solr (Docker Official) +# +# Triggers when solr config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/solr + +name: "SBOM: solr" + +on: + push: + branches: + - master + paths: + - 'apps/solr/config.yaml' + - '.github/workflows/sbom-solr.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: solr + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-sonarqube.yml b/.github/workflows/sbom-sonarqube.yml new file mode 100644 index 0000000..f47048e --- /dev/null +++ b/.github/workflows/sbom-sonarqube.yml @@ -0,0 +1,37 @@ +# SBOM workflow for SonarQube (Docker Official) +# +# Triggers when sonarqube config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/sonarqube + +name: "SBOM: sonarqube" + +on: + push: + branches: + - master + paths: + - 'apps/sonarqube/config.yaml' + - '.github/workflows/sbom-sonarqube.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: sonarqube + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-telegraf.yml b/.github/workflows/sbom-telegraf.yml new file mode 100644 index 0000000..943cd76 --- /dev/null +++ b/.github/workflows/sbom-telegraf.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Telegraf (Docker Official) +# +# Triggers when telegraf config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/telegraf + +name: "SBOM: telegraf" + +on: + push: + branches: + - master + paths: + - 'apps/telegraf/config.yaml' + - '.github/workflows/sbom-telegraf.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: telegraf + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-tomcat.yml b/.github/workflows/sbom-tomcat.yml new file mode 100644 index 0000000..4b99d8a --- /dev/null +++ b/.github/workflows/sbom-tomcat.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Apache Tomcat (Docker Official) +# +# Triggers when tomcat config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/tomcat + +name: "SBOM: tomcat" + +on: + push: + branches: + - master + paths: + - 'apps/tomcat/config.yaml' + - '.github/workflows/sbom-tomcat.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: tomcat + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-traefik.yml b/.github/workflows/sbom-traefik.yml new file mode 100644 index 0000000..daa528f --- /dev/null +++ b/.github/workflows/sbom-traefik.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Traefik (Docker Official) +# +# Triggers when traefik config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/traefik + +name: "SBOM: traefik" + +on: + push: + branches: + - master + paths: + - 'apps/traefik/config.yaml' + - '.github/workflows/sbom-traefik.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: traefik + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-ubuntu.yml b/.github/workflows/sbom-ubuntu.yml new file mode 100644 index 0000000..b7dfcca --- /dev/null +++ b/.github/workflows/sbom-ubuntu.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Ubuntu (Docker Official) +# +# Triggers when ubuntu config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/ubuntu + +name: "SBOM: ubuntu" + +on: + push: + branches: + - master + paths: + - 'apps/ubuntu/config.yaml' + - '.github/workflows/sbom-ubuntu.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: ubuntu + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-wordpress.yml b/.github/workflows/sbom-wordpress.yml new file mode 100644 index 0000000..2115753 --- /dev/null +++ b/.github/workflows/sbom-wordpress.yml @@ -0,0 +1,37 @@ +# SBOM workflow for WordPress (Docker Official) +# +# Triggers when wordpress config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/wordpress + +name: "SBOM: wordpress" + +on: + push: + branches: + - master + paths: + - 'apps/wordpress/config.yaml' + - '.github/workflows/sbom-wordpress.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: wordpress + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-zookeeper.yml b/.github/workflows/sbom-zookeeper.yml new file mode 100644 index 0000000..c26b5e9 --- /dev/null +++ b/.github/workflows/sbom-zookeeper.yml @@ -0,0 +1,37 @@ +# SBOM workflow for Apache ZooKeeper (Docker Official) +# +# Triggers when zookeeper config is updated. +# Extracts SBOM from Docker OCI attestation (SPDX). +# Also picked up by tea-sync hourly for image digest changes. +# +# https://hub.docker.com/_/zookeeper + +name: "SBOM: zookeeper" + +on: + push: + branches: + - master + paths: + - 'apps/zookeeper/config.yaml' + - '.github/workflows/sbom-zookeeper.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: zookeeper + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/tea-sync.yml b/.github/workflows/tea-sync.yml new file mode 100644 index 0000000..94beb5e --- /dev/null +++ b/.github/workflows/tea-sync.yml @@ -0,0 +1,78 @@ +# TEA Sync - Rebuild SBOMs for docker/chainguard apps +# +# Runs hourly to detect when upstream images change without a version bump. +# Triggers the SBOM builder for all docker/chainguard apps. The builder +# itself handles deduplication via TEA hash lookup — if the augmented SBOM +# is already published, the upload is skipped. + +name: TEA Sync + +on: + schedule: + - cron: '0 * * * *' + workflow_dispatch: {} + +env: + YQ_VERSION: "v4.40.5" + +jobs: + detect: + name: Find docker/chainguard apps + runs-on: ubuntu-latest + outputs: + apps: ${{ steps.find-apps.outputs.apps }} + has_apps: ${{ steps.find-apps.outputs.has_apps }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install yq + run: | + sudo curl -fsSL -o /usr/local/bin/yq \ + "https://github.com/mikefarah/yq/releases/download/${{ env.YQ_VERSION }}/yq_linux_amd64" + sudo chmod +x /usr/local/bin/yq + + - name: Find eligible apps + id: find-apps + run: | + apps=() + + for config in apps/*/config.yaml; do + app=$(basename "$(dirname "$config")") + source_type=$(yq -r '.source.type // ""' "$config") + + if [[ "$source_type" == "docker" || "$source_type" == "chainguard" ]]; then + echo "Found: $app ($source_type)" + apps+=("$app") + else + echo "Skipping $app ($source_type)" + fi + done + + json=$(printf '%s\n' "${apps[@]}" | jq -R -s -c 'split("\n") | map(select(. != ""))') + echo "Apps: $json" + echo "apps=${json}" >> "$GITHUB_OUTPUT" + + if [[ ${#apps[@]} -eq 0 ]]; then + echo "has_apps=false" >> "$GITHUB_OUTPUT" + else + echo "has_apps=true" >> "$GITHUB_OUTPUT" + fi + + sync: + name: Sync ${{ matrix.app }} + needs: detect + if: needs.detect.outputs.has_apps == 'true' + strategy: + fail-fast: false + matrix: + app: ${{ fromJson(needs.detect.outputs.apps) }} + uses: ./.github/workflows/sbom-builder.yml + with: + app: ${{ matrix.app }} + dry_run: false + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..5edd5e0 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,65 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +sbomify SBOM Library — automates Software Bill of Materials (SBOM) extraction from popular open-source projects and uploads them to sbomify.com. Each app has a `config.yaml` defining its version, SBOM format, and source type. + +## Architecture + +**Config-driven, per-app model:** Each app in `apps//config.yaml` declares how to fetch its SBOM. A central reusable workflow (`sbom-builder.yml`) reads the config and dispatches to the appropriate source handler. + +**Four SBOM source types** (each in `scripts/sources/`): +- `docker` — extract from Docker OCI attestations via `crane` +- `chainguard` — download signed attestations via `cosign` +- `github_release` — download SBOM asset from a GitHub release +- `lockfile` — download a lockfile (or clone repo) for SBOM generation + +**Script structure:** +- `scripts/fetch-sbom.sh` — entry point, routes to source handler +- `scripts/lib/common.sh` — shared utilities (logging, validation, config parsing) +- `scripts/sources/*.sh` — one handler per source type + +**CI/CD pattern:** Each app has a thin trigger workflow (`sbom-.yml`) that calls the reusable `sbom-builder.yml`. Path-based triggers ensure only changed apps rebuild. `ci.yml` validates PRs in dry-run mode. + +## Common Commands + +```bash +# Fetch SBOM for an app +./scripts/fetch-sbom.sh + +# Dry-run (no actual fetch/upload) +./scripts/fetch-sbom.sh --dry-run + +# Debug logging +LOG_LEVEL=DEBUG ./scripts/fetch-sbom.sh + +# Lint +shellcheck scripts/**/*.sh +yamllint . +``` + +## Adding a New App + +1. Copy `apps/.template/` to `apps//` +2. Edit `config.yaml` with version, format, source config, and sbomify component ID +3. Copy `.github/workflows/_sbom-template.yml` to `.github/workflows/sbom-.yml` and update paths/name + +## Code Conventions + +- **Bash:** `set -euo pipefail`, shellcheck-clean, proper quoting. Variables: `UPPER_CASE` for exports, `lower_case` for locals. Functions: `verb_noun` naming. +- **YAML:** 2-space indent, 120-char line limit (`.yamllint.yml`). +- **Versions:** Strict semver required in config.yaml — no "latest" tags. +- **App naming:** lowercase with hyphens (e.g., `osv-scanner`, `dependency-track-frontend`). + +## Environment Variables + +- `LOG_LEVEL` — DEBUG, INFO, WARN, ERROR (default: INFO) +- `DRY_RUN` — true/false +- `SBOMIFY_TOKEN` — API token for sbomify upload +- `GH_TOKEN` — GitHub API token + +## Required Tools + +bash 4.0+, jq, yq, crane, cosign (for chainguard sources), git diff --git a/apps/alpine/config.yaml b/apps/alpine/config.yaml new file mode 100644 index 0000000..45d197f --- /dev/null +++ b/apps/alpine/config.yaml @@ -0,0 +1,14 @@ +name: alpine +version: "3.23.3" +format: spdx + +source: + type: docker + image: "library/alpine" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "bKzgUrdak2jl" + component_name: "Alpine Linux" + product_id: "lR4o57FyDKzL" diff --git a/apps/bash/config.yaml b/apps/bash/config.yaml new file mode 100644 index 0000000..1b4350a --- /dev/null +++ b/apps/bash/config.yaml @@ -0,0 +1,14 @@ +name: bash +version: "5.3.9" +format: spdx + +source: + type: docker + image: "library/bash" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "hQs1ie0BWugp" + component_name: "Bash" + product_id: "QBQ0ynOS5ga8" diff --git a/apps/cassandra/config.yaml b/apps/cassandra/config.yaml new file mode 100644 index 0000000..4128f14 --- /dev/null +++ b/apps/cassandra/config.yaml @@ -0,0 +1,14 @@ +name: cassandra +version: "5.0.6" +format: spdx + +source: + type: docker + image: "library/cassandra" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "oIuuNVmjtLAw" + component_name: "Apache Cassandra" + product_id: "oBLEReaXmhrB" diff --git a/apps/debian/config.yaml b/apps/debian/config.yaml new file mode 100644 index 0000000..93178e5 --- /dev/null +++ b/apps/debian/config.yaml @@ -0,0 +1,14 @@ +name: debian +version: "12.11" +format: spdx + +source: + type: docker + image: "library/debian" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "utUsXu1LAKcy" + component_name: "Debian" + product_id: "gTiCfNK5WqNu" diff --git a/apps/dependency-track-frontend/config.yaml b/apps/dependency-track-frontend/config.yaml index 995321c..63f045d 100644 --- a/apps/dependency-track-frontend/config.yaml +++ b/apps/dependency-track-frontend/config.yaml @@ -20,4 +20,5 @@ source: sbomify: component_id: "UL9G0VKuqRiI" component_name: "Dependency Track Frontend" + product_id: "MoJ2O8FemjBc" diff --git a/apps/dependency-track/config.yaml b/apps/dependency-track/config.yaml index 3ca0c3b..15c9a1c 100644 --- a/apps/dependency-track/config.yaml +++ b/apps/dependency-track/config.yaml @@ -20,4 +20,5 @@ source: sbomify: component_id: "hZSsrKGgp1ZP" component_name: "Dependency Track API Server" + product_id: "MoJ2O8FemjBc" diff --git a/apps/drupal/config.yaml b/apps/drupal/config.yaml new file mode 100644 index 0000000..bb3953d --- /dev/null +++ b/apps/drupal/config.yaml @@ -0,0 +1,14 @@ +name: drupal +version: "11.3.3" +format: spdx + +source: + type: docker + image: "library/drupal" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "SEaIu1TmpCe3" + component_name: "Drupal" + product_id: "Giyoic8Rsmr6" diff --git a/apps/eclipse-mosquitto/config.yaml b/apps/eclipse-mosquitto/config.yaml new file mode 100644 index 0000000..0438dd6 --- /dev/null +++ b/apps/eclipse-mosquitto/config.yaml @@ -0,0 +1,14 @@ +name: eclipse-mosquitto +version: "2.1.2" +format: spdx + +source: + type: docker + image: "library/eclipse-mosquitto" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "RZxD3066UfHH" + component_name: "Eclipse Mosquitto" + product_id: "PLvRXXv1wTUk" diff --git a/apps/eclipse-temurin/config.yaml b/apps/eclipse-temurin/config.yaml new file mode 100644 index 0000000..377ded8 --- /dev/null +++ b/apps/eclipse-temurin/config.yaml @@ -0,0 +1,14 @@ +name: eclipse-temurin +version: "25.0.2_10-jdk-noble" +format: spdx + +source: + type: docker + image: "library/eclipse-temurin" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "YCYSmunHvhOC" + component_name: "Eclipse Temurin" + product_id: "tez37LDqal20" diff --git a/apps/ghost/config.yaml b/apps/ghost/config.yaml new file mode 100644 index 0000000..8498fab --- /dev/null +++ b/apps/ghost/config.yaml @@ -0,0 +1,14 @@ +name: ghost +version: "6.20.0" +format: spdx + +source: + type: docker + image: "library/ghost" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "pWgCahZC5xPp" + component_name: "Ghost" + product_id: "jp03nfwdWD3x" diff --git a/apps/golang/config.yaml b/apps/golang/config.yaml new file mode 100644 index 0000000..ea0f880 --- /dev/null +++ b/apps/golang/config.yaml @@ -0,0 +1,14 @@ +name: golang +version: "1.26.0" +format: spdx + +source: + type: docker + image: "library/golang" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "GkDDkMK1qe0q" + component_name: "Go" + product_id: "DPFjElH61z4A" diff --git a/apps/gradle/config.yaml b/apps/gradle/config.yaml new file mode 100644 index 0000000..e810b35 --- /dev/null +++ b/apps/gradle/config.yaml @@ -0,0 +1,14 @@ +name: gradle +version: "9.3.1" +format: spdx + +source: + type: docker + image: "library/gradle" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "NFeEXrJeQhzV" + component_name: "Gradle" + product_id: "dUlDeFPnei19" diff --git a/apps/haproxy/config.yaml b/apps/haproxy/config.yaml new file mode 100644 index 0000000..b96a0b3 --- /dev/null +++ b/apps/haproxy/config.yaml @@ -0,0 +1,14 @@ +name: haproxy +version: "3.3.4" +format: spdx + +source: + type: docker + image: "library/haproxy" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "m4bqIjHh8QU6" + component_name: "HAProxy" + product_id: "HS7FZr6Z3xjk" diff --git a/apps/httpd/config.yaml b/apps/httpd/config.yaml new file mode 100644 index 0000000..d382614 --- /dev/null +++ b/apps/httpd/config.yaml @@ -0,0 +1,14 @@ +name: httpd +version: "2.4.66" +format: spdx + +source: + type: docker + image: "library/httpd" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "JOy3yCj7Grul" + component_name: "Apache HTTP Server" + product_id: "ruWTQE7GtEWu" diff --git a/apps/influxdb/config.yaml b/apps/influxdb/config.yaml new file mode 100644 index 0000000..f9adf6e --- /dev/null +++ b/apps/influxdb/config.yaml @@ -0,0 +1,14 @@ +name: influxdb +version: "2" +format: spdx + +source: + type: docker + image: "library/influxdb" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "NH9kzZvGvdSc" + component_name: "InfluxDB" + product_id: "D1yKmXUTXaCp" diff --git a/apps/kong/config.yaml b/apps/kong/config.yaml new file mode 100644 index 0000000..2a6860b --- /dev/null +++ b/apps/kong/config.yaml @@ -0,0 +1,14 @@ +name: kong +version: "3.9.1" +format: spdx + +source: + type: docker + image: "library/kong" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "btLdIJ8XHsvx" + component_name: "Kong Gateway" + product_id: "apMx45XrFGit" diff --git a/apps/mariadb/config.yaml b/apps/mariadb/config.yaml new file mode 100644 index 0000000..161884e --- /dev/null +++ b/apps/mariadb/config.yaml @@ -0,0 +1,14 @@ +name: mariadb +version: "12.2.2" +format: spdx + +source: + type: docker + image: "library/mariadb" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "Fn2CB6i5QCyg" + component_name: "MariaDB" + product_id: "N2WH3M4WIeSe" diff --git a/apps/maven/config.yaml b/apps/maven/config.yaml new file mode 100644 index 0000000..ad5b2bf --- /dev/null +++ b/apps/maven/config.yaml @@ -0,0 +1,14 @@ +name: maven +version: "3.9.12" +format: spdx + +source: + type: docker + image: "library/maven" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "flwpzhDMl0jj" + component_name: "Apache Maven" + product_id: "M8h8x9sA3Q30" diff --git a/apps/memcached/config.yaml b/apps/memcached/config.yaml new file mode 100644 index 0000000..841f362 --- /dev/null +++ b/apps/memcached/config.yaml @@ -0,0 +1,14 @@ +name: memcached +version: "1.6.40" +format: spdx + +source: + type: docker + image: "library/memcached" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "DY5PDr2HvmO8" + component_name: "Memcached" + product_id: "mdU5IaJL0a3c" diff --git a/apps/mongo-express/config.yaml b/apps/mongo-express/config.yaml new file mode 100644 index 0000000..663bca5 --- /dev/null +++ b/apps/mongo-express/config.yaml @@ -0,0 +1,14 @@ +name: mongo-express +version: "1.0.2" +format: spdx + +source: + type: docker + image: "library/mongo-express" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "rAcKnctmMHtZ" + component_name: "Mongo Express" + product_id: "k2q6dJXPDE7a" diff --git a/apps/mongo/config.yaml b/apps/mongo/config.yaml new file mode 100644 index 0000000..93f0e27 --- /dev/null +++ b/apps/mongo/config.yaml @@ -0,0 +1,14 @@ +name: mongo +version: "8.2.5" +format: spdx + +source: + type: docker + image: "library/mongo" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "jfHJIfP0HTsX" + component_name: "MongoDB" + product_id: "J8c7mLCgK3BZ" diff --git a/apps/mysql/config.yaml b/apps/mysql/config.yaml new file mode 100644 index 0000000..988f0e9 --- /dev/null +++ b/apps/mysql/config.yaml @@ -0,0 +1,14 @@ +name: mysql +version: "9.6.0" +format: spdx + +source: + type: docker + image: "library/mysql" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "cK7BJXfnl4H3" + component_name: "MySQL" + product_id: "lKBb2t8mxkYK" diff --git a/apps/neo4j/config.yaml b/apps/neo4j/config.yaml new file mode 100644 index 0000000..67042f7 --- /dev/null +++ b/apps/neo4j/config.yaml @@ -0,0 +1,14 @@ +name: neo4j +version: "2026.01.4" +format: spdx + +source: + type: docker + image: "library/neo4j" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "kKzm0AbaxSfa" + component_name: "Neo4j" + product_id: "RWF5CMAXxuKa" diff --git a/apps/nextcloud/config.yaml b/apps/nextcloud/config.yaml new file mode 100644 index 0000000..50befd8 --- /dev/null +++ b/apps/nextcloud/config.yaml @@ -0,0 +1,14 @@ +name: nextcloud +version: "33.0.0" +format: spdx + +source: + type: docker + image: "library/nextcloud" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "Jb1ud2xdRV5F" + component_name: "Nextcloud" + product_id: "isvwpwqKgKum" diff --git a/apps/nginx/config.yaml b/apps/nginx/config.yaml new file mode 100644 index 0000000..3161041 --- /dev/null +++ b/apps/nginx/config.yaml @@ -0,0 +1,24 @@ +# Nginx (Chainguard) SBOM Configuration +# +# Chainguard's hardened, minimal nginx image. +# Uses rolling "latest" tag — digest changes without version bumps. +# The tea-sync workflow detects new digests hourly. +# +# SBOM source: Chainguard cosign attestation (SPDX) +# https://images.chainguard.dev/directory/image/nginx/overview + +name: nginx +version: "latest" + +# Chainguard publishes SPDX SBOMs +format: spdx + +source: + type: chainguard + image: "nginx" + registry: "cgr.dev/chainguard" + +sbomify: + component_id: "dmScTBXv5wN3" + component_name: "Nginx (Chainguard)" + product_id: "XoC0Ro4ozkjL" diff --git a/apps/node/config.yaml b/apps/node/config.yaml new file mode 100644 index 0000000..5a7c839 --- /dev/null +++ b/apps/node/config.yaml @@ -0,0 +1,14 @@ +name: node +version: "25.0.0" +format: spdx + +source: + type: docker + image: "library/node" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "tQJrbNKtQj9L" + component_name: "Node.js" + product_id: "uFOT9UPziTGy" diff --git a/apps/perl/config.yaml b/apps/perl/config.yaml new file mode 100644 index 0000000..1e7faa0 --- /dev/null +++ b/apps/perl/config.yaml @@ -0,0 +1,14 @@ +name: perl +version: "5.42.0" +format: spdx + +source: + type: docker + image: "library/perl" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "q7ONfvgQrpQ3" + component_name: "Perl" + product_id: "MTiQM6a0DKHM" diff --git a/apps/php/config.yaml b/apps/php/config.yaml new file mode 100644 index 0000000..3572ea4 --- /dev/null +++ b/apps/php/config.yaml @@ -0,0 +1,14 @@ +name: php +version: "8.5.3" +format: spdx + +source: + type: docker + image: "library/php" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "evPPPwZEs7pI" + component_name: "PHP" + product_id: "vbwatn2kX9VJ" diff --git a/apps/postgres/config.yaml b/apps/postgres/config.yaml new file mode 100644 index 0000000..a1b5354 --- /dev/null +++ b/apps/postgres/config.yaml @@ -0,0 +1,14 @@ +name: postgres +version: "18.3" +format: spdx + +source: + type: docker + image: "library/postgres" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "VbYFnUbp6hb3" + component_name: "PostgreSQL" + product_id: "cb4bTPDhndj1" diff --git a/apps/python/config.yaml b/apps/python/config.yaml new file mode 100644 index 0000000..5748728 --- /dev/null +++ b/apps/python/config.yaml @@ -0,0 +1,14 @@ +name: python +version: "3.14.3" +format: spdx + +source: + type: docker + image: "library/python" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "d5Ckld2GHK36" + component_name: "Python" + product_id: "DZk3qiQy5ey9" diff --git a/apps/rabbitmq/config.yaml b/apps/rabbitmq/config.yaml new file mode 100644 index 0000000..13c13ee --- /dev/null +++ b/apps/rabbitmq/config.yaml @@ -0,0 +1,14 @@ +name: rabbitmq +version: "4.2.4" +format: spdx + +source: + type: docker + image: "library/rabbitmq" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "tqp9wVrYpse1" + component_name: "RabbitMQ" + product_id: "Dg3DbwkrOe6p" diff --git a/apps/redis/config.yaml b/apps/redis/config.yaml new file mode 100644 index 0000000..efc2524 --- /dev/null +++ b/apps/redis/config.yaml @@ -0,0 +1,14 @@ +name: redis +version: "8.6.1" +format: spdx + +source: + type: docker + image: "library/redis" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "NtM9MiRLqcDH" + component_name: "Redis" + product_id: "uFfVy7vqchwy" diff --git a/apps/registry/config.yaml b/apps/registry/config.yaml new file mode 100644 index 0000000..0d795c2 --- /dev/null +++ b/apps/registry/config.yaml @@ -0,0 +1,14 @@ +name: registry +version: "3.0.0" +format: spdx + +source: + type: docker + image: "library/registry" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "pytZX4CKhijV" + component_name: "Docker Registry" + product_id: "eruyovtmbf1b" diff --git a/apps/ruby/config.yaml b/apps/ruby/config.yaml new file mode 100644 index 0000000..ed79467 --- /dev/null +++ b/apps/ruby/config.yaml @@ -0,0 +1,14 @@ +name: ruby +version: "4.0.1" +format: spdx + +source: + type: docker + image: "library/ruby" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "bz6hbPHK63OD" + component_name: "Ruby" + product_id: "TBHKte1ctfUn" diff --git a/apps/solr/config.yaml b/apps/solr/config.yaml new file mode 100644 index 0000000..9083493 --- /dev/null +++ b/apps/solr/config.yaml @@ -0,0 +1,14 @@ +name: solr +version: "9.10.1" +format: spdx + +source: + type: docker + image: "library/solr" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "NXBRWSLTOvgb" + component_name: "Apache Solr" + product_id: "qQ4HLsTl0pPF" diff --git a/apps/sonarqube/config.yaml b/apps/sonarqube/config.yaml new file mode 100644 index 0000000..f91f945 --- /dev/null +++ b/apps/sonarqube/config.yaml @@ -0,0 +1,14 @@ +name: sonarqube +version: "26.2.0.119303-community" +format: spdx + +source: + type: docker + image: "library/sonarqube" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "zxIeAhhucpbh" + component_name: "SonarQube" + product_id: "V7uHXOXLqEl9" diff --git a/apps/telegraf/config.yaml b/apps/telegraf/config.yaml new file mode 100644 index 0000000..a76a6a2 --- /dev/null +++ b/apps/telegraf/config.yaml @@ -0,0 +1,14 @@ +name: telegraf +version: "1.37.0" +format: spdx + +source: + type: docker + image: "library/telegraf" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "jYVZUIDeBJKr" + component_name: "Telegraf" + product_id: "phAZu64Y8F23" diff --git a/apps/tomcat/config.yaml b/apps/tomcat/config.yaml new file mode 100644 index 0000000..4f25dc1 --- /dev/null +++ b/apps/tomcat/config.yaml @@ -0,0 +1,14 @@ +name: tomcat +version: "11.0.18" +format: spdx + +source: + type: docker + image: "library/tomcat" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "XtLqFhMUnfgf" + component_name: "Apache Tomcat" + product_id: "M2ZOFIt3JsfI" diff --git a/apps/traefik/config.yaml b/apps/traefik/config.yaml new file mode 100644 index 0000000..a641fb5 --- /dev/null +++ b/apps/traefik/config.yaml @@ -0,0 +1,14 @@ +name: traefik +version: "v3.6.9" +format: spdx + +source: + type: docker + image: "library/traefik" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "vV4OdxZW9mIP" + component_name: "Traefik" + product_id: "a1xXrbBz2gEh" diff --git a/apps/ubuntu/config.yaml b/apps/ubuntu/config.yaml new file mode 100644 index 0000000..aea75a4 --- /dev/null +++ b/apps/ubuntu/config.yaml @@ -0,0 +1,14 @@ +name: ubuntu +version: "24.04" +format: spdx + +source: + type: docker + image: "library/ubuntu" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "MQUTwkIrxtOz" + component_name: "Ubuntu" + product_id: "u6ss2KevHMvn" diff --git a/apps/wordpress/config.yaml b/apps/wordpress/config.yaml new file mode 100644 index 0000000..46e30fe --- /dev/null +++ b/apps/wordpress/config.yaml @@ -0,0 +1,14 @@ +name: wordpress +version: "6.9.1" +format: spdx + +source: + type: docker + image: "library/wordpress" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "eek2LwRJuPOu" + component_name: "WordPress" + product_id: "PEDCbk2wOhHW" diff --git a/apps/zookeeper/config.yaml b/apps/zookeeper/config.yaml new file mode 100644 index 0000000..c077a6a --- /dev/null +++ b/apps/zookeeper/config.yaml @@ -0,0 +1,14 @@ +name: zookeeper +version: "3.9.4" +format: spdx + +source: + type: docker + image: "library/zookeeper" + registry: "docker.io" + platform: "linux/amd64" + +sbomify: + component_id: "YedZJ5vtjyHE" + component_name: "Apache ZooKeeper" + product_id: "B0VJWZ8OOOBg" diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh index a9a9e34..0550ada 100755 --- a/scripts/lib/common.sh +++ b/scripts/lib/common.sh @@ -159,8 +159,12 @@ get_latest_version() { die "Version not specified in: $config_file" fi - # Validate semver format - if ! validate_semver "$version"; then + # Validate semver format (skip for chainguard rolling tags like "latest") + local source_type + source_type="$(yq -r '.source.type // ""' "$config_file")" + if [[ "$source_type" == "chainguard" || "$source_type" == "docker" ]]; then + log_debug "Skipping semver validation for $source_type source (version: $version)" + elif ! validate_semver "$version"; then die "Invalid version '$version' in $config_file. Must be valid semver (e.g., 1.2.3, 1.2.3-rc1, 1.2.3+build)" fi diff --git a/scripts/sources/docker-attestation.sh b/scripts/sources/docker-attestation.sh index 0344171..9d4e874 100755 --- a/scripts/sources/docker-attestation.sh +++ b/scripts/sources/docker-attestation.sh @@ -18,13 +18,29 @@ image_ref="${registry}/${image}:${version}" log_info "Extracting SBOM: $image_ref" -manifest=$(crane manifest "$image_ref" --platform "$platform") +# Get the image index (multi-arch manifest list) +index=$(crane manifest "$image_ref") -sbom_digest=$(echo "$manifest" | jq -r ' +# Find the platform-specific image digest +IFS='/' read -r plat_os plat_arch <<< "$platform" +image_digest=$(echo "$index" | jq -r --arg os "$plat_os" --arg arch "$plat_arch" ' + .manifests[] | + select(.platform.os == $os and .platform.architecture == $arch) | + select(.annotations["vnd.docker.reference.type"] == null) | + .digest +' | head -1) + +[[ -z "$image_digest" || "$image_digest" == "null" ]] && \ + die "No image found for platform $platform" + +log_debug "Image digest: $image_digest" + +# Find the attestation manifest that references this image +sbom_digest=$(echo "$index" | jq -r --arg ref "$image_digest" ' .manifests[] | select( - (.annotations["vnd.docker.reference.type"] == "attestation-manifest") or - (.artifactType | contains("sbom") // false) + .annotations["vnd.docker.reference.type"] == "attestation-manifest" and + .annotations["vnd.docker.reference.digest"] == $ref ) | .digest ' | head -1) From 13c52790611cfb50b2ee9b002ee2c1fef6e558a1 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Wed, 4 Mar 2026 14:33:59 +0000 Subject: [PATCH 2/7] Add check-updates script, 11 OS/language images, fix CI failures - Add scripts/check-updates.sh for checking upstream version updates across all apps (supports --type, --app, --update, --json flags) - Add OS images: fedora, rockylinux, amazonlinux, oraclelinux - Add language images: rust, swift, elixir, erlang, r-base, haskell, julia - Fix eclipse-mosquitto: 2.1.2 -> 2.1.2-alpine (bare tag doesn't exist) - Fix trivy: 0.68.2 -> 0.69.3 (old release was deleted) - Remove nextcloud (no SBOM attestation in image, only SLSA provenance) - Update README with check-updates usage Co-Authored-By: Claude Opus 4.6 --- ...bom-nextcloud.yml => sbom-amazonlinux.yml} | 16 +- .github/workflows/sbom-elixir.yml | 29 + .github/workflows/sbom-erlang.yml | 29 + .github/workflows/sbom-fedora.yml | 29 + .github/workflows/sbom-haskell.yml | 29 + .github/workflows/sbom-julia.yml | 29 + .github/workflows/sbom-oraclelinux.yml | 29 + .github/workflows/sbom-r-base.yml | 29 + .github/workflows/sbom-rockylinux.yml | 29 + .github/workflows/sbom-rust.yml | 29 + .github/workflows/sbom-swift.yml | 29 + README.md | 28 +- apps/amazonlinux/config.yaml | 12 + apps/eclipse-mosquitto/config.yaml | 2 +- apps/elixir/config.yaml | 12 + apps/erlang/config.yaml | 12 + apps/fedora/config.yaml | 12 + apps/haskell/config.yaml | 12 + apps/julia/config.yaml | 12 + apps/nextcloud/config.yaml | 14 - apps/oraclelinux/config.yaml | 12 + apps/r-base/config.yaml | 12 + apps/rockylinux/config.yaml | 12 + apps/rust/config.yaml | 12 + apps/swift/config.yaml | 12 + apps/trivy/config.yaml | 2 +- scripts/check-updates.sh | 591 ++++++++++++++++++ 27 files changed, 1040 insertions(+), 35 deletions(-) rename .github/workflows/{sbom-nextcloud.yml => sbom-amazonlinux.yml} (55%) create mode 100644 .github/workflows/sbom-elixir.yml create mode 100644 .github/workflows/sbom-erlang.yml create mode 100644 .github/workflows/sbom-fedora.yml create mode 100644 .github/workflows/sbom-haskell.yml create mode 100644 .github/workflows/sbom-julia.yml create mode 100644 .github/workflows/sbom-oraclelinux.yml create mode 100644 .github/workflows/sbom-r-base.yml create mode 100644 .github/workflows/sbom-rockylinux.yml create mode 100644 .github/workflows/sbom-rust.yml create mode 100644 .github/workflows/sbom-swift.yml create mode 100644 apps/amazonlinux/config.yaml create mode 100644 apps/elixir/config.yaml create mode 100644 apps/erlang/config.yaml create mode 100644 apps/fedora/config.yaml create mode 100644 apps/haskell/config.yaml create mode 100644 apps/julia/config.yaml delete mode 100644 apps/nextcloud/config.yaml create mode 100644 apps/oraclelinux/config.yaml create mode 100644 apps/r-base/config.yaml create mode 100644 apps/rockylinux/config.yaml create mode 100644 apps/rust/config.yaml create mode 100644 apps/swift/config.yaml create mode 100755 scripts/check-updates.sh diff --git a/.github/workflows/sbom-nextcloud.yml b/.github/workflows/sbom-amazonlinux.yml similarity index 55% rename from .github/workflows/sbom-nextcloud.yml rename to .github/workflows/sbom-amazonlinux.yml index 625560e..faafcf8 100644 --- a/.github/workflows/sbom-nextcloud.yml +++ b/.github/workflows/sbom-amazonlinux.yml @@ -1,20 +1,12 @@ -# SBOM workflow for Nextcloud (Docker Official) -# -# Triggers when nextcloud config is updated. -# Extracts SBOM from Docker OCI attestation (SPDX). -# Also picked up by tea-sync hourly for image digest changes. -# -# https://hub.docker.com/_/nextcloud - -name: "SBOM: nextcloud" +name: "SBOM: amazonlinux" on: push: branches: - master paths: - - 'apps/nextcloud/config.yaml' - - '.github/workflows/sbom-nextcloud.yml' + - 'apps/amazonlinux/config.yaml' + - '.github/workflows/sbom-amazonlinux.yml' workflow_dispatch: inputs: @@ -28,7 +20,7 @@ jobs: build: uses: ./.github/workflows/sbom-builder.yml with: - app: nextcloud + app: amazonlinux dry_run: ${{ github.event.inputs.dry_run == 'true' }} secrets: inherit permissions: diff --git a/.github/workflows/sbom-elixir.yml b/.github/workflows/sbom-elixir.yml new file mode 100644 index 0000000..faa609d --- /dev/null +++ b/.github/workflows/sbom-elixir.yml @@ -0,0 +1,29 @@ +name: "SBOM: elixir" + +on: + push: + branches: + - master + paths: + - 'apps/elixir/config.yaml' + - '.github/workflows/sbom-elixir.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: elixir + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-erlang.yml b/.github/workflows/sbom-erlang.yml new file mode 100644 index 0000000..0968861 --- /dev/null +++ b/.github/workflows/sbom-erlang.yml @@ -0,0 +1,29 @@ +name: "SBOM: erlang" + +on: + push: + branches: + - master + paths: + - 'apps/erlang/config.yaml' + - '.github/workflows/sbom-erlang.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: erlang + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-fedora.yml b/.github/workflows/sbom-fedora.yml new file mode 100644 index 0000000..ed3a1a4 --- /dev/null +++ b/.github/workflows/sbom-fedora.yml @@ -0,0 +1,29 @@ +name: "SBOM: fedora" + +on: + push: + branches: + - master + paths: + - 'apps/fedora/config.yaml' + - '.github/workflows/sbom-fedora.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: fedora + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-haskell.yml b/.github/workflows/sbom-haskell.yml new file mode 100644 index 0000000..e4d725e --- /dev/null +++ b/.github/workflows/sbom-haskell.yml @@ -0,0 +1,29 @@ +name: "SBOM: haskell" + +on: + push: + branches: + - master + paths: + - 'apps/haskell/config.yaml' + - '.github/workflows/sbom-haskell.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: haskell + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-julia.yml b/.github/workflows/sbom-julia.yml new file mode 100644 index 0000000..3fa09a2 --- /dev/null +++ b/.github/workflows/sbom-julia.yml @@ -0,0 +1,29 @@ +name: "SBOM: julia" + +on: + push: + branches: + - master + paths: + - 'apps/julia/config.yaml' + - '.github/workflows/sbom-julia.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: julia + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-oraclelinux.yml b/.github/workflows/sbom-oraclelinux.yml new file mode 100644 index 0000000..78242db --- /dev/null +++ b/.github/workflows/sbom-oraclelinux.yml @@ -0,0 +1,29 @@ +name: "SBOM: oraclelinux" + +on: + push: + branches: + - master + paths: + - 'apps/oraclelinux/config.yaml' + - '.github/workflows/sbom-oraclelinux.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: oraclelinux + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-r-base.yml b/.github/workflows/sbom-r-base.yml new file mode 100644 index 0000000..12ed6f3 --- /dev/null +++ b/.github/workflows/sbom-r-base.yml @@ -0,0 +1,29 @@ +name: "SBOM: r-base" + +on: + push: + branches: + - master + paths: + - 'apps/r-base/config.yaml' + - '.github/workflows/sbom-r-base.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: r-base + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-rockylinux.yml b/.github/workflows/sbom-rockylinux.yml new file mode 100644 index 0000000..b19ab91 --- /dev/null +++ b/.github/workflows/sbom-rockylinux.yml @@ -0,0 +1,29 @@ +name: "SBOM: rockylinux" + +on: + push: + branches: + - master + paths: + - 'apps/rockylinux/config.yaml' + - '.github/workflows/sbom-rockylinux.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: rockylinux + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-rust.yml b/.github/workflows/sbom-rust.yml new file mode 100644 index 0000000..beeba1c --- /dev/null +++ b/.github/workflows/sbom-rust.yml @@ -0,0 +1,29 @@ +name: "SBOM: rust" + +on: + push: + branches: + - master + paths: + - 'apps/rust/config.yaml' + - '.github/workflows/sbom-rust.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: rust + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/.github/workflows/sbom-swift.yml b/.github/workflows/sbom-swift.yml new file mode 100644 index 0000000..575aa78 --- /dev/null +++ b/.github/workflows/sbom-swift.yml @@ -0,0 +1,29 @@ +name: "SBOM: swift" + +on: + push: + branches: + - master + paths: + - 'apps/swift/config.yaml' + - '.github/workflows/sbom-swift.yml' + + workflow_dispatch: + inputs: + dry_run: + description: 'Run in dry-run mode (no upload)' + required: false + type: boolean + default: false + +jobs: + build: + uses: ./.github/workflows/sbom-builder.yml + with: + app: swift + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/README.md b/README.md index d254530..1646b82 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Each app has its own folder with version tracking. When you bump the `version` i │ └── config.yaml # App configuration (includes version) ├── scripts/ │ ├── fetch-sbom.sh # Main entry point +│ ├── check-updates.sh # Check for upstream version updates │ ├── lib/ │ │ └── common.sh # Shared utilities │ └── sources/ @@ -234,9 +235,6 @@ For lockfile sources: ### Running Locally ```bash -# List available apps -./scripts/fetch-sbom.sh --list - # Fetch SBOM for an app ./scripts/fetch-sbom.sh nginx @@ -245,12 +243,28 @@ For lockfile sources: # Dry-run mode (no actual fetching) ./scripts/fetch-sbom.sh nginx --dry-run +``` + +### Checking for Updates + +```bash +# Check all apps for upstream version updates +./scripts/check-updates.sh + +# Only check specific source type +./scripts/check-updates.sh --type docker + +# Check specific apps +./scripts/check-updates.sh --app redis,trivy + +# Auto-update config.yaml files +./scripts/check-updates.sh --update -# Output to file -./scripts/fetch-sbom.sh nginx --output sbom.json +# Preview updates without writing +./scripts/check-updates.sh --update --dry-run -# Override version -./scripts/fetch-sbom.sh nginx --version 1.24.0 +# JSON output (for CI) +./scripts/check-updates.sh --json ``` ### Environment Variables diff --git a/apps/amazonlinux/config.yaml b/apps/amazonlinux/config.yaml new file mode 100644 index 0000000..ad80858 --- /dev/null +++ b/apps/amazonlinux/config.yaml @@ -0,0 +1,12 @@ +name: amazonlinux +version: "2023" +format: spdx +source: + type: docker + image: "library/amazonlinux" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "Ee393Rqsm1Cr" + component_name: "Amazon Linux" + product_id: "iF4eFtDmkYOL" diff --git a/apps/eclipse-mosquitto/config.yaml b/apps/eclipse-mosquitto/config.yaml index 0438dd6..8d44cb4 100644 --- a/apps/eclipse-mosquitto/config.yaml +++ b/apps/eclipse-mosquitto/config.yaml @@ -1,5 +1,5 @@ name: eclipse-mosquitto -version: "2.1.2" +version: "2.1.2-alpine" format: spdx source: diff --git a/apps/elixir/config.yaml b/apps/elixir/config.yaml new file mode 100644 index 0000000..b3e93bf --- /dev/null +++ b/apps/elixir/config.yaml @@ -0,0 +1,12 @@ +name: elixir +version: "1.19.5" +format: spdx +source: + type: docker + image: "library/elixir" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "lAFrNPRyd3C0" + component_name: "Elixir" + product_id: "rAsJawga7rVs" diff --git a/apps/erlang/config.yaml b/apps/erlang/config.yaml new file mode 100644 index 0000000..6ef27ca --- /dev/null +++ b/apps/erlang/config.yaml @@ -0,0 +1,12 @@ +name: erlang +version: "28.3.2.0" +format: spdx +source: + type: docker + image: "library/erlang" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "OrIcXZsMLVWF" + component_name: "Erlang" + product_id: "v1DMlwjOnrom" diff --git a/apps/fedora/config.yaml b/apps/fedora/config.yaml new file mode 100644 index 0000000..03ad697 --- /dev/null +++ b/apps/fedora/config.yaml @@ -0,0 +1,12 @@ +name: fedora +version: "43" +format: spdx +source: + type: docker + image: "library/fedora" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "khNxcYfndgS8" + component_name: "Fedora" + product_id: "BLpdu71SwXSy" diff --git a/apps/haskell/config.yaml b/apps/haskell/config.yaml new file mode 100644 index 0000000..3dea144 --- /dev/null +++ b/apps/haskell/config.yaml @@ -0,0 +1,12 @@ +name: haskell +version: "9.14.1" +format: spdx +source: + type: docker + image: "library/haskell" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "lvFUstRqj6RI" + component_name: "Haskell (GHC)" + product_id: "XZnGHIbIxZ8r" diff --git a/apps/julia/config.yaml b/apps/julia/config.yaml new file mode 100644 index 0000000..598ca5e --- /dev/null +++ b/apps/julia/config.yaml @@ -0,0 +1,12 @@ +name: julia +version: "1.12.5" +format: spdx +source: + type: docker + image: "library/julia" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "Ib4LRrXkb6kM" + component_name: "Julia" + product_id: "iia9JH3ewVZq" diff --git a/apps/nextcloud/config.yaml b/apps/nextcloud/config.yaml deleted file mode 100644 index 50befd8..0000000 --- a/apps/nextcloud/config.yaml +++ /dev/null @@ -1,14 +0,0 @@ -name: nextcloud -version: "33.0.0" -format: spdx - -source: - type: docker - image: "library/nextcloud" - registry: "docker.io" - platform: "linux/amd64" - -sbomify: - component_id: "Jb1ud2xdRV5F" - component_name: "Nextcloud" - product_id: "isvwpwqKgKum" diff --git a/apps/oraclelinux/config.yaml b/apps/oraclelinux/config.yaml new file mode 100644 index 0000000..829a0f0 --- /dev/null +++ b/apps/oraclelinux/config.yaml @@ -0,0 +1,12 @@ +name: oraclelinux +version: "9" +format: spdx +source: + type: docker + image: "library/oraclelinux" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "TeYsJGy2bHD0" + component_name: "Oracle Linux" + product_id: "nmIyLDwnDXPo" diff --git a/apps/r-base/config.yaml b/apps/r-base/config.yaml new file mode 100644 index 0000000..bd636ca --- /dev/null +++ b/apps/r-base/config.yaml @@ -0,0 +1,12 @@ +name: r-base +version: "4.5.2" +format: spdx +source: + type: docker + image: "library/r-base" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "QgmCH64nQWUT" + component_name: "R" + product_id: "Rri1h5kaem99" diff --git a/apps/rockylinux/config.yaml b/apps/rockylinux/config.yaml new file mode 100644 index 0000000..c9c5192 --- /dev/null +++ b/apps/rockylinux/config.yaml @@ -0,0 +1,12 @@ +name: rockylinux +version: "9.3.20231119" +format: spdx +source: + type: docker + image: "library/rockylinux" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "i5J4KAiSsaqE" + component_name: "Rocky Linux" + product_id: "wygKtpYVDXmj" diff --git a/apps/rust/config.yaml b/apps/rust/config.yaml new file mode 100644 index 0000000..3675c16 --- /dev/null +++ b/apps/rust/config.yaml @@ -0,0 +1,12 @@ +name: rust +version: "1.93.1" +format: spdx +source: + type: docker + image: "library/rust" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "BEoovD29bNnb" + component_name: "Rust" + product_id: "HBed132Jij5T" diff --git a/apps/swift/config.yaml b/apps/swift/config.yaml new file mode 100644 index 0000000..8d6b31d --- /dev/null +++ b/apps/swift/config.yaml @@ -0,0 +1,12 @@ +name: swift +version: "6.2.4" +format: spdx +source: + type: docker + image: "library/swift" + registry: "docker.io" + platform: "linux/amd64" +sbomify: + component_id: "uPJ2g6Z99b7U" + component_name: "Swift" + product_id: "yQzOiXrBpbzE" diff --git a/apps/trivy/config.yaml b/apps/trivy/config.yaml index 691af27..84d1f32 100644 --- a/apps/trivy/config.yaml +++ b/apps/trivy/config.yaml @@ -7,7 +7,7 @@ # https://github.com/aquasecurity/trivy name: trivy -version: "0.68.2" +version: "0.69.3" format: cyclonedx diff --git a/scripts/check-updates.sh b/scripts/check-updates.sh new file mode 100755 index 0000000..05197b8 --- /dev/null +++ b/scripts/check-updates.sh @@ -0,0 +1,591 @@ +#!/usr/bin/env bash +# check-updates.sh - Check for upstream version updates across all apps +# +# Usage: +# ./scripts/check-updates.sh # Check all apps +# ./scripts/check-updates.sh --type docker # Only docker apps +# ./scripts/check-updates.sh --app redis,trivy # Specific apps +# ./scripts/check-updates.sh --update # Auto-update config.yaml files +# ./scripts/check-updates.sh --json # JSON output for CI +# ./scripts/check-updates.sh --dry-run --update # Preview updates +# +# Exit codes: 0=all current, 1=updates available, 2=errors +# shellcheck source-path=SCRIPTDIR +# shellcheck source=lib/common.sh + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# shellcheck source=lib/common.sh +source "${SCRIPT_DIR}/lib/common.sh" + +# ============================================================================= +# Constants +# ============================================================================= + +MAX_PARALLEL=5 + +# ============================================================================= +# Argument Parsing +# ============================================================================= + +FILTER_TYPE="" +FILTER_APPS="" +AUTO_UPDATE=false +JSON_OUTPUT=false + +print_check_usage() { + cat >&2 <&1) || { + log_warn "Failed to fetch manifest for $image_ref: $index" + echo "ERROR" + return + } + + # Check if this is a manifest index (multi-arch) or a single manifest + local media_type + media_type=$(echo "$index" | jq -r '.mediaType // .schemaVersion' 2>/dev/null) + + local annotation="" + IFS='/' read -r plat_os plat_arch <<< "$platform" + + case "$media_type" in + *index*|*list*) + # Multi-arch manifest — find the annotation on the amd64 entry + annotation=$(echo "$index" | jq -r --arg os "$plat_os" --arg arch "$plat_arch" ' + .manifests[] | + select(.platform.os == $os and .platform.architecture == $arch) | + select(.annotations["vnd.docker.reference.type"] == null) | + .annotations["org.opencontainers.image.version"] // empty + ' 2>/dev/null | head -1) + ;; + *) + # Single manifest — check config for annotation + annotation=$(echo "$index" | jq -r ' + .annotations["org.opencontainers.image.version"] // empty + ' 2>/dev/null) + ;; + esac + + if [[ -z "$annotation" ]]; then + # Fallback: try to get annotation from image config + local config_json + config_json=$(crane config "${image_ref}" 2>/dev/null) || true + if [[ -n "$config_json" ]]; then + annotation=$(echo "$config_json" | jq -r ' + .config.Labels["org.opencontainers.image.version"] // empty + ' 2>/dev/null) + fi + fi + + if [[ -z "$annotation" ]]; then + log_warn "No version annotation found for $image_ref" + echo "ERROR" + return + fi + + log_debug "Annotation for $app: $annotation (current: $current)" + clean_docker_version "$annotation" "$current" +} + +# Get latest version from GitHub releases. +# Used for both github_release and lockfile source types. +get_latest_github_version() { + local app="$1" + local repo tag_prefix + repo=$(get_config "$app" ".source.repo") + tag_prefix=$(get_config "$app" ".source.tag_prefix" "") + + log_debug "Checking GitHub: $repo" + + local tag_name="" + + if command -v gh &>/dev/null; then + tag_name=$(gh release view --repo "$repo" --json tagName -q '.tagName' 2>&1) || { + log_warn "gh release view failed for $repo: $tag_name" + tag_name="" + } + fi + + # Fallback to curl if gh failed or isn't installed + if [[ -z "$tag_name" ]]; then + local api_url="https://api.github.com/repos/${repo}/releases/latest" + local response + response=$(curl -fsSL -H "Accept: application/vnd.github+json" \ + ${GH_TOKEN:+-H "Authorization: Bearer ${GH_TOKEN}"} \ + "$api_url" 2>&1) || { + log_warn "Failed to fetch latest release for $repo: $response" + echo "ERROR" + return + } + tag_name=$(echo "$response" | jq -r '.tag_name // empty') + fi + + if [[ -z "$tag_name" ]]; then + log_warn "No release found for $repo" + echo "ERROR" + return + fi + + # Strip tag prefix (e.g., "v1.2.3" → "1.2.3") + if [[ -n "$tag_prefix" && "$tag_name" == "${tag_prefix}"* ]]; then + tag_name="${tag_name#"$tag_prefix"}" + fi + + echo "$tag_name" +} + +# ============================================================================= +# Version Comparison +# ============================================================================= + +# Compare two versions. Returns 0 if latest is newer than current. +version_is_newer() { + local current="$1" + local latest="$2" + + if [[ "$current" == "$latest" ]]; then + return 1 + fi + + # Use sort -V (version sort) to determine order + local sorted_first + sorted_first=$(printf '%s\n%s\n' "$current" "$latest" | sort -V | head -1) + + # If current sorts first, then latest is newer + [[ "$sorted_first" == "$current" ]] +} + +# ============================================================================= +# Per-App Check +# ============================================================================= + +check_single_app() { + local app="$1" + local result_dir="$2" + + local source_type current latest status + source_type=$(get_source_type "$app") + current=$(yq -r '.version' "${APPS_DIR}/${app}/config.yaml") + + case "$source_type" in + docker) + latest=$(get_latest_docker_version "$app") + ;; + github_release|lockfile) + latest=$(get_latest_github_version "$app") + ;; + chainguard) + # Rolling latest tag — nothing to compare + echo "skipped|${app}|${current}||${source_type}" > "${result_dir}/${app}" + return + ;; + *) + log_warn "Unknown source type for $app: $source_type" + echo "error|${app}|${current}||${source_type}" > "${result_dir}/${app}" + return + ;; + esac + + if [[ "$latest" == "ERROR" ]]; then + echo "error|${app}|${current}||${source_type}" > "${result_dir}/${app}" + return + fi + + if version_is_newer "$current" "$latest"; then + status="update" + else + status="current" + fi + + echo "${status}|${app}|${current}|${latest}|${source_type}" > "${result_dir}/${app}" +} + +# ============================================================================= +# Output Functions +# ============================================================================= + +print_table() { + local result_dir="$1" + + local up_to_date=0 updates=0 skipped=0 errors=0 + + for result_file in "${result_dir}"/*; do + [[ -f "$result_file" ]] || continue + local line + line=$(<"$result_file") + + IFS='|' read -r status app current latest source_type <<< "$line" + + case "$status" in + current) + printf " %-30s %-24s (up to date)\n" "$app" "$current" + up_to_date=$(( up_to_date + 1 )) + ;; + update) + printf " %-30s %-24s -> %-16s (update available)\n" "$app" "$current" "$latest" + updates=$(( updates + 1 )) + ;; + skipped) + printf " %-30s %-24s (skipped: %s)\n" "$app" "$current" "$source_type" + skipped=$(( skipped + 1 )) + ;; + error) + printf " %-30s %-24s (error)\n" "$app" "$current" + errors=$(( errors + 1 )) + ;; + esac + done + + echo "" + echo "Summary: ${up_to_date} up to date, ${updates} updates available, ${skipped} skipped, ${errors} errors" +} + +print_json() { + local result_dir="$1" + + local results=() + local up_to_date=0 updates=0 skipped=0 errors=0 + + for result_file in "${result_dir}"/*; do + [[ -f "$result_file" ]] || continue + local line + line=$(<"$result_file") + + IFS='|' read -r status app current latest source_type <<< "$line" + + results+=("$(jq -n \ + --arg status "$status" \ + --arg app "$app" \ + --arg current "$current" \ + --arg latest "$latest" \ + --arg source_type "$source_type" \ + '{status: $status, app: $app, current_version: $current, latest_version: $latest, source_type: $source_type}' + )") + + case "$status" in + current) up_to_date=$(( up_to_date + 1 )) ;; + update) updates=$(( updates + 1 )) ;; + skipped) skipped=$(( skipped + 1 )) ;; + error) errors=$(( errors + 1 )) ;; + esac + done + + # Build JSON array from results + local json_array="[" + local first=true + for item in "${results[@]}"; do + if [[ "$first" == true ]]; then + first=false + else + json_array+="," + fi + json_array+="$item" + done + json_array+="]" + + jq -n \ + --argjson apps "$json_array" \ + --argjson up_to_date "$up_to_date" \ + --argjson updates "$updates" \ + --argjson skipped "$skipped" \ + --argjson errors "$errors" \ + '{ + apps: $apps, + summary: { + up_to_date: $up_to_date, + updates_available: $updates, + skipped: $skipped, + errors: $errors + } + }' +} + +# ============================================================================= +# Auto-Update +# ============================================================================= + +apply_updates() { + local result_dir="$1" + local applied=0 + + for result_file in "${result_dir}"/*; do + [[ -f "$result_file" ]] || continue + local line + line=$(<"$result_file") + + IFS='|' read -r status app _ latest _ <<< "$line" + + if [[ "$status" != "update" ]]; then + continue + fi + + local config_file="${APPS_DIR}/${app}/config.yaml" + + if is_dry_run; then + log_info "[DRY RUN] Would update $app to $latest in $config_file" + else + log_info "Updating $app to $latest" + yq -i ".version = \"${latest}\"" "$config_file" + fi + applied=$(( applied + 1 )) + done + + if [[ $applied -eq 0 ]]; then + log_info "No updates to apply." + else + local verb="Applied" + is_dry_run && verb="Would apply" + log_info "${verb} ${applied} update(s)." + fi +} + +# ============================================================================= +# Main +# ============================================================================= + +main() { + parse_args "$@" + + require_cmd "yq" "Install with: brew install yq" + require_cmd "jq" "Install with: brew install jq" + + # Check for source-type-specific tools + if [[ -z "$FILTER_TYPE" || "$FILTER_TYPE" == "docker" ]]; then + require_cmd "crane" "Install with: go install github.com/google/go-containerregistry/cmd/crane@latest" + fi + + # Build app list + local apps=() + + if [[ -n "$FILTER_APPS" ]]; then + IFS=',' read -ra apps <<< "$FILTER_APPS" + # Validate each app exists + for app in "${apps[@]}"; do + validate_app_dir "$app" + done + else + for config in "${APPS_DIR}"/*/config.yaml; do + local app_dir + app_dir="$(dirname "$config")" + local app + app="$(basename "$app_dir")" + + # Skip template + [[ "$app" == ".template" ]] && continue + + # Apply type filter + if [[ -n "$FILTER_TYPE" ]]; then + local src_type + src_type=$(yq -r '.source.type' "$config") + [[ "$src_type" != "$FILTER_TYPE" ]] && continue + fi + + apps+=("$app") + done + fi + + if [[ ${#apps[@]} -eq 0 ]]; then + die "No apps found matching filters." + fi + + local result_dir + result_dir=$(create_temp_dir "check-updates") + + if [[ "$JSON_OUTPUT" != true ]]; then + echo "Checking ${#apps[@]} apps for updates..." >&2 + echo "" >&2 + fi + + # Run checks in parallel, capped at MAX_PARALLEL + local running=0 + local pids=() + + for app in "${apps[@]}"; do + ( + # Clear inherited EXIT trap so child doesn't remove parent's temp dir + trap - EXIT + _SBOM_TEMP_DIRS=() + check_single_app "$app" "$result_dir" + ) & + pids+=($!) + running=$(( running + 1 )) + + if [[ $running -ge $MAX_PARALLEL ]]; then + # Wait for any one job to finish + wait -n 2>/dev/null || true + running=$(( running - 1 )) + fi + done + + # Wait for all remaining jobs + for pid in "${pids[@]}"; do + wait "$pid" 2>/dev/null || true + done + + # Output results + if [[ "$JSON_OUTPUT" == true ]]; then + print_json "$result_dir" + else + print_table "$result_dir" >&2 + fi + + # Apply updates if requested + if [[ "$AUTO_UPDATE" == true ]]; then + apply_updates "$result_dir" + fi + + # Set exit code based on results + local has_updates=false has_errors=false + for result_file in "${result_dir}"/*; do + [[ -f "$result_file" ]] || continue + local status + status=$(cut -d'|' -f1 < "$result_file") + [[ "$status" == "update" ]] && has_updates=true + [[ "$status" == "error" ]] && has_errors=true + done + + if [[ "$has_errors" == true ]]; then + exit 2 + elif [[ "$has_updates" == true ]]; then + exit 1 + fi + exit 0 +} + +main "$@" From f4ba964097508946720e0406e280a344ffc2e9b3 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Wed, 4 Mar 2026 14:39:17 +0000 Subject: [PATCH 3/7] Update README with complete project table and TEA TEI identifiers Reorganize projects table into categories (OS, Languages, Databases, Web Servers, Applications, Build Tools, Infrastructure, Security Tools) and add TEI column for TEA discovery of each SBOM. Co-Authored-By: Claude Opus 4.6 --- README.md | 109 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 99 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 1646b82..1c6991d 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,105 @@ Each app has its own folder with version tracking. When you bump the `version` i ## Projects -| Project | Component | Source | Job | sbomify | -|---------|-----------|--------|-----|---------| -| [Caddy](https://github.com/caddyserver/caddy) | Caddy | GitHub Release | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-caddy.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-caddy.yml) | [![sbomify](https://sbomify.com/assets/images/logo/badge.svg)](https://library.sbomify.com/product/caddy/) | -| [Dependency Track](https://github.com/DependencyTrack/dependency-track) | API Server | GitHub Release | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-dependency-track.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-dependency-track.yml) | [![sbomify](https://sbomify.com/assets/images/logo/badge.svg)](https://library.sbomify.com/product/dependency-track/) | -| [Dependency Track](https://github.com/DependencyTrack/frontend) | Frontend | GitHub Release | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-dependency-track-frontend.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-dependency-track-frontend.yml) | [![sbomify](https://sbomify.com/assets/images/logo/badge.svg)](https://library.sbomify.com/product/dependency-track/) | -| [Keycloak](https://github.com/keycloak/keycloak) | Backend | Lockfile (pom.xml) | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-keycloak.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-keycloak.yml) | [![sbomify](https://sbomify.com/assets/images/logo/badge.svg)](https://library.sbomify.com/product/keycloak/) | -| [Keycloak](https://github.com/keycloak/keycloak) | JS | Lockfile (pnpm) | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-keycloak-js.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-keycloak-js.yml) | [![sbomify](https://sbomify.com/assets/images/logo/badge.svg)](https://library.sbomify.com/product/keycloak/) | -| [OSV Scanner](https://github.com/google/osv-scanner) | OSV Scanner | Lockfile | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-osv-scanner.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-osv-scanner.yml) | [![sbomify](https://sbomify.com/assets/images/logo/badge.svg)](https://library.sbomify.com/product/osv-scanner/) | -| [Syft](https://github.com/anchore/syft) | Syft | Lockfile | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-syft.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-syft.yml) | [![sbomify](https://sbomify.com/assets/images/logo/badge.svg)](https://library.sbomify.com/product/syft/) | -| [Trivy](https://github.com/aquasecurity/trivy) | Trivy | GitHub Release | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-trivy.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-trivy.yml) | [![sbomify](https://sbomify.com/assets/images/logo/badge.svg)](https://library.sbomify.com/product/trivy/) | +Each SBOM is discoverable via the [Transparency Exchange API (TEA)](https://tc54.org/tea/) using the TEI identifiers listed below. + +### Operating Systems + +| Project | Source | TEI | Job | +|---------|--------|-----|-----| +| [Alpine Linux](https://hub.docker.com/_/alpine) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/alpine` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-alpine.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-alpine.yml) | +| [Amazon Linux](https://hub.docker.com/_/amazonlinux) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/amazonlinux` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-amazonlinux.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-amazonlinux.yml) | +| [Debian](https://hub.docker.com/_/debian) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/debian` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-debian.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-debian.yml) | +| [Fedora](https://hub.docker.com/_/fedora) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/fedora` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-fedora.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-fedora.yml) | +| [Oracle Linux](https://hub.docker.com/_/oraclelinux) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/oraclelinux` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-oraclelinux.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-oraclelinux.yml) | +| [Rocky Linux](https://hub.docker.com/_/rockylinux) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/rockylinux` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-rockylinux.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-rockylinux.yml) | +| [Ubuntu](https://hub.docker.com/_/ubuntu) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/ubuntu` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-ubuntu.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-ubuntu.yml) | + +### Languages & Runtimes + +| Project | Source | TEI | Job | +|---------|--------|-----|-----| +| [Eclipse Temurin](https://hub.docker.com/_/eclipse-temurin) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/eclipse-temurin` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-eclipse-temurin.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-eclipse-temurin.yml) | +| [Elixir](https://hub.docker.com/_/elixir) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/elixir` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-elixir.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-elixir.yml) | +| [Erlang](https://hub.docker.com/_/erlang) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/erlang` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-erlang.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-erlang.yml) | +| [Go](https://hub.docker.com/_/golang) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/golang` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-golang.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-golang.yml) | +| [Haskell (GHC)](https://hub.docker.com/_/haskell) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/haskell` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-haskell.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-haskell.yml) | +| [Julia](https://hub.docker.com/_/julia) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/julia` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-julia.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-julia.yml) | +| [Node.js](https://hub.docker.com/_/node) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/node` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-node.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-node.yml) | +| [Perl](https://hub.docker.com/_/perl) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/perl` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-perl.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-perl.yml) | +| [PHP](https://hub.docker.com/_/php) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/php` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-php.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-php.yml) | +| [Python](https://hub.docker.com/_/python) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/python` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-python.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-python.yml) | +| [R](https://hub.docker.com/_/r-base) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/r-base` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-r-base.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-r-base.yml) | +| [Ruby](https://hub.docker.com/_/ruby) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/ruby` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-ruby.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-ruby.yml) | +| [Rust](https://hub.docker.com/_/rust) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/rust` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-rust.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-rust.yml) | +| [Swift](https://hub.docker.com/_/swift) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/swift` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-swift.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-swift.yml) | + +### Databases + +| Project | Source | TEI | Job | +|---------|--------|-----|-----| +| [Apache Cassandra](https://hub.docker.com/_/cassandra) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/cassandra` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-cassandra.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-cassandra.yml) | +| [InfluxDB](https://hub.docker.com/_/influxdb) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/influxdb` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-influxdb.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-influxdb.yml) | +| [MariaDB](https://hub.docker.com/_/mariadb) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/mariadb` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-mariadb.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-mariadb.yml) | +| [Memcached](https://hub.docker.com/_/memcached) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/memcached` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-memcached.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-memcached.yml) | +| [MongoDB](https://hub.docker.com/_/mongo) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/mongo` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-mongo.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-mongo.yml) | +| [Mongo Express](https://hub.docker.com/_/mongo-express) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/mongo-express` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-mongo-express.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-mongo-express.yml) | +| [MySQL](https://hub.docker.com/_/mysql) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/mysql` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-mysql.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-mysql.yml) | +| [Neo4j](https://hub.docker.com/_/neo4j) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/neo4j` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-neo4j.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-neo4j.yml) | +| [PostgreSQL](https://hub.docker.com/_/postgres) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/postgres` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-postgres.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-postgres.yml) | +| [Redis](https://hub.docker.com/_/redis) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/redis` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-redis.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-redis.yml) | +| [Apache Solr](https://hub.docker.com/_/solr) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/solr` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-solr.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-solr.yml) | + +### Web & Application Servers + +| Project | Source | TEI | Job | +|---------|--------|-----|-----| +| [Apache HTTP Server](https://hub.docker.com/_/httpd) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/httpd` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-httpd.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-httpd.yml) | +| [Apache Tomcat](https://hub.docker.com/_/tomcat) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/tomcat` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-tomcat.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-tomcat.yml) | +| [Caddy](https://github.com/caddyserver/caddy) | GitHub Release | `urn:tei:purl:library.sbomify.com:pkg:github/caddyserver/caddy` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-caddy.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-caddy.yml) | +| [HAProxy](https://hub.docker.com/_/haproxy) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/haproxy` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-haproxy.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-haproxy.yml) | +| [Kong Gateway](https://hub.docker.com/_/kong) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/kong` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-kong.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-kong.yml) | +| [Nginx](https://images.chainguard.dev/directory/image/nginx/overview) | Chainguard | `urn:tei:purl:library.sbomify.com:pkg:oci/cgr.dev/chainguard/nginx` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-nginx.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-nginx.yml) | +| [Traefik](https://hub.docker.com/_/traefik) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/traefik` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-traefik.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-traefik.yml) | + +### Applications & Platforms + +| Project | Source | TEI | Job | +|---------|--------|-----|-----| +| [Drupal](https://hub.docker.com/_/drupal) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/drupal` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-drupal.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-drupal.yml) | +| [Ghost](https://hub.docker.com/_/ghost) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/ghost` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-ghost.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-ghost.yml) | +| [Keycloak](https://github.com/keycloak/keycloak) | Lockfile | `urn:tei:purl:library.sbomify.com:pkg:github/keycloak/keycloak` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-keycloak.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-keycloak.yml) | +| [Keycloak JS](https://github.com/keycloak/keycloak) | Lockfile | `urn:tei:purl:library.sbomify.com:pkg:github/keycloak/keycloak` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-keycloak-js.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-keycloak-js.yml) | +| [SonarQube](https://hub.docker.com/_/sonarqube) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/sonarqube` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-sonarqube.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-sonarqube.yml) | +| [WordPress](https://hub.docker.com/_/wordpress) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/wordpress` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-wordpress.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-wordpress.yml) | + +### Build Tools + +| Project | Source | TEI | Job | +|---------|--------|-----|-----| +| [Gradle](https://hub.docker.com/_/gradle) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/gradle` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-gradle.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-gradle.yml) | +| [Apache Maven](https://hub.docker.com/_/maven) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/maven` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-maven.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-maven.yml) | + +### Infrastructure & Messaging + +| Project | Source | TEI | Job | +|---------|--------|-----|-----| +| [Bash](https://hub.docker.com/_/bash) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/bash` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-bash.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-bash.yml) | +| [Docker Registry](https://hub.docker.com/_/registry) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/registry` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-registry.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-registry.yml) | +| [Eclipse Mosquitto](https://hub.docker.com/_/eclipse-mosquitto) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/eclipse-mosquitto` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-eclipse-mosquitto.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-eclipse-mosquitto.yml) | +| [RabbitMQ](https://hub.docker.com/_/rabbitmq) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/rabbitmq` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-rabbitmq.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-rabbitmq.yml) | +| [Telegraf](https://hub.docker.com/_/telegraf) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/telegraf` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-telegraf.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-telegraf.yml) | +| [Apache ZooKeeper](https://hub.docker.com/_/zookeeper) | Docker | `urn:tei:purl:library.sbomify.com:pkg:docker/library/zookeeper` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-zookeeper.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-zookeeper.yml) | + +### Security & SBOM Tools + +| Project | Source | TEI | Job | +|---------|--------|-----|-----| +| [Dependency Track](https://github.com/DependencyTrack/dependency-track) | GitHub Release | `urn:tei:purl:library.sbomify.com:pkg:github/DependencyTrack/dependency-track` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-dependency-track.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-dependency-track.yml) | +| [Dependency Track Frontend](https://github.com/DependencyTrack/frontend) | GitHub Release | `urn:tei:purl:library.sbomify.com:pkg:github/DependencyTrack/frontend` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-dependency-track-frontend.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-dependency-track-frontend.yml) | +| [OSV Scanner](https://github.com/google/osv-scanner) | Lockfile | `urn:tei:purl:library.sbomify.com:pkg:github/google/osv-scanner` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-osv-scanner.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-osv-scanner.yml) | +| [Syft](https://github.com/anchore/syft) | Lockfile | `urn:tei:purl:library.sbomify.com:pkg:github/anchore/syft` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-syft.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-syft.yml) | +| [Trivy](https://github.com/aquasecurity/trivy) | GitHub Release | `urn:tei:purl:library.sbomify.com:pkg:github/aquasecurity/trivy` | [![SBOM](https://github.com/sbomify/library/actions/workflows/sbom-trivy.yml/badge.svg)](https://github.com/sbomify/library/actions/workflows/sbom-trivy.yml) | ## Directory Structure From 75ab2863b724bdfdbf2c964232aa9b9234942b79 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Wed, 4 Mar 2026 14:44:52 +0000 Subject: [PATCH 4/7] Add daily 6AM UTC schedule to CI workflow Co-Authored-By: Claude Opus 4.6 --- .github/workflows/ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e53bf6e..042eb1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ name: CI on: + schedule: + - cron: '0 6 * * *' # Daily at 6:00 AM UTC + pull_request: paths: - 'apps/**' From df0a523404004c1a65414dec6a84f6d42e16a3dc Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Wed, 4 Mar 2026 15:03:02 +0000 Subject: [PATCH 5/7] Cache tool binaries (yq, crane, cosign) in CI pipeline Avoids re-downloading ~50MB of tool binaries on every job run. Cache key is versioned so bumping tool versions invalidates it. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sbom-builder.yml | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/.github/workflows/sbom-builder.yml b/.github/workflows/sbom-builder.yml index 4365517..142216d 100644 --- a/.github/workflows/sbom-builder.yml +++ b/.github/workflows/sbom-builder.yml @@ -32,7 +32,18 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Cache tools + id: tool-cache + uses: actions/cache@v4 + with: + path: | + /usr/local/bin/yq + /usr/local/bin/crane + /usr/local/bin/cosign + key: tools-yq-${{ env.YQ_VERSION }}-crane-${{ env.CRANE_VERSION }}-cosign-v2.2.2 + - name: Install tools + if: steps.tool-cache.outputs.cache-hit != 'true' run: | # yq sudo curl -fsSL -o /usr/local/bin/yq \ @@ -45,14 +56,9 @@ jobs: | tar -xzf - sudo mv crane /usr/local/bin/ - # Source-specific tools - SOURCE=$(yq -r '.source.type' "apps/${{ inputs.app }}/config.yaml") - case "$SOURCE" in - chainguard) - curl -sLO "https://github.com/sigstore/cosign/releases/download/v2.2.2/cosign-linux-amd64" - sudo install cosign-linux-amd64 /usr/local/bin/cosign - ;; - esac + # cosign (needed for chainguard sources) + curl -sLO "https://github.com/sigstore/cosign/releases/download/v2.2.2/cosign-linux-amd64" + sudo install cosign-linux-amd64 /usr/local/bin/cosign - name: Read config id: config From 8deb29585bd69b1d2a94f22df4e31c64348c6f3e Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Wed, 4 Mar 2026 15:05:19 +0000 Subject: [PATCH 6/7] Cache fetched SBOMs and verify tool checksums - Cache sbom.json/lockfile per app+version to skip Docker Hub and Chainguard pulls when the version hasn't changed - Verify SHA-256 checksums for all downloaded tool binaries (yq, crane, cosign) before installing Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sbom-builder.yml | 44 ++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/.github/workflows/sbom-builder.yml b/.github/workflows/sbom-builder.yml index 142216d..0e7ebff 100644 --- a/.github/workflows/sbom-builder.yml +++ b/.github/workflows/sbom-builder.yml @@ -46,19 +46,24 @@ jobs: if: steps.tool-cache.outputs.cache-hit != 'true' run: | # yq - sudo curl -fsSL -o /usr/local/bin/yq \ + curl -fsSL -o /tmp/yq \ "https://github.com/mikefarah/yq/releases/download/${{ env.YQ_VERSION }}/yq_linux_amd64" - sudo chmod +x /usr/local/bin/yq + echo "0d6aaf1cf44a8d18fbc7ed0ef14f735a8df8d2e314c4cc0f0242d35c0a440c95 /tmp/yq" | sha256sum -c - + sudo install /tmp/yq /usr/local/bin/yq # crane CRANE_URL="https://github.com/google/go-containerregistry/releases/download" - curl -fsSL "${CRANE_URL}/${{ env.CRANE_VERSION }}/go-containerregistry_Linux_x86_64.tar.gz" \ - | tar -xzf - - sudo mv crane /usr/local/bin/ + curl -fsSL -o /tmp/crane.tar.gz \ + "${CRANE_URL}/${{ env.CRANE_VERSION }}/go-containerregistry_Linux_x86_64.tar.gz" + echo "1b4d3ee1e214776bd74b88741ccf1b070e8ed5660056f05af632a1a399fe21c6 /tmp/crane.tar.gz" | sha256sum -c - + tar -xzf /tmp/crane.tar.gz -C /tmp crane + sudo install /tmp/crane /usr/local/bin/crane # cosign (needed for chainguard sources) - curl -sLO "https://github.com/sigstore/cosign/releases/download/v2.2.2/cosign-linux-amd64" - sudo install cosign-linux-amd64 /usr/local/bin/cosign + curl -fsSL -o /tmp/cosign \ + "https://github.com/sigstore/cosign/releases/download/v2.2.2/cosign-linux-amd64" + echo "121ba0031827c090364894688049651d3a0a82a87235c469322a202aa2944211 /tmp/cosign" | sha256sum -c - + sudo install /tmp/cosign /usr/local/bin/cosign - name: Read config id: config @@ -90,8 +95,28 @@ jobs: echo "product_release=[\"${PRODUCT_ID}:${VERSION}\"]" >> $GITHUB_OUTPUT fi + - name: Cache fetched SBOM + id: sbom-cache + if: steps.config.outputs.source_type != 'lockfile' + uses: actions/cache@v4 + with: + path: sbom.json + key: sbom-${{ inputs.app }}-${{ steps.config.outputs.version }} + + - name: Cache fetched lockfile + id: lockfile-cache + if: steps.config.outputs.source_type == 'lockfile' + uses: actions/cache@v4 + with: + path: | + ${{ steps.config.outputs.lockfile_path }} + repo/ + key: lockfile-${{ inputs.app }}-${{ steps.config.outputs.version }} + - name: Cache Maven dependencies - if: steps.config.outputs.source_type == 'lockfile' && steps.config.outputs.clone == 'true' + if: >- + steps.config.outputs.source_type == 'lockfile' && steps.config.outputs.clone == 'true' + && steps.lockfile-cache.outputs.cache-hit != 'true' uses: actions/cache@v4 with: path: ~/.m2/repository @@ -101,6 +126,9 @@ jobs: maven- - name: Fetch SBOM or lockfile + if: >- + (steps.sbom-cache.outputs.cache-hit != 'true' && steps.config.outputs.source_type != 'lockfile') + || (steps.lockfile-cache.outputs.cache-hit != 'true' && steps.config.outputs.source_type == 'lockfile') run: ./scripts/fetch-sbom.sh "${{ inputs.app }}" - name: Upload input artifact From 7a2dd3e2ead4163d3e280ccd20353bc1701f8f1f Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Wed, 4 Mar 2026 15:14:32 +0000 Subject: [PATCH 7/7] Verify tool checksums from GitHub release artifacts Download checksum files from each tool's GitHub release and verify against them, instead of hardcoding hashes. This means version bumps only require changing the version env vars. - yq: verify against checksums-bsd from release - crane: verify against checksums.txt from release - cosign: verify against cosign_checksums.txt from release - Move COSIGN_VERSION to workflow-level env var Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sbom-builder.yml | 38 +++++++++++++++++++----------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/.github/workflows/sbom-builder.yml b/.github/workflows/sbom-builder.yml index 0e7ebff..5d4f0ee 100644 --- a/.github/workflows/sbom-builder.yml +++ b/.github/workflows/sbom-builder.yml @@ -20,6 +20,7 @@ on: env: YQ_VERSION: "v4.40.5" CRANE_VERSION: "v0.17.0" + COSIGN_VERSION: "v2.2.2" jobs: build: @@ -40,29 +41,38 @@ jobs: /usr/local/bin/yq /usr/local/bin/crane /usr/local/bin/cosign - key: tools-yq-${{ env.YQ_VERSION }}-crane-${{ env.CRANE_VERSION }}-cosign-v2.2.2 + key: tools-yq-${{ env.YQ_VERSION }}-crane-${{ env.CRANE_VERSION }}-cosign-${{ env.COSIGN_VERSION }} - name: Install tools if: steps.tool-cache.outputs.cache-hit != 'true' run: | - # yq - curl -fsSL -o /tmp/yq \ - "https://github.com/mikefarah/yq/releases/download/${{ env.YQ_VERSION }}/yq_linux_amd64" - echo "0d6aaf1cf44a8d18fbc7ed0ef14f735a8df8d2e314c4cc0f0242d35c0a440c95 /tmp/yq" | sha256sum -c - + # yq — download binary and checksums from release + YQ_BASE="https://github.com/mikefarah/yq/releases/download/${{ env.YQ_VERSION }}" + curl -fsSL -o /tmp/yq "${YQ_BASE}/yq_linux_amd64" + curl -fsSL -o /tmp/yq_checksums "${YQ_BASE}/checksums-bsd" + expected=$(grep 'SHA256 (yq_linux_amd64)' /tmp/yq_checksums | awk '{print $NF}') + actual=$(sha256sum /tmp/yq | awk '{print $1}') + echo "yq: expected=$expected actual=$actual" + [[ "$expected" == "$actual" ]] || { echo "yq checksum mismatch"; exit 1; } sudo install /tmp/yq /usr/local/bin/yq - # crane - CRANE_URL="https://github.com/google/go-containerregistry/releases/download" - curl -fsSL -o /tmp/crane.tar.gz \ - "${CRANE_URL}/${{ env.CRANE_VERSION }}/go-containerregistry_Linux_x86_64.tar.gz" - echo "1b4d3ee1e214776bd74b88741ccf1b070e8ed5660056f05af632a1a399fe21c6 /tmp/crane.tar.gz" | sha256sum -c - + # crane — download tarball and checksums from release + CRANE_BASE="https://github.com/google/go-containerregistry/releases/download/${{ env.CRANE_VERSION }}" + curl -fsSL -o /tmp/crane.tar.gz "${CRANE_BASE}/go-containerregistry_Linux_x86_64.tar.gz" + curl -fsSL -o /tmp/crane_checksums.txt "${CRANE_BASE}/checksums.txt" + grep go-containerregistry_Linux_x86_64.tar.gz /tmp/crane_checksums.txt \ + | sed 's|go-containerregistry_Linux_x86_64.tar.gz|/tmp/crane.tar.gz|' \ + | sha256sum -c - tar -xzf /tmp/crane.tar.gz -C /tmp crane sudo install /tmp/crane /usr/local/bin/crane - # cosign (needed for chainguard sources) - curl -fsSL -o /tmp/cosign \ - "https://github.com/sigstore/cosign/releases/download/v2.2.2/cosign-linux-amd64" - echo "121ba0031827c090364894688049651d3a0a82a87235c469322a202aa2944211 /tmp/cosign" | sha256sum -c - + # cosign — download binary and checksums from release + COSIGN_BASE="https://github.com/sigstore/cosign/releases/download/${{ env.COSIGN_VERSION }}" + curl -fsSL -o /tmp/cosign "${COSIGN_BASE}/cosign-linux-amd64" + curl -fsSL -o /tmp/cosign_checksums.txt "${COSIGN_BASE}/cosign_checksums.txt" + grep 'cosign-linux-amd64$' /tmp/cosign_checksums.txt \ + | sed 's|cosign-linux-amd64|/tmp/cosign|' \ + | sha256sum -c - sudo install /tmp/cosign /usr/local/bin/cosign - name: Read config