|
| 1 | +#!/usr/bin/env bash |
| 2 | +# ============================================================================= |
| 3 | +# Vyrox Simulator smoke test (CI-05) |
| 4 | +# ============================================================================= |
| 5 | +# |
| 6 | +# A fast, hermetic check that the public-facing simulator still emits a |
| 7 | +# correctly-shaped, signed request. It needs NO ingestion server: it sources |
| 8 | +# simulate.sh (which, thanks to the BASH_SOURCE guard, defines its functions |
| 9 | +# without dispatching), builds a real scenario payload, signs it the exact way |
| 10 | +# send_alert does, and asserts the signature shape the ingestion webhook |
| 11 | +# expects. |
| 12 | +# |
| 13 | +# What it proves: |
| 14 | +# 1. simulate.sh is syntactically valid (sh -n, also run in CI separately). |
| 15 | +# 2. build_payload produces valid JSON for a known scenario. |
| 16 | +# 3. build_signature produces a 64-char lowercase-hex HMAC-SHA256 digest. |
| 17 | +# 4. that digest matches an independent openssl computation (the signer is |
| 18 | +# doing real HMAC-SHA256 over the payload with the secret, not a stub). |
| 19 | +# 5. the wire header send_alert would set is "sha256=<hex>" (the prefix the |
| 20 | +# ingestion service strips), so the contract with the webhook holds. |
| 21 | +# |
| 22 | +# Exit 0 on success, non-zero (with a message) on the first failed assertion. |
| 23 | +# ============================================================================= |
| 24 | + |
| 25 | +set -euo pipefail |
| 26 | + |
| 27 | +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" |
| 28 | +SIM_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" |
| 29 | + |
| 30 | +fail() { |
| 31 | + echo "[FAIL] $*" >&2 |
| 32 | + exit 1 |
| 33 | +} |
| 34 | +ok() { |
| 35 | + echo "[OK] $*" |
| 36 | +} |
| 37 | + |
| 38 | +# A throwaway test secret (NOT a real credential): the smoke test only checks |
| 39 | +# the signature SHAPE, it never reaches a server, so any value works. |
| 40 | +TEST_SECRET="smoke-test-secret-not-a-real-credential" |
| 41 | +TEST_TENANT="smoke-tenant" |
| 42 | + |
| 43 | +# 1. Syntax check (cheap, also gives a clear message if the script is broken). |
| 44 | +sh -n "${SIM_ROOT}/simulate.sh" || fail "simulate.sh failed syntax check (sh -n)" |
| 45 | +ok "simulate.sh passes syntax check" |
| 46 | + |
| 47 | +# Source the simulator. The BASH_SOURCE guard in simulate.sh means main does |
| 48 | +# NOT run on source, so this defines build_payload/build_signature/etc with no |
| 49 | +# network side effects. The scenario scripts define build_payload; source the |
| 50 | +# one we test, same as run_scenario does. |
| 51 | +# shellcheck source=/dev/null |
| 52 | +source "${SIM_ROOT}/simulate.sh" |
| 53 | +# shellcheck source=/dev/null |
| 54 | +source "${SIM_ROOT}/scenarios/mimikatz.sh" |
| 55 | + |
| 56 | +# 2. build_payload produces valid JSON. |
| 57 | +payload="$(build_payload "${TEST_TENANT}")" |
| 58 | +[[ -n "${payload}" ]] || fail "build_payload returned an empty payload" |
| 59 | +echo "${payload}" | python3 -m json.tool >/dev/null 2>&1 \ |
| 60 | + || fail "build_payload did not produce valid JSON" |
| 61 | +ok "build_payload emits valid JSON for the mimikatz scenario" |
| 62 | + |
| 63 | +# 3 + 4. build_signature produces a 64-char lowercase-hex digest that matches |
| 64 | +# an independent HMAC-SHA256 computation over the same bytes. |
| 65 | +signature="$(build_signature "${payload}" "${TEST_SECRET}")" |
| 66 | +[[ "${signature}" =~ ^[0-9a-f]{64}$ ]] \ |
| 67 | + || fail "build_signature output is not a 64-char lowercase-hex digest: '${signature}'" |
| 68 | +ok "build_signature emits a 64-char hex HMAC-SHA256 digest" |
| 69 | + |
| 70 | +expected="$(printf '%s' "${payload}" | openssl dgst -sha256 -hmac "${TEST_SECRET}" | sed 's/^.* //')" |
| 71 | +[[ "${signature}" == "${expected}" ]] \ |
| 72 | + || fail "build_signature digest does not match an independent openssl HMAC-SHA256" |
| 73 | +ok "signature matches an independent HMAC-SHA256 computation" |
| 74 | + |
| 75 | +# 5. The wire header send_alert sets is "sha256=<hex>": the prefix the |
| 76 | +# ingestion webhook strips before verifying. |
| 77 | +header_value="sha256=${signature}" |
| 78 | +[[ "${header_value}" =~ ^sha256=[0-9a-f]{64}$ ]] \ |
| 79 | + || fail "X-Vyrox-Signature header is not 'sha256=<64-hex>': '${header_value}'" |
| 80 | +ok "X-Vyrox-Signature header has the expected 'sha256=<hex>' shape" |
| 81 | + |
| 82 | +echo "[PASS] simulator smoke test: payload + signed-request shape is correct" |
0 commit comments