#!/usr/bin/env bash # Risk Bundle Builder Script # RISK-BUNDLE-69-002: CI/offline kit pipeline integration # # Usage: build-bundle.sh --output [--fixtures-only] [--include-osv] # # This script builds a risk bundle for offline kit distribution. # In --fixtures-only mode, it generates a deterministic fixture bundle # suitable for CI testing without requiring live provider data. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)" # Defaults OUTPUT_DIR="" FIXTURES_ONLY=false INCLUDE_OSV=false BUNDLE_ID="" # Parse arguments while [[ $# -gt 0 ]]; do case $1 in --output) OUTPUT_DIR="$2" shift 2 ;; --fixtures-only) FIXTURES_ONLY=true shift ;; --include-osv) INCLUDE_OSV=true shift ;; --bundle-id) BUNDLE_ID="$2" shift 2 ;; -h|--help) echo "Usage: build-bundle.sh --output [--fixtures-only] [--include-osv] [--bundle-id ]" echo "" echo "Options:" echo " --output Output directory for bundle artifacts (required)" echo " --fixtures-only Use fixture data instead of live provider downloads" echo " --include-osv Include OSV providers (larger bundle)" echo " --bundle-id Custom bundle ID (default: auto-generated)" exit 0 ;; *) echo "Unknown option: $1" exit 1 ;; esac done # Validate required arguments if [[ -z "$OUTPUT_DIR" ]]; then echo "Error: --output is required" exit 1 fi # Generate bundle ID if not provided if [[ -z "$BUNDLE_ID" ]]; then BUNDLE_ID="risk-bundle-$(date -u +%Y%m%d-%H%M%S)" fi echo "=== Risk Bundle Builder ===" echo "Output directory: $OUTPUT_DIR" echo "Bundle ID: $BUNDLE_ID" echo "Fixtures only: $FIXTURES_ONLY" echo "Include OSV: $INCLUDE_OSV" # Create output directory mkdir -p "$OUTPUT_DIR" # Create temporary working directory WORK_DIR=$(mktemp -d) trap "rm -rf $WORK_DIR" EXIT echo "" echo "=== Preparing provider data ===" # Provider directories mkdir -p "$WORK_DIR/providers/cisa-kev" mkdir -p "$WORK_DIR/providers/first-epss" mkdir -p "$WORK_DIR/manifests" mkdir -p "$WORK_DIR/signatures" # Fixed timestamp for deterministic builds (2024-01-01 00:00:00 UTC) FIXED_TIMESTAMP="2024-01-01T00:00:00Z" FIXED_EPOCH=1704067200 if [[ "$FIXTURES_ONLY" == "true" ]]; then echo "Using fixture data..." # Create CISA KEV fixture (mandatory provider) cat > "$WORK_DIR/providers/cisa-kev/snapshot" <<'EOF' { "catalogVersion": "2024.12.11", "dateReleased": "2024-12-11T00:00:00Z", "count": 3, "vulnerabilities": [ { "cveID": "CVE-2024-0001", "vendorProject": "Example Vendor", "product": "Example Product", "vulnerabilityName": "Example Vulnerability 1", "dateAdded": "2024-01-15", "shortDescription": "Test vulnerability for CI fixtures", "requiredAction": "Apply updates per vendor instructions", "dueDate": "2024-02-05", "knownRansomwareCampaignUse": "Unknown" }, { "cveID": "CVE-2024-0002", "vendorProject": "Another Vendor", "product": "Another Product", "vulnerabilityName": "Example Vulnerability 2", "dateAdded": "2024-02-01", "shortDescription": "Another test vulnerability", "requiredAction": "Apply updates per vendor instructions", "dueDate": "2024-02-22", "knownRansomwareCampaignUse": "Known" }, { "cveID": "CVE-2024-0003", "vendorProject": "Third Vendor", "product": "Third Product", "vulnerabilityName": "Example Vulnerability 3", "dateAdded": "2024-03-01", "shortDescription": "Third test vulnerability", "requiredAction": "Apply updates per vendor instructions", "dueDate": "2024-03-22", "knownRansomwareCampaignUse": "Unknown" } ] } EOF # Create FIRST EPSS fixture (optional provider) cat > "$WORK_DIR/providers/first-epss/snapshot" <<'EOF' { "model_version": "v2024.01.01", "score_date": "2024-12-11", "scores": [ {"cve": "CVE-2024-0001", "epss": 0.00043, "percentile": 0.08}, {"cve": "CVE-2024-0002", "epss": 0.00156, "percentile": 0.45}, {"cve": "CVE-2024-0003", "epss": 0.00089, "percentile": 0.21} ] } EOF # Include OSV if requested if [[ "$INCLUDE_OSV" == "true" ]]; then mkdir -p "$WORK_DIR/providers/osv" cat > "$WORK_DIR/providers/osv/snapshot" <<'EOF' { "source": "osv", "updated": "2024-12-11T00:00:00Z", "advisories": [ {"id": "GHSA-test-0001", "modified": "2024-01-15T00:00:00Z", "aliases": ["CVE-2024-0001"]}, {"id": "GHSA-test-0002", "modified": "2024-02-01T00:00:00Z", "aliases": ["CVE-2024-0002"]} ] } EOF fi else echo "Live provider download not yet implemented" echo "Use --fixtures-only for CI testing" exit 1 fi echo "" echo "=== Computing hashes ===" # Compute hashes for each provider file CISA_HASH=$(sha256sum "$WORK_DIR/providers/cisa-kev/snapshot" | cut -d' ' -f1) EPSS_HASH=$(sha256sum "$WORK_DIR/providers/first-epss/snapshot" | cut -d' ' -f1) echo "cisa-kev hash: $CISA_HASH" echo "first-epss hash: $EPSS_HASH" PROVIDERS_JSON="[ {\"providerId\": \"cisa-kev\", \"digest\": \"sha256:$CISA_HASH\", \"snapshotDate\": \"$FIXED_TIMESTAMP\", \"optional\": false}, {\"providerId\": \"first-epss\", \"digest\": \"sha256:$EPSS_HASH\", \"snapshotDate\": \"$FIXED_TIMESTAMP\", \"optional\": true}" if [[ "$INCLUDE_OSV" == "true" ]]; then OSV_HASH=$(sha256sum "$WORK_DIR/providers/osv/snapshot" | cut -d' ' -f1) echo "osv hash: $OSV_HASH" PROVIDERS_JSON="$PROVIDERS_JSON, {\"providerId\": \"osv\", \"digest\": \"sha256:$OSV_HASH\", \"snapshotDate\": \"$FIXED_TIMESTAMP\", \"optional\": true}" fi PROVIDERS_JSON="$PROVIDERS_JSON ]" # Compute inputs hash (hash of all provider hashes sorted) INPUTS_HASH=$(echo -n "$CISA_HASH$EPSS_HASH" | sha256sum | cut -d' ' -f1) echo "inputs hash: $INPUTS_HASH" echo "" echo "=== Creating manifest ===" # Create provider manifest cat > "$WORK_DIR/manifests/provider-manifest.json" </dev/null || base64 "$WORK_DIR/manifests/provider-manifest.json") cat > "$WORK_DIR/signatures/provider-manifest.dsse" < /tmp/bundle-files.txt # Create tar with fixed mtime tar --mtime="@$FIXED_EPOCH" \ --sort=name \ --owner=0 --group=0 \ --numeric-owner \ -cvf "$OUTPUT_DIR/risk-bundle.tar" \ -T /tmp/bundle-files.txt # Compress with gzip (deterministic) gzip -n -9 < "$OUTPUT_DIR/risk-bundle.tar" > "$OUTPUT_DIR/risk-bundle.tar.gz" rm "$OUTPUT_DIR/risk-bundle.tar" # Copy manifest to output for easy access cp "$WORK_DIR/manifests/provider-manifest.json" "$OUTPUT_DIR/manifest.json" # Compute bundle hash BUNDLE_HASH=$(sha256sum "$OUTPUT_DIR/risk-bundle.tar.gz" | cut -d' ' -f1) echo "" echo "=== Build complete ===" echo "Bundle: $OUTPUT_DIR/risk-bundle.tar.gz" echo "Bundle hash: $BUNDLE_HASH" echo "Manifest: $OUTPUT_DIR/manifest.json" echo "Manifest hash: $MANIFEST_HASH" # Create checksum file echo "$BUNDLE_HASH risk-bundle.tar.gz" > "$OUTPUT_DIR/risk-bundle.tar.gz.sha256" echo "" echo "=== Artifacts ===" ls -la "$OUTPUT_DIR"