245 lines
6.4 KiB
Bash
245 lines
6.4 KiB
Bash
#!/bin/bash
|
|
# scripts/validate-sbom.sh
|
|
# Sprint: SPRINT_8200_0001_0003 - SBOM Schema Validation in CI
|
|
# Task: SCHEMA-8200-004 - Create validate-sbom.sh wrapper for sbom-utility
|
|
#
|
|
# Validates SBOM files against official CycloneDX JSON schemas.
|
|
# Uses sbom-utility for CycloneDX validation.
|
|
#
|
|
# Usage:
|
|
# ./scripts/validate-sbom.sh <sbom-file> [--schema <schema-path>]
|
|
# ./scripts/validate-sbom.sh bench/golden-corpus/sample.cyclonedx.json
|
|
# ./scripts/validate-sbom.sh --all # Validate all CycloneDX fixtures
|
|
#
|
|
# Exit codes:
|
|
# 0 - All validations passed
|
|
# 1 - Validation failed or error
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
|
|
SCHEMA_DIR="${REPO_ROOT}/docs/schemas"
|
|
DEFAULT_SCHEMA="${SCHEMA_DIR}/cyclonedx-bom-1.6.schema.json"
|
|
SBOM_UTILITY_VERSION="v0.16.0"
|
|
|
|
# Colors for output
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m' # No Color
|
|
|
|
log_info() {
|
|
echo -e "${GREEN}[INFO]${NC} $*"
|
|
}
|
|
|
|
log_warn() {
|
|
echo -e "${YELLOW}[WARN]${NC} $*"
|
|
}
|
|
|
|
log_error() {
|
|
echo -e "${RED}[ERROR]${NC} $*"
|
|
}
|
|
|
|
check_sbom_utility() {
|
|
if ! command -v sbom-utility &> /dev/null; then
|
|
log_warn "sbom-utility not found in PATH"
|
|
log_info "Installing sbom-utility ${SBOM_UTILITY_VERSION}..."
|
|
|
|
# Detect OS and architecture
|
|
local os arch
|
|
case "$(uname -s)" in
|
|
Linux*) os="linux";;
|
|
Darwin*) os="darwin";;
|
|
MINGW*|MSYS*|CYGWIN*) os="windows";;
|
|
*) log_error "Unsupported OS: $(uname -s)"; exit 1;;
|
|
esac
|
|
|
|
case "$(uname -m)" in
|
|
x86_64|amd64) arch="amd64";;
|
|
arm64|aarch64) arch="arm64";;
|
|
*) log_error "Unsupported architecture: $(uname -m)"; exit 1;;
|
|
esac
|
|
|
|
local url="https://github.com/CycloneDX/sbom-utility/releases/download/${SBOM_UTILITY_VERSION}/sbom-utility-${SBOM_UTILITY_VERSION}-${os}-${arch}.tar.gz"
|
|
local temp_dir
|
|
temp_dir=$(mktemp -d)
|
|
|
|
log_info "Downloading from ${url}..."
|
|
curl -sSfL "${url}" | tar xz -C "${temp_dir}"
|
|
|
|
if [[ "$os" == "windows" ]]; then
|
|
log_info "Please add ${temp_dir}/sbom-utility.exe to your PATH"
|
|
export PATH="${temp_dir}:${PATH}"
|
|
else
|
|
log_info "Installing to /usr/local/bin (may require sudo)..."
|
|
if [[ -w /usr/local/bin ]]; then
|
|
mv "${temp_dir}/sbom-utility" /usr/local/bin/
|
|
else
|
|
sudo mv "${temp_dir}/sbom-utility" /usr/local/bin/
|
|
fi
|
|
fi
|
|
|
|
rm -rf "${temp_dir}"
|
|
log_info "sbom-utility installed successfully"
|
|
fi
|
|
}
|
|
|
|
validate_cyclonedx() {
|
|
local sbom_file="$1"
|
|
local schema="${2:-$DEFAULT_SCHEMA}"
|
|
|
|
if [[ ! -f "$sbom_file" ]]; then
|
|
log_error "File not found: $sbom_file"
|
|
return 1
|
|
fi
|
|
|
|
if [[ ! -f "$schema" ]]; then
|
|
log_error "Schema not found: $schema"
|
|
log_info "Expected schema at: ${DEFAULT_SCHEMA}"
|
|
return 1
|
|
fi
|
|
|
|
# Detect if it's a CycloneDX file
|
|
if ! grep -q '"bomFormat"' "$sbom_file" 2>/dev/null; then
|
|
log_warn "File does not appear to be CycloneDX: $sbom_file"
|
|
log_info "Skipping (use validate-spdx.sh for SPDX files)"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Validating: $sbom_file"
|
|
|
|
# Run sbom-utility validation
|
|
if sbom-utility validate --input-file "$sbom_file" --format json 2>&1; then
|
|
log_info "✓ Validation passed: $sbom_file"
|
|
return 0
|
|
else
|
|
log_error "✗ Validation failed: $sbom_file"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
validate_all() {
|
|
local fixture_dir="${REPO_ROOT}/bench/golden-corpus"
|
|
local failed=0
|
|
local passed=0
|
|
local skipped=0
|
|
|
|
log_info "Validating all CycloneDX fixtures in ${fixture_dir}..."
|
|
|
|
if [[ ! -d "$fixture_dir" ]]; then
|
|
log_error "Fixture directory not found: $fixture_dir"
|
|
return 1
|
|
fi
|
|
|
|
while IFS= read -r -d '' file; do
|
|
if grep -q '"bomFormat".*"CycloneDX"' "$file" 2>/dev/null; then
|
|
if validate_cyclonedx "$file"; then
|
|
((passed++))
|
|
else
|
|
((failed++))
|
|
fi
|
|
else
|
|
log_info "Skipping non-CycloneDX file: $file"
|
|
((skipped++))
|
|
fi
|
|
done < <(find "$fixture_dir" -type f -name '*.json' -print0)
|
|
|
|
echo ""
|
|
log_info "Validation Summary:"
|
|
log_info " Passed: ${passed}"
|
|
log_info " Failed: ${failed}"
|
|
log_info " Skipped: ${skipped}"
|
|
|
|
if [[ $failed -gt 0 ]]; then
|
|
log_error "Some validations failed!"
|
|
return 1
|
|
fi
|
|
|
|
log_info "All CycloneDX validations passed!"
|
|
return 0
|
|
}
|
|
|
|
usage() {
|
|
cat << EOF
|
|
Usage: $(basename "$0") [OPTIONS] <sbom-file>
|
|
|
|
Validates CycloneDX SBOM files against official JSON schemas.
|
|
|
|
Options:
|
|
--all Validate all CycloneDX fixtures in bench/golden-corpus/
|
|
--schema <path> Use custom schema file (default: docs/schemas/cyclonedx-bom-1.6.schema.json)
|
|
--help, -h Show this help message
|
|
|
|
Examples:
|
|
$(basename "$0") sample.cyclonedx.json
|
|
$(basename "$0") --schema custom-schema.json sample.json
|
|
$(basename "$0") --all
|
|
|
|
Exit codes:
|
|
0 All validations passed
|
|
1 Validation failed or error
|
|
EOF
|
|
}
|
|
|
|
main() {
|
|
local schema="$DEFAULT_SCHEMA"
|
|
local validate_all_flag=false
|
|
local files=()
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--all)
|
|
validate_all_flag=true
|
|
shift
|
|
;;
|
|
--schema)
|
|
schema="$2"
|
|
shift 2
|
|
;;
|
|
--help|-h)
|
|
usage
|
|
exit 0
|
|
;;
|
|
-*)
|
|
log_error "Unknown option: $1"
|
|
usage
|
|
exit 1
|
|
;;
|
|
*)
|
|
files+=("$1")
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
# Ensure sbom-utility is available
|
|
check_sbom_utility
|
|
|
|
if [[ "$validate_all_flag" == "true" ]]; then
|
|
validate_all
|
|
exit $?
|
|
fi
|
|
|
|
if [[ ${#files[@]} -eq 0 ]]; then
|
|
log_error "No SBOM file specified"
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
local failed=0
|
|
for file in "${files[@]}"; do
|
|
if ! validate_cyclonedx "$file" "$schema"; then
|
|
((failed++))
|
|
fi
|
|
done
|
|
|
|
if [[ $failed -gt 0 ]]; then
|
|
exit 1
|
|
fi
|
|
|
|
exit 0
|
|
}
|
|
|
|
main "$@"
|