CI / Check (push) Skipped
CI / Check (pull_request) Failing after 3m1s
CI / Detect Changes (pull_request) Skipped
CI / Deploy Agent (pull_request) Skipped
CI / Deploy Dashboard (pull_request) Skipped
CI / Deploy Docs (pull_request) Skipped
CI / Deploy MCP (pull_request) Skipped
Versioned, reproducible set of representative targets so every scan path can be exercised repeatably and the nightly regression (#188) has a baseline. - fixtures/demo-targets/targets.json: 5 targets — PlcSps composite (pump_station.st + pump_fbd.xml + optional Modbus live URL + optional firmware image), PlcSps pure (conveyor.xml + traffic_light.st), plain git SAST (sharang/cra-vuln-demo, pinned), WebApp (juice-shop v19.2.1 + live URL), FirmwareRtos (zephyr example-application, pinned). Each carries an `expect` golden baseline (min_findings, sast_rule_ids, cwes, control_refs, min_sbom_components, scans_offered, pentest_supported, detected_facts). - compliance-agent::fixtures: typed loader (DemoTargets/DemoTarget/ DemoArtifact/Expect) + lib tests that keep the manifest well-formed and assert the PLC baselines offline by running analyze_tree over the checked-in fixtures (runs in the normal --lib CI job). - scripts/seed-demo-targets.sh: curl+jq seeder over the public onboarding API (create → upload → detect → optional --scan), --only, --reset (deletes only the "Demo · " prefix), env overrides for infra-dependent artifacts. - docs/guide/demo-targets.md + sidebar entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EgxGHn22YEfQz5fLHSHkLv
180 lines
6.6 KiB
Bash
Executable File
180 lines
6.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Seed the curated demo targets (#187) into a running compliance-agent.
|
|
#
|
|
# Reads fixtures/demo-targets/targets.json and, for every target:
|
|
# 1. POST /api/v1/targets (name = name_prefix + name)
|
|
# 2. POST /api/v1/targets/{id}/artifacts/upload for each `upload` artifact
|
|
# 3. POST /api/v1/targets/{id}/detect (surface detected facts)
|
|
# 4. POST /api/v1/targets/{id}/scan (only with --scan)
|
|
#
|
|
# Artifact env overrides (see the manifest): DEMO_WEB_URL, DEMO_PLC_MODBUS_URL,
|
|
# DEMO_PLC_FIRMWARE_IMAGE. Optional artifacts whose env var is unset are
|
|
# skipped, so the set seeds on a bare laptop; set them on comp-dev / Orca.
|
|
#
|
|
# Usage:
|
|
# scripts/seed-demo-targets.sh # seed all
|
|
# scripts/seed-demo-targets.sh --scan # seed + trigger first scan
|
|
# scripts/seed-demo-targets.sh --only web-juice-shop,git-cra-vuln-demo
|
|
# scripts/seed-demo-targets.sh --reset # delete every "Demo · " target
|
|
# scripts/seed-demo-targets.sh --reset --scan # reset, reseed, scan
|
|
#
|
|
# Env:
|
|
# AGENT_URL base URL of the agent (default http://localhost:3011)
|
|
# AGENT_TOKEN bearer token; omit for dev mode (no Keycloak → dev tenant)
|
|
# MANIFEST alternative manifest path
|
|
|
|
set -euo pipefail
|
|
|
|
AGENT_URL="${AGENT_URL:-http://localhost:3011}"
|
|
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
MANIFEST="${MANIFEST:-$ROOT/fixtures/demo-targets/targets.json}"
|
|
|
|
DO_SCAN=0
|
|
DO_RESET=0
|
|
ONLY=""
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--scan) DO_SCAN=1 ;;
|
|
--reset) DO_RESET=1 ;;
|
|
--only) ONLY="$2"; shift ;;
|
|
-h|--help) sed -n '2,25p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
*) echo "unknown arg: $1" >&2; exit 2 ;;
|
|
esac
|
|
shift
|
|
done
|
|
|
|
for bin in jq curl; do
|
|
command -v "$bin" >/dev/null || { echo "need $bin" >&2; exit 1; }
|
|
done
|
|
[[ -f "$MANIFEST" ]] || { echo "manifest not found: $MANIFEST" >&2; exit 1; }
|
|
|
|
AUTH=()
|
|
[[ -n "${AGENT_TOKEN:-}" ]] && AUTH=(-H "Authorization: Bearer ${AGENT_TOKEN}")
|
|
|
|
green() { printf '\033[32m%s\033[0m' "$*"; }
|
|
yellow() { printf '\033[33m%s\033[0m' "$*"; }
|
|
red() { printf '\033[31m%s\033[0m' "$*"; }
|
|
|
|
# api METHOD PATH [curl args...] → body on stdout; non-2xx → exit 1 with body.
|
|
api() {
|
|
local method="$1" path="$2"; shift 2
|
|
local out code
|
|
out=$(curl -sS -X "$method" "${AGENT_URL}${path}" "${AUTH[@]}" -w '\n%{http_code}' "$@")
|
|
code="${out##*$'\n'}"
|
|
out="${out%$'\n'*}"
|
|
if [[ "$code" != 2* ]]; then
|
|
echo "$(red "HTTP $code") $method $path" >&2
|
|
echo "$out" >&2
|
|
return 1
|
|
fi
|
|
printf '%s' "$out"
|
|
}
|
|
|
|
PREFIX="$(jq -r '.name_prefix' "$MANIFEST")"
|
|
|
|
reset_targets() {
|
|
echo "== reset: deleting targets named '${PREFIX}*'"
|
|
local list ids
|
|
list=$(api GET "/api/v1/targets?limit=500")
|
|
ids=$(jq -r --arg p "$PREFIX" '.data[] | select(.name | startswith($p)) | ._id."$oid"' <<<"$list")
|
|
local n=0
|
|
for id in $ids; do
|
|
api DELETE "/api/v1/targets/${id}" >/dev/null && n=$((n + 1))
|
|
done
|
|
echo " deleted $n"
|
|
}
|
|
|
|
# Build the JSON artifact list for reference-style (non-upload) artifacts.
|
|
# Prints one JSON object per artifact that should be created inline.
|
|
inline_artifacts() {
|
|
local target_json="$1"
|
|
jq -c '.artifacts[] | select(.upload == null and .upload_env == null)' <<<"$target_json" |
|
|
while IFS= read -r a; do
|
|
local kind env ref optional branch
|
|
kind=$(jq -r '.kind' <<<"$a")
|
|
env=$(jq -r '.source_ref_env // empty' <<<"$a")
|
|
ref=$(jq -r '.source_ref // empty' <<<"$a")
|
|
optional=$(jq -r '.optional // false' <<<"$a")
|
|
branch=$(jq -r '.branch // empty' <<<"$a")
|
|
if [[ -n "$env" && -n "${!env:-}" ]]; then
|
|
ref="${!env}"
|
|
elif [[ -n "$env" && "$optional" == "true" ]]; then
|
|
echo " $(yellow skip) $kind (set \$$env to include)" >&2
|
|
continue
|
|
fi
|
|
[[ -n "$ref" ]] || continue
|
|
jq -cn --arg k "$kind" --arg r "$ref" --arg b "$branch" \
|
|
'{kind:$k, source_ref:$r} + (if $b != "" then {branch:$b} else {} end)'
|
|
done
|
|
}
|
|
|
|
# Upload every `upload` / `upload_env` artifact of the target.
|
|
upload_artifacts() {
|
|
local id="$1" target_json="$2"
|
|
jq -c '.artifacts[] | select(.upload != null or .upload_env != null)' <<<"$target_json" |
|
|
while IFS= read -r a; do
|
|
local kind path env fmt optional
|
|
kind=$(jq -r '.kind' <<<"$a")
|
|
env=$(jq -r '.upload_env // empty' <<<"$a")
|
|
path=$(jq -r '.upload // empty' <<<"$a")
|
|
fmt=$(jq -r '.plc_format // empty' <<<"$a")
|
|
optional=$(jq -r '.optional // false' <<<"$a")
|
|
if [[ -n "$env" && -n "${!env:-}" ]]; then
|
|
path="${!env}"
|
|
elif [[ -n "$path" ]]; then
|
|
path="$ROOT/$path"
|
|
elif [[ "$optional" == "true" ]]; then
|
|
echo " $(yellow skip) $kind (set \$$env to include)" >&2
|
|
continue
|
|
fi
|
|
[[ -f "$path" ]] || { echo " $(red missing) $path" >&2; return 1; }
|
|
local form=(-F "file=@${path}" -F "kind=${kind}")
|
|
[[ -n "$fmt" ]] && form+=(-F "plc_format=${fmt}")
|
|
api POST "/api/v1/targets/${id}/artifacts/upload" "${form[@]}" >/dev/null
|
|
echo " $(green upload) $kind $(basename "$path")"
|
|
done
|
|
}
|
|
|
|
seed_target() {
|
|
local t="$1"
|
|
local key name type desc
|
|
key=$(jq -r '.key' <<<"$t")
|
|
name="${PREFIX}$(jq -r '.name' <<<"$t")"
|
|
type=$(jq -r '.target_type' <<<"$t")
|
|
desc=$(jq -r '.description // ""' <<<"$t")
|
|
echo "== $key ($type)"
|
|
|
|
local arts body resp id
|
|
arts=$(inline_artifacts "$t" | jq -cs '.')
|
|
body=$(jq -cn --arg n "$name" --arg tt "$type" --arg d "$desc" --argjson a "$arts" \
|
|
'{name:$n, target_type:$tt, description:$d, artifacts:$a}')
|
|
resp=$(api POST "/api/v1/targets" -H 'Content-Type: application/json' -d "$body")
|
|
id=$(jq -r '.data._id."$oid"' <<<"$resp")
|
|
echo " $(green created) $id $(jq -r '.data.artifacts|length' <<<"$resp") inline artifact(s)"
|
|
|
|
upload_artifacts "$id" "$t"
|
|
|
|
local det
|
|
det=$(api POST "/api/v1/targets/${id}/detect" -H 'Content-Type: application/json' -d '{}' || true)
|
|
if [[ -n "$det" ]]; then
|
|
echo " detect → $(jq -r '.data.classification.suggested // "n/a"' <<<"$det")"
|
|
fi
|
|
|
|
if [[ "$DO_SCAN" == 1 ]]; then
|
|
api POST "/api/v1/targets/${id}/scan" -H 'Content-Type: application/json' -d '{}' >/dev/null
|
|
echo " $(green scan) triggered"
|
|
fi
|
|
}
|
|
|
|
[[ "$DO_RESET" == 1 ]] && reset_targets
|
|
|
|
echo "== seeding from $MANIFEST → $AGENT_URL"
|
|
jq -c '.targets[]' "$MANIFEST" | while IFS= read -r t; do
|
|
key=$(jq -r '.key' <<<"$t")
|
|
if [[ -n "$ONLY" && ",$ONLY," != *",$key,"* ]]; then
|
|
continue
|
|
fi
|
|
seed_target "$t"
|
|
done
|
|
echo "== done"
|