Node.js daemon: Ed25519 identity + self-signed TLS, signed registration/ renewal with the central registry, mDNS advertising, disk-backed fetch-through asset cache with an origin allowlist. Includes a local dev-registry stand-in for the real oxmc.me endpoints and an end-to-end smoke test (register -> token -> cache miss/hit -> SSRF rejection). See README.md for the full protocol and trust model.
126 lines
5.2 KiB
Bash
126 lines
5.2 KiB
Bash
#!/usr/bin/env bash
|
|
# End-to-end local smoke test: dev-registry + pawletcache-server + a throwaway
|
|
# HTTPS origin, wired together, verifying register -> signed token -> asset
|
|
# fetch-through -> disk cache actually works. No real oxmc.me dependency.
|
|
#
|
|
# Requires: `npm install` already run in pawletcache-server/ (bonjour-service,
|
|
# js-yaml, selfsigned). dev-registry/ has zero deps, nothing to install there.
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.." # pawletcache-server/
|
|
|
|
ORIGIN_PORT=9443
|
|
REGISTRY_PORT=4100
|
|
CACHE_PORT=8543
|
|
ENROLLMENT_TOKEN=smoke-test-token
|
|
# Path handling here is fussy on Windows+Git-Bash: native Windows node.exe
|
|
# doesn't understand MSYS-style POSIX paths at all (not just /tmp — `pwd`'s
|
|
# /c/Users/... gets misread as literal drive-root segments, e.g. resolves to
|
|
# C:\c\Users\...). `pwd -W` gives the Windows-style form (C:/Users/...),
|
|
# which both node.exe and Bash/`ls` resolve to the same real location — use
|
|
# that for anything written into a file Node will read a path back out of.
|
|
# (On real POSIX systems `pwd -W` doesn't exist; fall back to plain pwd.)
|
|
WORKDIR="$(pwd -W 2>/dev/null || pwd)/.smoke-test-tmp-$$"
|
|
mkdir -p "$WORKDIR"
|
|
|
|
PIDS=()
|
|
cleanup() {
|
|
for pid in "${PIDS[@]:-}"; do kill "$pid" 2>/dev/null || true; done
|
|
rm -rf "$WORKDIR"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
pass() { echo "PASS: $1"; }
|
|
fail() { echo "FAIL: $1"; cleanup; exit 1; }
|
|
|
|
wait_for() {
|
|
local url=$1 tries=${2:-30}
|
|
for _ in $(seq 1 "$tries"); do
|
|
curl -sk -o /dev/null "$url" && return 0
|
|
sleep 0.5
|
|
done
|
|
return 1
|
|
}
|
|
|
|
echo "== starting dummy HTTPS origin on :$ORIGIN_PORT =="
|
|
node scripts/dummy-origin.js "$ORIGIN_PORT" > "$WORKDIR/origin.log" 2>&1 &
|
|
PIDS+=($!)
|
|
wait_for "https://localhost:$ORIGIN_PORT/" || fail "dummy origin never came up"
|
|
|
|
echo "== starting dev-registry on :$REGISTRY_PORT =="
|
|
DEV_REGISTRY_PORT=$REGISTRY_PORT \
|
|
DEV_REGISTRY_DATA_DIR="$WORKDIR/registry-data" \
|
|
DEV_ENROLLMENT_TOKENS=$ENROLLMENT_TOKEN \
|
|
DEV_TOKEN_TTL_MS=600000 \
|
|
node dev-registry/src/index.js > "$WORKDIR/registry.log" 2>&1 &
|
|
PIDS+=($!)
|
|
wait_for "http://localhost:$REGISTRY_PORT/health" || fail "dev-registry never came up"
|
|
pass "dev-registry up"
|
|
|
|
echo "== writing pawletcache-server config =="
|
|
cat > "$WORKDIR/config.yml" <<EOF
|
|
hostname: localhost
|
|
listenAddr: "0.0.0.0"
|
|
listenPort: $CACHE_PORT
|
|
storageDir: $WORKDIR/assets
|
|
identityDir: $WORKDIR/identity
|
|
centralUrl: http://localhost:$REGISTRY_PORT
|
|
enrollmentToken: $ENROLLMENT_TOKEN
|
|
allowedOrigins:
|
|
- localhost
|
|
EOF
|
|
|
|
echo "== starting pawletcache-server on :$CACHE_PORT =="
|
|
# dummy-origin.js is self-signed on purpose (see its header comment) — cache.js
|
|
# rightly rejects that for a real origin, so this smoke test alone tells
|
|
# Node's fetch() to skip TLS verification. Never do this outside a scoped
|
|
# local test — cache.js's own code has no such override.
|
|
NODE_TLS_REJECT_UNAUTHORIZED=0 node src/index.js "$WORKDIR/config.yml" > "$WORKDIR/cache-server.log" 2>&1 &
|
|
PIDS+=($!)
|
|
wait_for "https://localhost:$CACHE_PORT/asset" || fail "pawletcache-server never came up"
|
|
|
|
echo "== waiting for registration to land in dev-registry =="
|
|
registered=false
|
|
for _ in $(seq 1 20); do
|
|
lookup=$(curl -s "http://localhost:$REGISTRY_PORT/apis/aosp/cache/lookup")
|
|
if echo "$lookup" | grep -q '"available":true'; then
|
|
registered=true
|
|
break
|
|
fi
|
|
sleep 0.5
|
|
done
|
|
[ "$registered" = true ] || fail "pawletcache-server never registered with dev-registry (see $WORKDIR/cache-server.log, $WORKDIR/registry.log)"
|
|
pass "pawletcache-server registered, dev-registry issued a signed token"
|
|
|
|
echo "== verifying the CacheToken carries the right lanHost/port =="
|
|
token=$(node -e "console.log(JSON.parse(process.argv[1]).token)" "$lookup")
|
|
node -e "
|
|
const env = JSON.parse(Buffer.from(process.argv[1], 'base64').toString('utf8'));
|
|
const p = env.payload;
|
|
if (p.hostname !== 'localhost') throw new Error('hostname mismatch: ' + p.hostname);
|
|
if (p.port !== String($CACHE_PORT)) throw new Error('port mismatch: ' + p.port);
|
|
if (!p.tlsSpkiSha256) throw new Error('missing tlsSpkiSha256');
|
|
console.log('token payload OK:', JSON.stringify(p));
|
|
" "$token" || fail "CacheToken payload didn't look right"
|
|
pass "CacheToken payload shape OK"
|
|
|
|
echo "== fetching an asset through the cache (miss, then hit) =="
|
|
origin_url="https%3A%2F%2Flocalhost%3A${ORIGIN_PORT}%2Ffile"
|
|
body1=$(curl -sk "https://localhost:$CACHE_PORT/asset?url=$origin_url")
|
|
[ "$body1" = "hello from dummy origin" ] || fail "first fetch-through returned wrong body: '$body1'"
|
|
pass "cache miss fetch-through returned correct content"
|
|
|
|
body2=$(curl -sk "https://localhost:$CACHE_PORT/asset?url=$origin_url")
|
|
[ "$body2" = "hello from dummy origin" ] || fail "second (cached) fetch returned wrong body: '$body2'"
|
|
pass "cache hit returned correct content"
|
|
|
|
[ -n "$(ls -A "$WORKDIR/assets" 2>/dev/null)" ] || fail "nothing landed in storageDir — cache didn't persist to disk"
|
|
pass "asset persisted to disk cache"
|
|
|
|
echo "== verifying the SSRF allowlist actually blocks non-allowlisted origins =="
|
|
status=$(curl -sk -o /dev/null -w '%{http_code}' "https://localhost:$CACHE_PORT/asset?url=https%3A%2F%2Fevil.example%2Ffile")
|
|
[ "$status" = "403" ] || fail "expected 403 for a non-allowlisted origin, got $status"
|
|
pass "non-allowlisted origin correctly rejected (403)"
|
|
|
|
echo ""
|
|
echo "ALL CHECKS PASSED"
|