#!/usr/bin/env bash
#
# A3 real nginx+njs integration acceptance (FIX C-native-3 / PLAN §1.2 B1 step 4).
#
# Proves the SINGLE load-bearing tenant-isolation control on a REAL nginx with the REAL njs module
# and the REAL getCertSub.js, using device-CA-signed client certs whose dNSName SAN is stamped
# exactly as the B1/A4 issuers stamp it:
#
#   POSITIVE : alice client cert  → https://alice.terminal.yaojia.wang  → 200   (own host)
#   NEGATIVE : bob   client cert  → https://alice.terminal.yaojia.wang  → 403   (cross-tenant)
#   NEGATIVE : alice client cert  → https://bob.terminal.yaojia.wang    → 403   (cross-tenant)
#   POSITIVE : bob   client cert  → https://bob.terminal.yaojia.wang    → 200
#   NEGATIVE : NO client cert     → https://alice.terminal.yaojia.wang  → 400   (ssl_verify_client on)
#
# Requires: docker + openssl. If docker is unavailable, this same script is the VPS/CI acceptance
# step (run it there). Exit 0 iff every assertion holds.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
NJS_FILE="$(cd "$SCRIPT_DIR/../../njs" && pwd)/getCertSub.js"
CONF="$SCRIPT_DIR/nginx.test.conf"
IMAGE="wt-nginx-njs:a3-test"
CONTAINER="wt-a3-nginx-$$"
HOST_PORT="${HOST_PORT:-18470}"
WORK="$(mktemp -d)"

cleanup() {
  docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
  rm -rf "$WORK"
}
trap cleanup EXIT

echo "==> workdir: $WORK"
echo "==> njs source under test: $NJS_FILE"

# ---------------------------------------------------------------------------
# 1. Mint the device CA, a server cert, and two client leaves with dNSName SANs
# ---------------------------------------------------------------------------
gen_ca() {
  openssl ecparam -name prime256v1 -genkey -noout -out "$WORK/ca.key"
  openssl req -x509 -new -key "$WORK/ca.key" -sha256 -days 1 -subj "/CN=device-CA" \
    -addext "basicConstraints=critical,CA:TRUE" \
    -addext "keyUsage=critical,keyCertSign,cRLSign" \
    -out "$WORK/ca.crt"
}

gen_server() {
  openssl ecparam -name prime256v1 -genkey -noout -out "$WORK/server.key"
  openssl req -x509 -new -key "$WORK/server.key" -sha256 -days 1 \
    -subj "/CN=*.terminal.yaojia.wang" \
    -addext "subjectAltName=DNS:*.terminal.yaojia.wang" \
    -out "$WORK/server.crt"
}

# gen_client <name> <subdomain>  → mints <name>.key/.crt with SAN dNSName <subdomain>.terminal...
gen_client() {
  local name="$1" sub="$2"
  openssl ecparam -name prime256v1 -genkey -noout -out "$WORK/$name.key"
  openssl req -new -key "$WORK/$name.key" -subj "/CN=$sub" -out "$WORK/$name.csr"
  openssl x509 -req -in "$WORK/$name.csr" -CA "$WORK/ca.crt" -CAkey "$WORK/ca.key" \
    -CAcreateserial -days 1 -sha256 \
    -extfile <(printf 'subjectAltName=DNS:%s.terminal.yaojia.wang,URI:spiffe://terminal.yaojia.wang/account/a1/host/%s\nextendedKeyUsage=clientAuth\nbasicConstraints=critical,CA:FALSE\n' "$sub" "$sub") \
    -out "$WORK/$name.crt"
}

echo "==> minting CA + server + client (alice, bob) certs"
gen_ca
gen_server
gen_client alice alice
gen_client bob bob

# ---------------------------------------------------------------------------
# 2. Build the nginx+njs image and start the container
# ---------------------------------------------------------------------------
echo "==> docker build ($IMAGE)"
docker build -q -t "$IMAGE" "$SCRIPT_DIR" >/dev/null

echo "==> docker run ($CONTAINER) on :$HOST_PORT"
docker rm -f "$CONTAINER" >/dev/null 2>&1 || true
docker run -d --name "$CONTAINER" \
  -p "$HOST_PORT:8470" \
  -v "$NJS_FILE:/etc/nginx/njs/getCertSub.js:ro" \
  -v "$CONF:/etc/nginx/nginx.conf:ro" \
  -v "$WORK:/certs:ro" \
  "$IMAGE" >/dev/null

# nginx -t inside the running image (config sanity on the real binary)
echo "==> nginx -t (real binary)"
docker exec "$CONTAINER" nginx -t

# wait for readiness
for i in $(seq 1 30); do
  if curl -sk -o /dev/null "https://alice.terminal.yaojia.wang:$HOST_PORT/" \
       --resolve "alice.terminal.yaojia.wang:$HOST_PORT:127.0.0.1" \
       --cert "$WORK/alice.crt" --key "$WORK/alice.key" 2>/dev/null; then
    break
  fi
  sleep 0.3
done

# ---------------------------------------------------------------------------
# 3. Assert positive (200) and negative (403 / 400) cases
# ---------------------------------------------------------------------------
# hit <host> <clientname|-> → prints HTTP status code
hit() {
  local host="$1" client="$2"
  local args=(-sk -o /dev/null -w '%{http_code}'
    --resolve "$host:$HOST_PORT:127.0.0.1"
    "https://$host:$HOST_PORT/")
  if [ "$client" != "-" ]; then
    args+=(--cert "$WORK/$client.crt" --key "$WORK/$client.key")
  fi
  curl "${args[@]}" || echo "000"
}

FAILED=0
assert() {
  local label="$1" expected="$2" actual="$3"
  if [ "$actual" = "$expected" ]; then
    echo "  PASS  $label → $actual"
  else
    echo "  FAIL  $label → expected $expected, got $actual"
    FAILED=1
  fi
}

echo "==> assertions"
assert "POSITIVE alice-cert @ alice host" 200 "$(hit alice.terminal.yaojia.wang alice)"
assert "NEGATIVE bob-cert   @ alice host" 403 "$(hit alice.terminal.yaojia.wang bob)"
assert "NEGATIVE alice-cert @ bob host"   403 "$(hit bob.terminal.yaojia.wang   alice)"
assert "POSITIVE bob-cert   @ bob host"   200 "$(hit bob.terminal.yaojia.wang   bob)"
assert "NEGATIVE no-cert     @ alice host" 400 "$(hit alice.terminal.yaojia.wang -)"

if [ "$FAILED" -ne 0 ]; then
  echo "==> A3 INTEGRATION FAILED"
  docker logs "$CONTAINER" 2>&1 | tail -20 || true
  exit 1
fi
echo "==> A3 INTEGRATION PASSED (positive 200 + cross-tenant 403 on real nginx+njs)"
