From 3046d61f06861d6ea7ece4397edee244d6cf6113 Mon Sep 17 00:00:00 2001 From: Victoria Date: Mon, 17 Nov 2025 17:10:55 +0100 Subject: [PATCH] build(compliance): add missing known license information in sboms Signed-off-by: Victoria --- .github/workflows/release.yaml | 5 + .../workflows/utils/add-license-to-sbom.sh | 168 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100755 .github/workflows/utils/add-license-to-sbom.sh diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fd848c03c..e1f020750 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -135,6 +135,11 @@ jobs: sbom_name="${repo_name}-sbom-${arch}" syft -o cyclonedx-json=/tmp/sbom-$material_name.cyclonedx.json $entry + + # Add missing known licenses + ${{ github.workspace }}/.github/workflows/utils/add-license-to-sbom.sh /tmp/sbom-$material_name.cyclonedx.json "ariga.io/atlas/cmd/atlas" "Apache-2.0" type="library" + ${{ github.workspace }}/.github/workflows/utils/add-license-to-sbom.sh /tmp/sbom-$material_name.cyclonedx.json "github.com/ariga/language-tools/packages/language-server-go" "Apache-2.0" type="library" + chainloop attestation add --name $container_name --value $entry --kind CONTAINER_IMAGE --attestation-id ${{ env.ATTESTATION_ID }} chainloop attestation add --name $sbom_name --value /tmp/sbom-$material_name.cyclonedx.json --kind SBOM_CYCLONEDX_JSON --attestation-id ${{ env.ATTESTATION_ID }} diff --git a/.github/workflows/utils/add-license-to-sbom.sh b/.github/workflows/utils/add-license-to-sbom.sh new file mode 100755 index 000000000..6776ea967 --- /dev/null +++ b/.github/workflows/utils/add-license-to-sbom.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash + +# Script to add license information to SBOM components +# Usage: +# ./add-license-to-sbom.sh [version=] [type=] [--custom_license] [--strict] [--help] +# Example: ./add-license-to-sbom.sh sbom.json "backend" "Apache-2.0" version="v1.0.0" +# Example: ./add-license-to-sbom.sh sbom.json "frontend" "Chainloop Proprietary License" type="library" --custom_license --strict + +set -euo pipefail + +# Check for help flag first +if [[ "${1:-}" == "--help" ]] || [[ "${1:-}" == "-h" ]]; then + cat << EOF +SBOM License Addition Script + +This script adds license information to SBOM components if they exist and don't already have licenses. + +Usage: + ./add-license-to-sbom.sh [options] + +Arguments: + sbom_file Path to the SBOM file (CycloneDX JSON format) + component_name Name of the component to add license to + license_identifier SPDX license identifier (e.g., "Apache-2.0", "MIT") or custom license name + +Options: + version= Match component by name and version + type= Match component by name and type + --custom_license Treat license_identifier as custom license name (default: SPDX identifier) + --strict Fail if component doesn't exist or already has licenses + --help, -h Show this help message + +Examples: + # Add SPDX license identifier to component by name and version + ./add-license-to-sbom.sh sbom.json "backend" "Apache-2.0" version="v1.0.0" + + # Add SPDX license identifier to component by name and type + ./add-license-to-sbom.sh sbom.json "frontend" "MIT" type="library" + + # Add custom license name to component + ./add-license-to-sbom.sh sbom.json "backend" "Chainloop Proprietary License" --custom_license + + # Use strict mode (fail if component missing or has licenses) + ./add-license-to-sbom.sh sbom.json "cli" "BSD-3-Clause" --strict + + # Match by name only with SPDX identifier + ./add-license-to-sbom.sh sbom.json "backend" "GPL-3.0-or-later" + +Behavior: + - Default: Skips with message if component not found or already has licenses + - Strict: Exits with error code 1 if component not found or already has licenses +EOF + exit 0 +fi + +# Validate required arguments +if [[ $# -lt 3 ]]; then + echo "Error: Missing required arguments" + echo "Use --help for usage information" + exit 1 +fi + +SBOM_FILE="$1" +COMPONENT_NAME="$2" +LICENSE_NAME="$3" + +# Validate SBOM file exists +if [[ ! -f "$SBOM_FILE" ]]; then + echo "Error: SBOM file '$SBOM_FILE' does not exist" + exit 1 +fi + +# Initialize optional parameters +VERSION="" +TYPE="" +CUSTOM_LICENSE=false # Default to SPDX identifier format +STRICT_MODE=false + +# Parse optional parameters from command line arguments (starting from 4th argument) +for arg in "${@:4}"; do + case $arg in + version=*) + # Extract version value after the '=' sign + VERSION="${arg#*=}" + ;; + type=*) + # Extract type value after the '=' sign + TYPE="${arg#*=}" + ;; + --custom_license) + # Treat license_identifier as custom license name instead of SPDX identifier + CUSTOM_LICENSE=true + ;; + --strict) + # Enable strict mode - will fail if component missing or has licenses + STRICT_MODE=true + ;; + esac +done + +# Build jq selector based on available criteria to identify the component +# This creates a flexible matching system depending on what parameters were provided +if [[ -n "$VERSION" && -n "$TYPE" ]]; then + # Match by name, version AND type (most specific) + SELECTOR=".name == \"$COMPONENT_NAME\" and .version == \"$VERSION\" and .type == \"$TYPE\"" + IDENTIFIER="name='$COMPONENT_NAME', version='$VERSION', type='$TYPE'" +elif [[ -n "$VERSION" ]]; then + # Match by name and version only + SELECTOR=".name == \"$COMPONENT_NAME\" and .version == \"$VERSION\"" + IDENTIFIER="name='$COMPONENT_NAME', version='$VERSION'" +elif [[ -n "$TYPE" ]]; then + # Match by name and type only + SELECTOR=".name == \"$COMPONENT_NAME\" and .type == \"$TYPE\"" + IDENTIFIER="name='$COMPONENT_NAME', type='$TYPE'" +else + # Match by name only (least specific) + SELECTOR=".name == \"$COMPONENT_NAME\"" + IDENTIFIER="name='$COMPONENT_NAME'" +fi + +# Check if component exists in SBOM using our constructed selector +# Uses jq to search through components array and returns the name if found +HAS_COMPONENT=$(jq -r ".components[] | select($SELECTOR) | .name" "$SBOM_FILE" | head -1) + +if [[ -n "$HAS_COMPONENT" ]]; then + # Component was found - now check if it already has license information + # Count existing licenses (using // [] to handle missing licenses field) + HAS_LICENSES=$(jq -r ".components[] | select($SELECTOR) | (.licenses // []) | length" "$SBOM_FILE" | head -1) + + if [[ "$HAS_LICENSES" == "0" ]]; then + # Component exists but has no licenses - proceed with adding license + echo "Adding license '$LICENSE_NAME' to component with $IDENTIFIER" + + # Use jq to add license information to the matching component + # Creates temporary file and atomically moves it to avoid corruption + if [[ "$CUSTOM_LICENSE" == "true" ]]; then + # Use "name" field for custom license names + jq "(.components[] | select($SELECTOR) | select((.licenses // []) | length == 0) | .licenses) = [{\"license\": {\"name\": \"$LICENSE_NAME\"}}]" "$SBOM_FILE" > "${SBOM_FILE}.tmp" && mv "${SBOM_FILE}.tmp" "$SBOM_FILE" + else + # Use "id" field for SPDX license identifiers + jq "(.components[] | select($SELECTOR) | select((.licenses // []) | length == 0) | .licenses) = [{\"license\": {\"id\": \"$LICENSE_NAME\"}}]" "$SBOM_FILE" > "${SBOM_FILE}.tmp" && mv "${SBOM_FILE}.tmp" "$SBOM_FILE" + fi + + echo "License added successfully" + else + # Component already has license information + echo "Component with $IDENTIFIER already has licenses" + if [[ "$STRICT_MODE" == "true" ]]; then + # In strict mode, this is an error condition + echo "ERROR: --strict mode enabled and component already has licenses" + exit 1 + else + # In lenient mode, just skip with a message + echo "Skipping license addition" + fi + fi +else + # Component was not found in SBOM + echo "Component with $IDENTIFIER not found in SBOM" + if [[ "$STRICT_MODE" == "true" ]]; then + # In strict mode, missing component is an error + echo "ERROR: --strict mode enabled and component not found" + exit 1 + else + # In lenient mode, just skip with a message + echo "Skipping license addition" + fi +fi \ No newline at end of file