From 62de5ed6ed6a5398afb968bf3006126e84e98d97 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Fri, 2 Jan 2026 20:55:14 +0000 Subject: [PATCH 1/8] feat: add Keycloak and delegate SBOM generation to sbomify action Add Keycloak v26.4.7 using lockfile source (pom.xml from GitHub). Refactor lockfile handling to delegate SBOM generation to the sbomify GitHub Action instead of running cdxgen/syft locally: - Remove cdxgen installation from workflow for lockfile sources - Update sbom-builder.yml to pass LOCK_FILE to sbomify action - Simplify lockfile-generator.sh to only download lockfiles - Add separate workflow steps for SBOM vs lockfile sources New files: - apps/keycloak/config.yaml - .github/workflows/sbom-keycloak.yml Updated documentation to reflect that SBOM generation from lockfiles is now handled by the sbomify GitHub Action. --- .github/workflows/sbom-builder.yml | 44 +++++++++++++++++++++++---- .github/workflows/sbom-keycloak.yml | 35 +++++++++++++++++++++ README.md | 19 ++++++------ apps/keycloak/config.yaml | 24 +++++++++++++++ scripts/fetch-sbom.sh | 15 ++++++++- scripts/sources/chainguard.sh | 2 ++ scripts/sources/docker-attestation.sh | 2 ++ scripts/sources/github-release.sh | 2 ++ scripts/sources/lockfile-generator.sh | 35 +++++---------------- 9 files changed, 134 insertions(+), 44 deletions(-) create mode 100644 .github/workflows/sbom-keycloak.yml create mode 100644 apps/keycloak/config.yaml diff --git a/.github/workflows/sbom-builder.yml b/.github/workflows/sbom-builder.yml index b71618f..9a5ebc0 100644 --- a/.github/workflows/sbom-builder.yml +++ b/.github/workflows/sbom-builder.yml @@ -48,7 +48,6 @@ jobs: # Source-specific tools SOURCE=$(yq -r '.source.type' "apps/${{ inputs.app }}/config.yaml") case "$SOURCE" in - lockfile) npm install -g @cyclonedx/cdxgen ;; 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 @@ -64,25 +63,43 @@ jobs: echo "version=${VERSION}" >> $GITHUB_OUTPUT echo "component_id=$(yq -r '.sbomify.component_id // ""' "$CONFIG")" >> $GITHUB_OUTPUT echo "component_name=$(yq -r '.sbomify.component_name // ""' "$CONFIG")" >> $GITHUB_OUTPUT + echo "source_type=$(yq -r '.source.type // ""' "$CONFIG")" >> $GITHUB_OUTPUT + LOCKFILE_PATH=$(yq -r '.source.lockfile // ""' "$CONFIG") + if [[ -n "$LOCKFILE_PATH" ]]; then + echo "lockfile=$(basename "$LOCKFILE_PATH")" >> $GITHUB_OUTPUT + else + echo "lockfile=" >> $GITHUB_OUTPUT + fi PRODUCT_ID=$(yq -r '.sbomify.product_id // ""' "$CONFIG") if [[ -n "$PRODUCT_ID" && -n "$VERSION" ]]; then echo "product_release=[\"${PRODUCT_ID}:${VERSION}\"]" >> $GITHUB_OUTPUT fi - - name: Fetch SBOM + - name: Fetch SBOM or lockfile run: | ./scripts/fetch-sbom.sh "${{ inputs.app }}" - ls -la sbom.json + if [[ "${{ steps.config.outputs.source_type }}" == "lockfile" ]]; then + ls -la "${{ steps.config.outputs.lockfile }}" + else + ls -la sbom.json + fi - name: Upload input artifact - if: always() + if: always() && steps.config.outputs.source_type != 'lockfile' uses: actions/upload-artifact@v4 with: name: sbom-${{ inputs.app }}-${{ steps.config.outputs.version }} path: sbom.json - - name: Upload to sbomify - if: steps.config.outputs.component_id != '' + - name: Upload lockfile artifact + if: always() && steps.config.outputs.source_type == 'lockfile' + uses: actions/upload-artifact@v4 + with: + name: lockfile-${{ inputs.app }}-${{ steps.config.outputs.version }} + path: ${{ steps.config.outputs.lockfile }} + + - name: Upload to sbomify (SBOM) + if: steps.config.outputs.component_id != '' && steps.config.outputs.source_type != 'lockfile' uses: sbomify/github-action@master env: TOKEN: ${{ secrets.SBOMIFY_TOKEN }} @@ -96,6 +113,21 @@ jobs: UPLOAD: ${{ !inputs.dry_run }} PRODUCT_RELEASE: ${{ steps.config.outputs.product_release }} + - name: Upload to sbomify (lockfile) + if: steps.config.outputs.component_id != '' && steps.config.outputs.source_type == 'lockfile' + uses: sbomify/github-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 }} + LOCK_FILE: ${{ steps.config.outputs.lockfile }} + OUTPUT_FILE: sbom-output.json + AUGMENT: true + ENRICH: true + UPLOAD: ${{ !inputs.dry_run }} + PRODUCT_RELEASE: ${{ steps.config.outputs.product_release }} + - name: Upload output artifact if: always() uses: actions/upload-artifact@v4 diff --git a/.github/workflows/sbom-keycloak.yml b/.github/workflows/sbom-keycloak.yml new file mode 100644 index 0000000..be312d6 --- /dev/null +++ b/.github/workflows/sbom-keycloak.yml @@ -0,0 +1,35 @@ +# Keycloak SBOM workflow +# +# Generates SBOM from the pom.xml lockfile of Keycloak + +name: "SBOM: keycloak" + +on: + push: + branches: + - master + paths: + - 'apps/keycloak/config.yaml' + - '.github/workflows/sbom-keycloak.yml' + + # Allow manual triggering + 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: keycloak + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + permissions: + id-token: write + contents: read + attestations: write + diff --git a/README.md b/README.md index e5560bd..011d01d 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This repository manages SBOM extraction from multiple sources: - **Docker OCI Attestations** - Extract SBOMs embedded in Docker images via BuildKit attestations - **Chainguard Images** - Download signed SBOM attestations from Chainguard images via cosign - **GitHub Releases** - Download SBOMs published as release assets -- **Lockfile Generation** - Generate SBOMs from project dependency lockfiles +- **Lockfile Sources** - Download lockfiles for SBOM generation by sbomify Each app has its own folder with version tracking. When you bump the `version` in `config.yaml`, only that app's SBOM is rebuilt and uploaded - not the entire repository. @@ -22,6 +22,7 @@ Each app has its own folder with version tracking. When you bump the `version` i | [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) | Keycloak | Lockfile | [![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/) | | [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/) | ## Directory Structure @@ -43,7 +44,7 @@ Each app has its own folder with version tracking. When you bump the `version` i │ └── sources/ │ ├── docker-attestation.sh # Docker extraction │ ├── github-release.sh # GitHub release download -│ └── lockfile-generator.sh # Lockfile-based generation +│ └── lockfile-generator.sh # Lockfile download └── README.md ``` @@ -183,9 +184,9 @@ source: tag_prefix: "v" ``` -#### Lockfile Generation +#### Lockfile Sources -Generate SBOMs from project lockfiles: +Download lockfiles for SBOM generation by the sbomify GitHub Action: ```yaml source: @@ -193,11 +194,10 @@ source: repo: "owner/repo" # GitHub repository (required) lockfile: "package-lock.json" # Path to lockfile (required) tag_prefix: "v" # Tag prefix - generator: "auto" # cdxgen | syft | auto - extra_files: # Additional files to download - - "package.json" ``` +Note: SBOM generation from lockfiles is handled automatically by the sbomify GitHub Action. + ## Local Development ### Prerequisites @@ -214,9 +214,8 @@ For Docker sources: For Chainguard sources: - **cosign** (from sigstore) -For lockfile generation: -- **cdxgen** (`npm install -g @cyclonedx/cdxgen`), or -- **syft** +For lockfile sources: +- No additional tools required (SBOM generation handled by sbomify GitHub Action) ### Running Locally diff --git a/apps/keycloak/config.yaml b/apps/keycloak/config.yaml new file mode 100644 index 0000000..d5c0ea0 --- /dev/null +++ b/apps/keycloak/config.yaml @@ -0,0 +1,24 @@ +# Keycloak SBOM Configuration +# +# Keycloak is an Open Source Identity and Access Management solution +# for modern Applications and Services. +# +# SBOM source: Lockfile (pom.xml from GitHub) +# https://github.com/keycloak/keycloak + +name: keycloak +version: "26.4.7" + +format: cyclonedx + +source: + type: lockfile + repo: "keycloak/keycloak" + lockfile: "pom.xml" + tag_prefix: "" + +sbomify: + component_id: "GYsnuXarUapb" + component_name: "Keycloak" + product_id: "DYmPnVgOSdyT" + diff --git a/scripts/fetch-sbom.sh b/scripts/fetch-sbom.sh index 7d002d0..18040e2 100755 --- a/scripts/fetch-sbom.sh +++ b/scripts/fetch-sbom.sh @@ -2,6 +2,7 @@ # fetch-sbom.sh - Fetch SBOM for an app # # Usage: ./fetch-sbom.sh +# shellcheck source-path=SCRIPTDIR set -euo pipefail @@ -37,7 +38,19 @@ main() { fi "$handler" "$app" - log_info "Done: sbom.json" + + # Log appropriate output based on source type + case "$source_type" in + lockfile) + local lockfile_path lockfile + lockfile_path=$(get_config "$app" ".source.lockfile") + lockfile=$(basename "$lockfile_path") + log_info "Done: $lockfile" + ;; + *) + log_info "Done: sbom.json" + ;; + esac } main "$@" diff --git a/scripts/sources/chainguard.sh b/scripts/sources/chainguard.sh index cb68e3a..671798c 100755 --- a/scripts/sources/chainguard.sh +++ b/scripts/sources/chainguard.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash # chainguard.sh - Download SBOM from Chainguard image via cosign +# shellcheck source-path=SCRIPTDIR + set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/sources/docker-attestation.sh b/scripts/sources/docker-attestation.sh index fb2bf28..f7497fc 100755 --- a/scripts/sources/docker-attestation.sh +++ b/scripts/sources/docker-attestation.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash # docker-attestation.sh - Extract SBOM from Docker image OCI attestation +# shellcheck source-path=SCRIPTDIR + set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/sources/github-release.sh b/scripts/sources/github-release.sh index 280df6f..5fa23fc 100755 --- a/scripts/sources/github-release.sh +++ b/scripts/sources/github-release.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash # github-release.sh - Download SBOM from GitHub release +# shellcheck source-path=SCRIPTDIR + set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" diff --git a/scripts/sources/lockfile-generator.sh b/scripts/sources/lockfile-generator.sh index 3c053ca..f71ab25 100755 --- a/scripts/sources/lockfile-generator.sh +++ b/scripts/sources/lockfile-generator.sh @@ -1,5 +1,10 @@ #!/usr/bin/env bash -# lockfile-generator.sh - Generate SBOM from lockfile +# lockfile-generator.sh - Download lockfile for SBOM generation +# +# This script only downloads the lockfile from GitHub. +# SBOM generation is handled by the sbomify GitHub Action. +# shellcheck source-path=SCRIPTDIR + set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" @@ -16,31 +21,7 @@ tag="${tag_prefix}${version}${tag_suffix}" url="https://raw.githubusercontent.com/${repo}/${tag}/${lockfile}" log_info "Downloading lockfile: $url" -work_dir=$(mktemp -d) -cleanup() { rm -rf "$work_dir"; } -trap cleanup EXIT - lockfile_name=$(basename "$lockfile") -curl -fsSL -o "${work_dir}/${lockfile_name}" "$url" - -# Try to get package.json for JS projects -case "$lockfile_name" in - package-lock.json|yarn.lock|pnpm-lock.yaml) - pkg_dir=$(dirname "$lockfile") - [[ "$pkg_dir" == "." ]] && pkg_dir="" - curl -fsSL -o "${work_dir}/package.json" \ - "https://raw.githubusercontent.com/${repo}/${tag}/${pkg_dir}package.json" 2>/dev/null || true - ;; -esac +curl -fsSL -o "${lockfile_name}" "$url" -# Generate with cdxgen or syft -if command -v cdxgen &> /dev/null; then - log_info "Generating with cdxgen..." - (cd "$work_dir" && cdxgen -o sbom.json .) - mv "${work_dir}/sbom.json" sbom.json -elif command -v syft &> /dev/null; then - log_info "Generating with syft..." - syft "dir:${work_dir}" -o cyclonedx-json=sbom.json -else - die "No SBOM generator found. Install cdxgen or syft." -fi +log_info "Downloaded: ${lockfile_name}" From 59287c792f43ea5daa905737d79365f826de5d38 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Fri, 2 Jan 2026 21:07:36 +0000 Subject: [PATCH 2/8] Adds clone option for complex lock file systems (like maven) --- .github/workflows/sbom-builder.yml | 26 ++++++++++++++++++++++--- README.md | 11 +++++++++++ apps/keycloak/config.yaml | 1 + scripts/fetch-sbom.sh | 16 +++++++++++---- scripts/sources/chainguard.sh | 2 ++ scripts/sources/docker-attestation.sh | 2 ++ scripts/sources/github-release.sh | 2 ++ scripts/sources/lockfile-generator.sh | 28 ++++++++++++++++++--------- 8 files changed, 72 insertions(+), 16 deletions(-) diff --git a/.github/workflows/sbom-builder.yml b/.github/workflows/sbom-builder.yml index 9a5ebc0..b65b660 100644 --- a/.github/workflows/sbom-builder.yml +++ b/.github/workflows/sbom-builder.yml @@ -64,11 +64,20 @@ jobs: echo "component_id=$(yq -r '.sbomify.component_id // ""' "$CONFIG")" >> $GITHUB_OUTPUT echo "component_name=$(yq -r '.sbomify.component_name // ""' "$CONFIG")" >> $GITHUB_OUTPUT echo "source_type=$(yq -r '.source.type // ""' "$CONFIG")" >> $GITHUB_OUTPUT + echo "clone=$(yq -r '.source.clone // "false"' "$CONFIG")" >> $GITHUB_OUTPUT LOCKFILE_PATH=$(yq -r '.source.lockfile // ""' "$CONFIG") if [[ -n "$LOCKFILE_PATH" ]]; then echo "lockfile=$(basename "$LOCKFILE_PATH")" >> $GITHUB_OUTPUT + # For cloned repos, lockfile is inside the repo/ directory + CLONE=$(yq -r '.source.clone // "false"' "$CONFIG") + if [[ "$CLONE" == "true" ]]; then + echo "lockfile_path=repo/$LOCKFILE_PATH" >> $GITHUB_OUTPUT + else + echo "lockfile_path=$(basename "$LOCKFILE_PATH")" >> $GITHUB_OUTPUT + fi else echo "lockfile=" >> $GITHUB_OUTPUT + echo "lockfile_path=" >> $GITHUB_OUTPUT fi PRODUCT_ID=$(yq -r '.sbomify.product_id // ""' "$CONFIG") if [[ -n "$PRODUCT_ID" && -n "$VERSION" ]]; then @@ -79,7 +88,11 @@ jobs: run: | ./scripts/fetch-sbom.sh "${{ inputs.app }}" if [[ "${{ steps.config.outputs.source_type }}" == "lockfile" ]]; then - ls -la "${{ steps.config.outputs.lockfile }}" + if [[ "${{ steps.config.outputs.clone }}" == "true" ]]; then + ls -la repo/ + else + ls -la "${{ steps.config.outputs.lockfile }}" + fi else ls -la sbom.json fi @@ -92,12 +105,19 @@ jobs: path: sbom.json - name: Upload lockfile artifact - if: always() && steps.config.outputs.source_type == 'lockfile' + if: always() && steps.config.outputs.source_type == 'lockfile' && steps.config.outputs.clone != 'true' uses: actions/upload-artifact@v4 with: name: lockfile-${{ inputs.app }}-${{ steps.config.outputs.version }} path: ${{ steps.config.outputs.lockfile }} + - name: Upload cloned repo artifact + if: always() && steps.config.outputs.source_type == 'lockfile' && steps.config.outputs.clone == 'true' + uses: actions/upload-artifact@v4 + with: + name: repo-${{ inputs.app }}-${{ steps.config.outputs.version }} + path: repo/ + - name: Upload to sbomify (SBOM) if: steps.config.outputs.component_id != '' && steps.config.outputs.source_type != 'lockfile' uses: sbomify/github-action@master @@ -121,7 +141,7 @@ jobs: COMPONENT_ID: ${{ steps.config.outputs.component_id }} COMPONENT_NAME: ${{ steps.config.outputs.component_name }} COMPONENT_VERSION: ${{ steps.config.outputs.version }} - LOCK_FILE: ${{ steps.config.outputs.lockfile }} + LOCK_FILE: ${{ steps.config.outputs.lockfile_path }} OUTPUT_FILE: sbom-output.json AUGMENT: true ENRICH: true diff --git a/README.md b/README.md index 011d01d..4904f7f 100644 --- a/README.md +++ b/README.md @@ -194,6 +194,17 @@ source: repo: "owner/repo" # GitHub repository (required) lockfile: "package-lock.json" # Path to lockfile (required) tag_prefix: "v" # Tag prefix + clone: false # Shallow clone repo instead of downloading lockfile +``` + +For projects with complex dependency structures (e.g., Maven multi-module projects), set `clone: true` to perform a shallow clone of the entire repository: + +```yaml +source: + type: lockfile + repo: "keycloak/keycloak" + lockfile: "pom.xml" + clone: true # Clone repo for full dependency resolution ``` Note: SBOM generation from lockfiles is handled automatically by the sbomify GitHub Action. diff --git a/apps/keycloak/config.yaml b/apps/keycloak/config.yaml index d5c0ea0..6459f46 100644 --- a/apps/keycloak/config.yaml +++ b/apps/keycloak/config.yaml @@ -16,6 +16,7 @@ source: repo: "keycloak/keycloak" lockfile: "pom.xml" tag_prefix: "" + clone: true sbomify: component_id: "GYsnuXarUapb" diff --git a/scripts/fetch-sbom.sh b/scripts/fetch-sbom.sh index 18040e2..a88f4c4 100755 --- a/scripts/fetch-sbom.sh +++ b/scripts/fetch-sbom.sh @@ -3,12 +3,14 @@ # # Usage: ./fetch-sbom.sh # shellcheck source-path=SCRIPTDIR +# shellcheck source=lib/common.sh set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SOURCES_DIR="${SCRIPT_DIR}/sources" +# shellcheck source=lib/common.sh source "${SCRIPT_DIR}/lib/common.sh" main() { @@ -42,10 +44,16 @@ main() { # Log appropriate output based on source type case "$source_type" in lockfile) - local lockfile_path lockfile - lockfile_path=$(get_config "$app" ".source.lockfile") - lockfile=$(basename "$lockfile_path") - log_info "Done: $lockfile" + local clone + clone=$(get_config "$app" ".source.clone" "false") + if [[ "$clone" == "true" ]]; then + log_info "Done: repo/" + else + local lockfile_path lockfile + lockfile_path=$(get_config "$app" ".source.lockfile") + lockfile=$(basename "$lockfile_path") + log_info "Done: $lockfile" + fi ;; *) log_info "Done: sbom.json" diff --git a/scripts/sources/chainguard.sh b/scripts/sources/chainguard.sh index 671798c..0d30a93 100755 --- a/scripts/sources/chainguard.sh +++ b/scripts/sources/chainguard.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash # chainguard.sh - Download SBOM from Chainguard image via cosign # 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" app="$1" diff --git a/scripts/sources/docker-attestation.sh b/scripts/sources/docker-attestation.sh index f7497fc..0344171 100755 --- a/scripts/sources/docker-attestation.sh +++ b/scripts/sources/docker-attestation.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash # docker-attestation.sh - Extract SBOM from Docker image OCI attestation # 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" app="$1" diff --git a/scripts/sources/github-release.sh b/scripts/sources/github-release.sh index 5fa23fc..8467b58 100755 --- a/scripts/sources/github-release.sh +++ b/scripts/sources/github-release.sh @@ -1,10 +1,12 @@ #!/usr/bin/env bash # github-release.sh - Download SBOM from GitHub release # 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" app="$1" diff --git a/scripts/sources/lockfile-generator.sh b/scripts/sources/lockfile-generator.sh index f71ab25..2aea2ff 100755 --- a/scripts/sources/lockfile-generator.sh +++ b/scripts/sources/lockfile-generator.sh @@ -1,13 +1,15 @@ #!/usr/bin/env bash -# lockfile-generator.sh - Download lockfile for SBOM generation +# lockfile-generator.sh - Download lockfile or clone repo for SBOM generation # -# This script only downloads the lockfile from GitHub. +# This script downloads lockfiles or clones repos from GitHub. # SBOM generation is handled by the sbomify GitHub Action. # 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" app="$1" @@ -16,12 +18,20 @@ repo=$(get_config "$app" ".source.repo") lockfile=$(get_config "$app" ".source.lockfile") tag_prefix=$(get_config "$app" ".source.tag_prefix" "") tag_suffix=$(get_config "$app" ".source.tag_suffix" "") +clone=$(get_config "$app" ".source.clone" "false") tag="${tag_prefix}${version}${tag_suffix}" -url="https://raw.githubusercontent.com/${repo}/${tag}/${lockfile}" -log_info "Downloading lockfile: $url" - -lockfile_name=$(basename "$lockfile") -curl -fsSL -o "${lockfile_name}" "$url" - -log_info "Downloaded: ${lockfile_name}" +if [[ "$clone" == "true" ]]; then + # Shallow clone the repository + repo_url="https://github.com/${repo}.git" + log_info "Shallow cloning: $repo_url (tag: $tag)" + git clone --depth 1 --branch "$tag" "$repo_url" repo + log_info "Cloned: repo/" +else + # Download just the lockfile + url="https://raw.githubusercontent.com/${repo}/${tag}/${lockfile}" + log_info "Downloading lockfile: $url" + lockfile_name=$(basename "$lockfile") + curl -fsSL -o "${lockfile_name}" "$url" + log_info "Downloaded: ${lockfile_name}" +fi From 846867d9127a9f12068d975c3e4767868df85214 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Sat, 3 Jan 2026 20:51:10 +0000 Subject: [PATCH 3/8] Separate out Keycloak frontend --- .github/workflows/sbom-keycloak-js.yml | 33 ++++++++++++++++++++++++++ README.md | 3 ++- apps/keycloak-js/config.yaml | 27 +++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/sbom-keycloak-js.yml create mode 100644 apps/keycloak-js/config.yaml diff --git a/.github/workflows/sbom-keycloak-js.yml b/.github/workflows/sbom-keycloak-js.yml new file mode 100644 index 0000000..547e4af --- /dev/null +++ b/.github/workflows/sbom-keycloak-js.yml @@ -0,0 +1,33 @@ +# SBOM workflow for Keycloak JS +# +# Triggers when keycloak-js version or config is updated. +# Generates SBOM from pnpm-lock.yaml lockfile. +# +# https://github.com/keycloak/keycloak + +name: "SBOM: keycloak-js" + +on: + push: + branches: + - master + paths: + - 'apps/keycloak-js/config.yaml' + - '.github/workflows/sbom-keycloak-js.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: keycloak-js + dry_run: ${{ github.event.inputs.dry_run == 'true' }} + secrets: inherit + diff --git a/README.md b/README.md index 4904f7f..e218352 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ Each app has its own folder with version tracking. When you bump the `version` i | [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) | Keycloak | Lockfile | [![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) | 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/) | ## Directory Structure diff --git a/apps/keycloak-js/config.yaml b/apps/keycloak-js/config.yaml new file mode 100644 index 0000000..07ee802 --- /dev/null +++ b/apps/keycloak-js/config.yaml @@ -0,0 +1,27 @@ +# Keycloak JS SBOM Configuration +# +# Keycloak is an open source identity and access management solution. +# This config is for the JavaScript/frontend component. +# +# SBOM source: Generated from pnpm-lock.yaml lockfile +# https://github.com/keycloak/keycloak + +name: keycloak-js +version: "26.4.7" + +format: cyclonedx + +source: + type: lockfile + repo: "keycloak/keycloak" + lockfile: "js/pnpm-lock.yaml" + tag_prefix: "" + clone: true + extra_files: + - "js/package.json" + +sbomify: + component_id: "SwIXpcPzBedP" + component_name: "Keycloak JS" + product_id: "DYmPnVgOSdyT" + From 84a33ee32c94a9f702ef693a7a7e2f7b0e9d0000 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Mon, 5 Jan 2026 12:04:27 +0000 Subject: [PATCH 4/8] feat(keycloak): add post-clone commands and Maven caching Overhaul keycloak SBOM generation to properly resolve Maven dependencies: - Add post_clone_commands support to lockfile source type, allowing arbitrary commands to run after cloning (e.g., Maven builds) - Add get_config_array helper to common.sh for reading YAML arrays - Add Maven dependency caching (~/.m2/repository) to workflow for faster subsequent builds - Update keycloak config to: - Use quarkus/runtime/pom.xml as the lockfile target - Run Maven install to resolve dependencies before SBOM generation The Maven build uses -DskipTestsuite -DskipExamples -DskipTests flags to minimize build time while still resolving the full dependency tree. --- .github/workflows/sbom-builder.yml | 10 +++++ .github/workflows/sbom-caddy.yml | 1 + .github/workflows/sbom-osv-scanner.yml | 1 + apps/caddy/config.yaml | 1 + apps/keycloak/config.yaml | 4 +- apps/osv-scanner/config.yaml | 1 + scripts/lib/common.sh | 57 ++++++++++++++++---------- scripts/sources/lockfile-generator.sh | 8 ++++ 8 files changed, 61 insertions(+), 22 deletions(-) diff --git a/.github/workflows/sbom-builder.yml b/.github/workflows/sbom-builder.yml index b65b660..15dfecd 100644 --- a/.github/workflows/sbom-builder.yml +++ b/.github/workflows/sbom-builder.yml @@ -84,6 +84,16 @@ jobs: echo "product_release=[\"${PRODUCT_ID}:${VERSION}\"]" >> $GITHUB_OUTPUT fi + - name: Cache Maven dependencies + if: steps.config.outputs.source_type == 'lockfile' && steps.config.outputs.clone == 'true' + uses: actions/cache@v4 + with: + path: ~/.m2/repository + key: maven-${{ inputs.app }}-${{ steps.config.outputs.version }} + restore-keys: | + maven-${{ inputs.app }}- + maven- + - name: Fetch SBOM or lockfile run: | ./scripts/fetch-sbom.sh "${{ inputs.app }}" diff --git a/.github/workflows/sbom-caddy.yml b/.github/workflows/sbom-caddy.yml index 0472523..63d2393 100644 --- a/.github/workflows/sbom-caddy.yml +++ b/.github/workflows/sbom-caddy.yml @@ -35,3 +35,4 @@ jobs: contents: read attestations: write + diff --git a/.github/workflows/sbom-osv-scanner.yml b/.github/workflows/sbom-osv-scanner.yml index 634d958..7ea58af 100644 --- a/.github/workflows/sbom-osv-scanner.yml +++ b/.github/workflows/sbom-osv-scanner.yml @@ -33,3 +33,4 @@ jobs: contents: read attestations: write + diff --git a/apps/caddy/config.yaml b/apps/caddy/config.yaml index e32d786..8144897 100644 --- a/apps/caddy/config.yaml +++ b/apps/caddy/config.yaml @@ -23,3 +23,4 @@ sbomify: component_name: "Caddy" product_id: "qYfcffpkcfav" + diff --git a/apps/keycloak/config.yaml b/apps/keycloak/config.yaml index 6459f46..2b46baf 100644 --- a/apps/keycloak/config.yaml +++ b/apps/keycloak/config.yaml @@ -14,9 +14,11 @@ format: cyclonedx source: type: lockfile repo: "keycloak/keycloak" - lockfile: "pom.xml" + lockfile: "quarkus/runtime/pom.xml" tag_prefix: "" clone: true + post_clone_commands: + - "./mvnw clean install -DskipTestsuite -DskipExamples -DskipTests" sbomify: component_id: "GYsnuXarUapb" diff --git a/apps/osv-scanner/config.yaml b/apps/osv-scanner/config.yaml index 4793383..f43df05 100644 --- a/apps/osv-scanner/config.yaml +++ b/apps/osv-scanner/config.yaml @@ -23,3 +23,4 @@ sbomify: component_name: "OSV Scanner" product_id: "BbFX2DwrjpP6" + diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh index 24814d3..cc8e0c9 100755 --- a/scripts/lib/common.sh +++ b/scripts/lib/common.sh @@ -84,7 +84,7 @@ die() { require_cmd() { local cmd="$1" local install_hint="${2:-}" - + if ! command -v "$cmd" &> /dev/null; then if [[ -n "$install_hint" ]]; then die "Required command '$cmd' not found. $install_hint" @@ -105,15 +105,15 @@ check_required_tools() { validate_app_dir() { local app="$1" local app_dir="${APPS_DIR}/${app}" - + if [[ ! -d "$app_dir" ]]; then die "App directory not found: $app_dir" fi - + if [[ ! -f "${app_dir}/config.yaml" ]]; then die "config.yaml not found in: $app_dir" fi - + log_debug "Validated app directory: $app_dir" } @@ -125,15 +125,15 @@ validate_app_dir() { # Accepts: X.Y.Z, X.Y.Z-prerelease, X.Y.Z+build, X.Y.Z-prerelease+build validate_semver() { local version="$1" - + # Semver regex pattern # Matches: MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD] local semver_regex='^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$' - + if [[ ! "$version" =~ $semver_regex ]]; then return 1 fi - + return 0 } @@ -141,29 +141,29 @@ validate_semver() { get_latest_version() { local app="$1" local config_file="${APPS_DIR}/${app}/config.yaml" - + if [[ ! -f "$config_file" ]]; then die "Config file not found: $config_file" fi - + # Ensure yq is available before attempting to read the config if ! command -v yq >/dev/null 2>&1; then die "'yq' command not found. It is required to read: $config_file. Please install 'yq' and try again." fi - + # Read version from config.yaml local version version="$(yq -r '.version // ""' "$config_file" | tr -d '[:space:]')" - + if [[ -z "$version" ]]; then die "Version not specified in: $config_file" fi - + # Validate semver format if ! 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 - + echo "$version" } @@ -223,6 +223,21 @@ get_sbomify_component_id() { get_config "$app" ".sbomify.component_id" } +# Get array values from app config.yaml +# Usage: get_config_array +# Outputs each array element on a separate line +get_config_array() { + local app="$1" + local path="$2" + local config_file="${APPS_DIR}/${app}/config.yaml" + + if [[ ! -f "$config_file" ]]; then + return 0 # No config file, return empty + fi + + yq -r "${path}[]? // empty" "$config_file" +} + # ============================================================================= # Utility Functions # ============================================================================= @@ -249,10 +264,10 @@ create_temp_dir() { local prefix="${1:-sbom}" local temp_dir temp_dir="$(mktemp -d -t "${prefix}.XXXXXX")" - + # Add to cleanup list _SBOM_TEMP_DIRS+=("$temp_dir") - + echo "$temp_dir" } @@ -274,11 +289,11 @@ run_cmd() { # Validate JSON output validate_json() { local input="$1" - + if ! echo "$input" | jq empty 2>/dev/null; then die "Invalid JSON output" fi - + log_debug "JSON validation passed" } @@ -286,10 +301,10 @@ validate_json() { validate_sbom() { local sbom="$1" local format="$2" - + # Check it's valid JSON first validate_json "$sbom" - + case "$format" in cyclonedx) # Check for bomFormat field @@ -307,7 +322,7 @@ validate_sbom() { log_warn "Unknown SBOM format: $format, skipping validation" ;; esac - + log_debug "SBOM validation passed for format: $format" } @@ -315,7 +330,7 @@ validate_sbom() { print_usage() { local script_name="$1" local description="$2" - + cat >&2 < [options] diff --git a/scripts/sources/lockfile-generator.sh b/scripts/sources/lockfile-generator.sh index 2aea2ff..e4f0faa 100755 --- a/scripts/sources/lockfile-generator.sh +++ b/scripts/sources/lockfile-generator.sh @@ -27,6 +27,14 @@ if [[ "$clone" == "true" ]]; then log_info "Shallow cloning: $repo_url (tag: $tag)" git clone --depth 1 --branch "$tag" "$repo_url" repo log_info "Cloned: repo/" + + # Run post-clone commands if configured + while IFS= read -r cmd; do + if [[ -n "$cmd" ]]; then + log_info "Running post-clone command: $cmd" + (cd repo && bash -c "$cmd") + fi + done < <(get_config_array "$app" ".source.post_clone_commands") else # Download just the lockfile url="https://raw.githubusercontent.com/${repo}/${tag}/${lockfile}" From 4c8e57ce153f68ac590228625dc3f12bd3041e08 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Mon, 5 Jan 2026 13:49:37 +0000 Subject: [PATCH 5/8] chore: clean up workflow output - Suppress git detached HEAD advice during clone - Remove verbose ls output from fetch step --- .github/workflows/sbom-builder.yml | 12 +----------- scripts/sources/lockfile-generator.sh | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/sbom-builder.yml b/.github/workflows/sbom-builder.yml index 15dfecd..a545a7a 100644 --- a/.github/workflows/sbom-builder.yml +++ b/.github/workflows/sbom-builder.yml @@ -95,17 +95,7 @@ jobs: maven- - name: Fetch SBOM or lockfile - run: | - ./scripts/fetch-sbom.sh "${{ inputs.app }}" - if [[ "${{ steps.config.outputs.source_type }}" == "lockfile" ]]; then - if [[ "${{ steps.config.outputs.clone }}" == "true" ]]; then - ls -la repo/ - else - ls -la "${{ steps.config.outputs.lockfile }}" - fi - else - ls -la sbom.json - fi + run: ./scripts/fetch-sbom.sh "${{ inputs.app }}" - name: Upload input artifact if: always() && steps.config.outputs.source_type != 'lockfile' diff --git a/scripts/sources/lockfile-generator.sh b/scripts/sources/lockfile-generator.sh index e4f0faa..d5e27e1 100755 --- a/scripts/sources/lockfile-generator.sh +++ b/scripts/sources/lockfile-generator.sh @@ -25,7 +25,7 @@ if [[ "$clone" == "true" ]]; then # Shallow clone the repository repo_url="https://github.com/${repo}.git" log_info "Shallow cloning: $repo_url (tag: $tag)" - git clone --depth 1 --branch "$tag" "$repo_url" repo + git -c advice.detachedHead=false clone --depth 1 --branch "$tag" "$repo_url" repo log_info "Cloned: repo/" # Run post-clone commands if configured From 7be9135fce8ebc6be706bfb55cf6e5f8c902ba7c Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Mon, 5 Jan 2026 13:59:30 +0000 Subject: [PATCH 6/8] Yaml lint --- .github/workflows/sbom-caddy.yml | 1 - .github/workflows/sbom-osv-scanner.yml | 1 - apps/caddy/config.yaml | 1 - apps/osv-scanner/config.yaml | 1 - 4 files changed, 4 deletions(-) diff --git a/.github/workflows/sbom-caddy.yml b/.github/workflows/sbom-caddy.yml index 63d2393..0472523 100644 --- a/.github/workflows/sbom-caddy.yml +++ b/.github/workflows/sbom-caddy.yml @@ -35,4 +35,3 @@ jobs: contents: read attestations: write - diff --git a/.github/workflows/sbom-osv-scanner.yml b/.github/workflows/sbom-osv-scanner.yml index 7ea58af..634d958 100644 --- a/.github/workflows/sbom-osv-scanner.yml +++ b/.github/workflows/sbom-osv-scanner.yml @@ -33,4 +33,3 @@ jobs: contents: read attestations: write - diff --git a/apps/caddy/config.yaml b/apps/caddy/config.yaml index 8144897..e32d786 100644 --- a/apps/caddy/config.yaml +++ b/apps/caddy/config.yaml @@ -23,4 +23,3 @@ sbomify: component_name: "Caddy" product_id: "qYfcffpkcfav" - diff --git a/apps/osv-scanner/config.yaml b/apps/osv-scanner/config.yaml index f43df05..4793383 100644 --- a/apps/osv-scanner/config.yaml +++ b/apps/osv-scanner/config.yaml @@ -23,4 +23,3 @@ sbomify: component_name: "OSV Scanner" product_id: "BbFX2DwrjpP6" - From a7a4b941d9b99e3ca88273b020cad6cd28b1349a Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Mon, 5 Jan 2026 14:04:28 +0000 Subject: [PATCH 7/8] Refs copilots feedback --- .github/workflows/sbom-keycloak-js.yml | 4 ++++ apps/keycloak-js/config.yaml | 2 -- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sbom-keycloak-js.yml b/.github/workflows/sbom-keycloak-js.yml index 547e4af..edfbc19 100644 --- a/.github/workflows/sbom-keycloak-js.yml +++ b/.github/workflows/sbom-keycloak-js.yml @@ -30,4 +30,8 @@ jobs: app: keycloak-js dry_run: ${{ github.event.inputs.dry_run == 'true' }} secrets: inherit + permissions: + id-token: write + contents: read + attestations: write diff --git a/apps/keycloak-js/config.yaml b/apps/keycloak-js/config.yaml index 07ee802..291b47e 100644 --- a/apps/keycloak-js/config.yaml +++ b/apps/keycloak-js/config.yaml @@ -17,8 +17,6 @@ source: lockfile: "js/pnpm-lock.yaml" tag_prefix: "" clone: true - extra_files: - - "js/package.json" sbomify: component_id: "SwIXpcPzBedP" From 07d9e46e34c22515f5183e14bced9557e23e0436 Mon Sep 17 00:00:00 2001 From: Viktor Petersson Date: Mon, 5 Jan 2026 15:56:00 +0000 Subject: [PATCH 8/8] Fixes parsing --- scripts/lib/common.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/lib/common.sh b/scripts/lib/common.sh index cc8e0c9..a9a9e34 100755 --- a/scripts/lib/common.sh +++ b/scripts/lib/common.sh @@ -235,7 +235,7 @@ get_config_array() { return 0 # No config file, return empty fi - yq -r "${path}[]? // empty" "$config_file" + yq -r "(${path} // [])[]" "$config_file" 2>/dev/null || true } # =============================================================================