#!/usr/bin/env bash # Generate dependency/artifact security evidence and run available scanners. set -euo pipefail ROOT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd) REPORT_DIR="${ROOT_DIR}/target/supply-chain-gate" SUMMARY_JSON="${REPORT_DIR}/summary.json" SUMMARY_TXT="${REPORT_DIR}/summary.txt" METRICS_FILE="${REPORT_DIR}/metrics.prom" SBOM_JSON="${REPORT_DIR}/sbom.cargo-metadata.json" TREE_TXT="${REPORT_DIR}/dependency-tree.txt" SECRET_TXT="${REPORT_DIR}/secret-scan.txt" PUSHGATEWAY_URL=${QUALITY_GATE_PUSHGATEWAY_URL:-} ENFORCE_TOOLS=${LESAVKA_SUPPLY_CHAIN_ENFORCE_TOOLS:-0} mkdir -p "${REPORT_DIR}" cd "${ROOT_DIR}" branch=${BRANCH_NAME:-${GIT_BRANCH:-}} if [[ -z "${branch}" ]]; then branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown) fi commit=${GIT_COMMIT:-} if [[ -z "${commit}" ]]; then commit=$(git rev-parse --short HEAD 2>/dev/null || echo unknown) fi status=0 cargo metadata --locked --format-version 1 >"${SBOM_JSON}" cargo tree --locked --workspace >"${TREE_TXT}" secret_status=ok : >"${SECRET_TXT}" while IFS= read -r path; do case "$path" in Cargo.lock|target/*|dist/*) continue ;; esac [[ -f "$path" ]] || continue if file "$path" | grep -qi 'text\|json\|xml\|yaml\|toml\|script'; then if grep -EHni \ -e 'AKIA[0-9A-Z]{16}' \ -e '-----BEGIN (RSA|EC|OPENSSH|PRIVATE) KEY-----' \ -e "(password|secret|token|api[_-]?key)[[:space:]]*[:=][[:space:]]*[\"'][A-Za-z0-9_+/=.-]{12,}[\"']" \ "$path" >>"${SECRET_TXT}" 2>/dev/null; then secret_status=failed status=1 fi fi done < <(git ls-files) audit_status=not_applicable if command -v cargo-audit >/dev/null 2>&1; then audit_status=ok if ! cargo audit --locked >"${REPORT_DIR}/cargo-audit.txt" 2>&1; then audit_status=failed status=1 fi elif [[ "${ENFORCE_TOOLS}" == "1" ]]; then audit_status=failed status=1 fi deny_status=not_applicable if command -v cargo-deny >/dev/null 2>&1; then deny_status=ok if ! cargo deny check >"${REPORT_DIR}/cargo-deny.txt" 2>&1; then deny_status=failed status=1 fi elif [[ "${ENFORCE_TOOLS}" == "1" ]]; then deny_status=failed status=1 fi artifact_status=not_applicable if compgen -G "dist/*.tar.gz" >/dev/null; then artifact_status=ok sha256sum dist/*.tar.gz >"${REPORT_DIR}/SHA256SUMS" fi python3 - "${SUMMARY_JSON}" "${SUMMARY_TXT}" "${METRICS_FILE}" "${branch}" "${commit}" "${secret_status}" "${audit_status}" "${deny_status}" "${artifact_status}" <<'PY' import json import pathlib import sys from datetime import datetime, timezone summary_path = pathlib.Path(sys.argv[1]) text_path = pathlib.Path(sys.argv[2]) metrics_path = pathlib.Path(sys.argv[3]) branch = sys.argv[4] commit = sys.argv[5] secret_status = sys.argv[6] audit_status = sys.argv[7] deny_status = sys.argv[8] artifact_status = sys.argv[9] def esc(value: str) -> str: return value.replace('\\', r'\\').replace('\n', r'\n').replace('"', r'\"') checks = { 'secret_scan': secret_status, 'cargo_audit': audit_status, 'cargo_deny': deny_status, 'artifact_checksums': artifact_status, 'sbom': 'ok', } status = 'failed' if any(value == 'failed' for value in checks.values()) else 'ok' summary = { 'suite': 'lesavka', 'check': 'supply_chain', 'status': status, 'branch': branch, 'commit': commit, 'generated_at': datetime.now(timezone.utc).isoformat(), 'checks': checks, } summary_path.write_text(json.dumps(summary, indent=2, sort_keys=True) + '\n', encoding='utf-8') lines = ['supply chain gate report', f'status: {status}', f'branch: {branch}', f'commit: {commit}', ''] for name, value in checks.items(): lines.append(f'{name}: {value}') text_path.write_text('\n'.join(lines) + '\n', encoding='utf-8') labels = f'suite="lesavka",branch="{esc(branch)}",commit="{esc(commit)}"' metrics = [ '# HELP platform_quality_gate_checks_total Check outcomes from the latest lesavka gate run.', '# TYPE platform_quality_gate_checks_total gauge', ] for state in ('ok', 'failed', 'not_applicable'): value = 1 if state == status else 0 metrics.append(f'platform_quality_gate_checks_total{{{labels},check="supply_chain",status="{state}"}} {value}') metrics.append('# HELP lesavka_supply_chain_subcheck_info Supply-chain subcheck evidence status.') metrics.append('# TYPE lesavka_supply_chain_subcheck_info gauge') for name, value in checks.items(): metrics.append(f'lesavka_supply_chain_subcheck_info{{{labels},subcheck="{esc(name)}",status="{esc(value)}"}} 1') metrics_path.write_text('\n'.join(metrics) + '\n', encoding='utf-8') print(text_path.read_text(encoding='utf-8')) PY if [[ -n "${PUSHGATEWAY_URL}" ]]; then curl --fail --silent --show-error \ --data-binary @"${METRICS_FILE}" \ "${PUSHGATEWAY_URL%/}/metrics/job/lesavka-supply-chain-gate/suite/lesavka" || status=$? fi exit "${status}"