Implements comprehensive Software Bill of Materials generation for EU Cyber Resilience Act compliance with ANSSI CSPN certification path. SBOM Pipeline: - scripts/check-sbom-prereqs.sh: Prerequisites validation (OpenWrt, tools, Kconfig) - scripts/sbom-generate.sh: Multi-source SBOM generation (native, feed, rootfs, firmware) - scripts/sbom-audit-feed.sh: PKG_HASH/PKG_LICENSE feed audit with MANIFEST.md - Makefile: SBOM targets (sbom, sbom-quick, sbom-validate, sbom-scan, sbom-audit) - .github/workflows/sbom-release.yml: CI with CVE gating and auto-security issues Documentation: - SECURITY.md: CRA Art. 13 §6 compliant vulnerability disclosure policy - docs/sbom-pipeline.md: Architecture, CRA mapping, ANSSI CSPN guidance AI Gateway (bonus feed): - secubox-ai-gateway: 3-tier data classification (LOCAL_ONLY/SANITIZED/CLOUD_DIRECT) - luci-app-ai-gateway: LuCI dashboard with provider management and audit logging Output formats: CycloneDX 1.6 (primary) + SPDX 2.3 (secondary) Tools: syft, grype, cyclonedx-cli (auto-installed if missing) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
220 lines
5.8 KiB
Bash
Executable File
220 lines
5.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# SecuBox Feed Audit Script
|
|
# Audits SecuBox feed Makefiles for SBOM metadata completeness
|
|
#
|
|
# Copyright (C) 2026 CyberMind Produits SASU
|
|
# License: GPL-2.0-only
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
TOPDIR="${SCRIPT_DIR}/.."
|
|
FEED_DIR="${FEED_DIR:-${TOPDIR}/feeds/secubox}"
|
|
OUTPUT_DIR="${OUTPUT_DIR:-${FEED_DIR}}"
|
|
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
NC='\033[0m'
|
|
|
|
log_ok() { echo -e "${GREEN}[✓]${NC} $*"; }
|
|
log_warn() { echo -e "${YELLOW}[!]${NC} $*"; }
|
|
log_fail() { echo -e "${RED}[✗]${NC} $*"; }
|
|
|
|
MISSING_HASH=()
|
|
MISSING_LICENSE=()
|
|
PACKAGES=()
|
|
|
|
# Extract variable from Makefile
|
|
extract_var() {
|
|
local file="$1"
|
|
local var="$2"
|
|
grep -E "^${var}\s*[:?]?=" "$file" 2>/dev/null | head -1 | sed 's/.*=\s*//' | tr -d ' ' || echo ""
|
|
}
|
|
|
|
# Try to compute hash from downloaded source
|
|
compute_hash() {
|
|
local pkg_name="$1"
|
|
local pkg_version="$2"
|
|
|
|
local dl_dir="${TOPDIR}/dl"
|
|
local patterns=(
|
|
"${dl_dir}/${pkg_name}-${pkg_version}.tar.gz"
|
|
"${dl_dir}/${pkg_name}-${pkg_version}.tar.xz"
|
|
"${dl_dir}/${pkg_name}-${pkg_version}.tar.bz2"
|
|
"${dl_dir}/${pkg_name}_${pkg_version}.orig.tar.gz"
|
|
)
|
|
|
|
for file in "${patterns[@]}"; do
|
|
if [[ -f "$file" ]]; then
|
|
sha256sum "$file" | cut -d' ' -f1
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
audit_package() {
|
|
local makefile="$1"
|
|
local pkg_dir
|
|
pkg_dir=$(dirname "$makefile")
|
|
local dir_name
|
|
dir_name=$(basename "$pkg_dir")
|
|
|
|
local pkg_name pkg_version pkg_license pkg_hash pkg_source_url
|
|
|
|
pkg_name=$(extract_var "$makefile" "PKG_NAME")
|
|
[[ -z "$pkg_name" ]] && pkg_name="$dir_name"
|
|
|
|
pkg_version=$(extract_var "$makefile" "PKG_VERSION")
|
|
[[ -z "$pkg_version" ]] && pkg_version="unknown"
|
|
|
|
pkg_license=$(extract_var "$makefile" "PKG_LICENSE")
|
|
pkg_hash=$(extract_var "$makefile" "PKG_HASH")
|
|
pkg_source_url=$(extract_var "$makefile" "PKG_SOURCE_URL")
|
|
|
|
# Check for missing hash
|
|
local hash_status="❌ MISSING"
|
|
local computed_hash=""
|
|
if [[ -n "$pkg_hash" && "$pkg_hash" != "skip" ]]; then
|
|
hash_status="✅ ${pkg_hash:0:12}..."
|
|
else
|
|
# Try to compute from local download
|
|
computed_hash=$(compute_hash "$pkg_name" "$pkg_version" 2>/dev/null || echo "")
|
|
if [[ -n "$computed_hash" ]]; then
|
|
hash_status="⚠️ COMPUTED: ${computed_hash:0:12}..."
|
|
else
|
|
MISSING_HASH+=("$pkg_name")
|
|
fi
|
|
fi
|
|
|
|
# Check for missing license
|
|
local license_status="❌ MISSING"
|
|
if [[ -n "$pkg_license" ]]; then
|
|
license_status="$pkg_license"
|
|
else
|
|
MISSING_LICENSE+=("$pkg_name")
|
|
fi
|
|
|
|
# Store package info
|
|
PACKAGES+=("$pkg_name|$pkg_version|$license_status|$hash_status|${pkg_source_url:-N/A}")
|
|
|
|
# Output computed hash suggestion if found
|
|
if [[ -n "$computed_hash" && ( -z "$pkg_hash" || "$pkg_hash" == "skip" ) ]]; then
|
|
echo " → Suggested: PKG_HASH:=${computed_hash}" >&2
|
|
fi
|
|
}
|
|
|
|
generate_manifest() {
|
|
local manifest="${OUTPUT_DIR}/MANIFEST.md"
|
|
|
|
cat > "$manifest" <<EOF
|
|
# SecuBox Feed — Package Manifest
|
|
_Generated by scripts/sbom-audit-feed.sh — $(date -Iseconds)_
|
|
|
|
| Package | Version | License | PKG_HASH | Source URL |
|
|
|---------|---------|---------|----------|------------|
|
|
EOF
|
|
|
|
for pkg_info in "${PACKAGES[@]}"; do
|
|
IFS='|' read -r name version license hash source <<< "$pkg_info"
|
|
echo "| $name | $version | $license | $hash | $source |" >> "$manifest"
|
|
done
|
|
|
|
cat >> "$manifest" <<EOF
|
|
|
|
## Summary
|
|
|
|
- **Total packages:** ${#PACKAGES[@]}
|
|
- **Missing PKG_HASH:** ${#MISSING_HASH[@]}
|
|
- **Missing PKG_LICENSE:** ${#MISSING_LICENSE[@]}
|
|
|
|
EOF
|
|
|
|
if [[ ${#MISSING_HASH[@]} -gt 0 ]]; then
|
|
echo "### Packages Missing PKG_HASH" >> "$manifest"
|
|
echo "" >> "$manifest"
|
|
for pkg in "${MISSING_HASH[@]}"; do
|
|
echo "- \`$pkg\`" >> "$manifest"
|
|
done
|
|
echo "" >> "$manifest"
|
|
fi
|
|
|
|
if [[ ${#MISSING_LICENSE[@]} -gt 0 ]]; then
|
|
echo "### Packages Missing PKG_LICENSE" >> "$manifest"
|
|
echo "" >> "$manifest"
|
|
for pkg in "${MISSING_LICENSE[@]}"; do
|
|
echo "- \`$pkg\`" >> "$manifest"
|
|
done
|
|
echo "" >> "$manifest"
|
|
fi
|
|
|
|
log_ok "Generated: $manifest"
|
|
}
|
|
|
|
main() {
|
|
echo "=========================================="
|
|
echo "SecuBox Feed Audit"
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
if [[ ! -d "$FEED_DIR" ]]; then
|
|
log_fail "Feed directory not found: $FEED_DIR"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Scanning: $FEED_DIR"
|
|
echo ""
|
|
|
|
local count=0
|
|
for makefile in "$FEED_DIR"/*/Makefile; do
|
|
[[ -f "$makefile" ]] || continue
|
|
((count++))
|
|
audit_package "$makefile"
|
|
done
|
|
|
|
echo ""
|
|
echo "=========================================="
|
|
echo "Audit Results"
|
|
echo "=========================================="
|
|
echo ""
|
|
|
|
echo "Total packages: $count"
|
|
echo "Missing PKG_HASH: ${#MISSING_HASH[@]}"
|
|
echo "Missing PKG_LICENSE: ${#MISSING_LICENSE[@]}"
|
|
echo ""
|
|
|
|
generate_manifest
|
|
|
|
if [[ ${#MISSING_HASH[@]} -gt 0 || ${#MISSING_LICENSE[@]} -gt 0 ]]; then
|
|
echo ""
|
|
log_warn "Some packages have missing metadata (see MANIFEST.md)"
|
|
|
|
if [[ ${#MISSING_HASH[@]} -gt 0 ]]; then
|
|
echo ""
|
|
echo "Packages missing PKG_HASH:"
|
|
for pkg in "${MISSING_HASH[@]}"; do
|
|
echo " - $pkg"
|
|
done
|
|
fi
|
|
|
|
if [[ ${#MISSING_LICENSE[@]} -gt 0 ]]; then
|
|
echo ""
|
|
echo "Packages missing PKG_LICENSE:"
|
|
for pkg in "${MISSING_LICENSE[@]}"; do
|
|
echo " - $pkg"
|
|
done
|
|
fi
|
|
|
|
echo ""
|
|
log_fail "Fix missing metadata for CRA compliance"
|
|
exit 1
|
|
else
|
|
log_ok "All packages have required metadata"
|
|
exit 0
|
|
fi
|
|
}
|
|
|
|
main "$@"
|