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/**' 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-amazonlinux.yml b/.github/workflows/sbom-amazonlinux.yml new file mode 100644 index 0000000..faafcf8 --- /dev/null +++ b/.github/workflows/sbom-amazonlinux.yml @@ -0,0 +1,29 @@ +name: "SBOM: amazonlinux" + +on: + push: + branches: + - master + paths: + - 'apps/amazonlinux/config.yaml' + - '.github/workflows/sbom-amazonlinux.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: amazonlinux + 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..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: @@ -32,27 +33,47 @@ 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-${{ env.COSIGN_VERSION }} + - name: Install tools + if: steps.tool-cache.outputs.cache-hit != 'true' run: | - # yq - 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 - - # 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/ - - # 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 + # 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 — 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 — 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 id: config @@ -84,8 +105,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 @@ -95,6 +136,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 @@ -111,9 +155,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 +168,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 +182,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 +246,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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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-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/.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/README.md b/README.md index d254530..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 @@ -42,6 +131,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 +324,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 +332,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/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/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/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..8d44cb4 --- /dev/null +++ b/apps/eclipse-mosquitto/config.yaml @@ -0,0 +1,14 @@ +name: eclipse-mosquitto +version: "2.1.2-alpine" +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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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/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 "$@" 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)