Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 60 additions & 8 deletions .github/workflows/sbom-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -64,25 +63,63 @@ 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
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
echo "product_release=[\"${PRODUCT_ID}:${VERSION}\"]" >> $GITHUB_OUTPUT
fi

- name: Fetch SBOM
run: |
./scripts/fetch-sbom.sh "${{ inputs.app }}"
ls -la sbom.json
- 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 }}"

- 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' && 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
env:
TOKEN: ${{ secrets.SBOMIFY_TOKEN }}
Expand All @@ -96,6 +133,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_path }}
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
Expand Down
37 changes: 37 additions & 0 deletions .github/workflows/sbom-keycloak-js.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# 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
permissions:
id-token: write
contents: read
attestations: write

35 changes: 35 additions & 0 deletions .github/workflows/sbom-keycloak.yml
Original file line number Diff line number Diff line change
@@ -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

31 changes: 21 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -22,6 +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) | 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
Expand All @@ -43,7 +45,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
```

Expand Down Expand Up @@ -183,21 +185,31 @@ 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:
type: lockfile
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"
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.

## Local Development

### Prerequisites
Expand All @@ -214,9 +226,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

Expand Down
25 changes: 25 additions & 0 deletions apps/keycloak-js/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 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

sbomify:
component_id: "SwIXpcPzBedP"
component_name: "Keycloak JS"
product_id: "DYmPnVgOSdyT"

27 changes: 27 additions & 0 deletions apps/keycloak/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# 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: "quarkus/runtime/pom.xml"
tag_prefix: ""
clone: true
post_clone_commands:
- "./mvnw clean install -DskipTestsuite -DskipExamples -DskipTests"

sbomify:
component_id: "GYsnuXarUapb"
component_name: "Keycloak"
product_id: "DYmPnVgOSdyT"

23 changes: 22 additions & 1 deletion scripts/fetch-sbom.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@
# fetch-sbom.sh - Fetch SBOM for an app
#
# Usage: ./fetch-sbom.sh <app-name>
# 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() {
Expand Down Expand Up @@ -37,7 +40,25 @@ main() {
fi

"$handler" "$app"
log_info "Done: sbom.json"

# Log appropriate output based on source type
case "$source_type" in
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"
;;
esac
}

main "$@"
Loading