#!/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"