#!/usr/bin/env bash
# Idempotently stand up the CVE-2025-1974 (ingress-nginx admission webhook RCE)
# reproduction environment on a local kind cluster.
#
# Substrate: kind (Kubernetes-in-Docker). A real Kubernetes control plane is
# required because the vulnerable surface is the ingress-nginx admission webhook
# (a ValidatingWebhookConfiguration backed by the controller), and the controller
# only runs when it can list/watch Ingresses against an API server.
#
# Self-contained: uses the run-local kind binary in env/bin and writes its
# kubeconfig to env/kubeconfig. It recreates the cluster from scratch so the
# state always matches the authored manifests (no drift from prior runs).
#
# This run's environment adds, vs. the prior run, a pre-existing referenceable
# auth-tls CA secret (key `ca.crt`) in namespace ingress-nginx. Without it,
# authtls.Parse() in v1.11.4 returns an empty Config and the injected ssl_engine
# directive (carried via auth-tls-match-cn) is silently dropped, so the bug
# cannot fire end-to-end. The secret is a benign, realistic cluster precondition
# (the IngressNightmare PoCs reference such a secret, e.g. calico-system/node-certs);
# it grants the unauthenticated attacker NO credential -- the attacker only
# reaches the webhook over the network and names an already-present secret.
set -euo pipefail

HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export PATH="$HERE/bin:$PATH"
export KUBECONFIG="$HERE/kubeconfig"

CLUSTER=cve-2025-1974
KCTL="kubectl --kubeconfig=$KUBECONFIG"

# The pre-existing auth-tls secret the injection references.
AUTH_SECRET_NS=ingress-nginx
AUTH_SECRET_NAME=ingressnightmare-auth-ca
CA_DIR="$HERE/ca"

echo "[up] Using kind: $(command -v kind)  ($(kind version))"

# --- 1. (Re)create a clean cluster ------------------------------------------
if kind get clusters 2>/dev/null | grep -qx "$CLUSTER"; then
  echo "[up] Deleting pre-existing kind cluster '$CLUSTER' for a clean rebuild..."
  kind delete cluster --name "$CLUSTER"
fi
echo "[up] Creating kind cluster '$CLUSTER'..."
kind create cluster --name "$CLUSTER" --config "$HERE/manifests/kind-cluster.yaml" --wait 120s
kind get kubeconfig --name "$CLUSTER" > "$KUBECONFIG"

# --- 2. Preload images into the node (avoid pull flakiness) -------------------
for IMG in \
  "registry.k8s.io/ingress-nginx/controller:v1.11.4" \
  "nginx:1.27.3-bookworm" ; do
  if docker image inspect "$IMG" >/dev/null 2>&1; then
    echo "[up] Loading cached image into node: $IMG"
    kind load docker-image "$IMG" --name "$CLUSTER" >/dev/null 2>&1 || \
      echo "[up]   (load skipped; node will pull $IMG from registry)"
  fi
done

# --- 3. Install the vulnerable ingress-nginx v1.11.4 -------------------------
echo "[up] Installing ingress-nginx controller v1.11.4 (vulnerable)..."
$KCTL apply -f "$HERE/manifests/ingress-nginx-v1.11.4.yaml"

echo "[up] Waiting for admission certgen jobs..."
$KCTL -n ingress-nginx wait --for=condition=complete job \
  -l app.kubernetes.io/component=admission-webhook --timeout=180s || true

echo "[up] Waiting for controller to become Ready..."
$KCTL -n ingress-nginx rollout status deploy/ingress-nginx-controller --timeout=300s
$KCTL -n ingress-nginx wait --for=condition=ready pod \
  -l app.kubernetes.io/component=controller --timeout=180s

# --- 4. Pre-seed the referenceable auth-tls CA secret ------------------------
# ingress-nginx v1.11.4 authtls.Parse() resolves auth-tls-secret via
# GetAuthCertificate() -> store/backend_ssl.go getPemCertificate(), which
# requires the referenced secret to carry key `ca.crt` (a valid PEM CA cert).
# Only then does Parse() render MatchCN (carrying the injected ssl_engine) into
# the generated NGINX config. We pre-create that secret here. It is a benign,
# realistic cluster precondition and grants the attacker no credential.
echo "[up] Generating + seeding auth-tls CA secret ${AUTH_SECRET_NS}/${AUTH_SECRET_NAME} (key ca.crt)..."
mkdir -p "$CA_DIR"
if [ ! -s "$CA_DIR/ca.crt" ]; then
  openssl req -x509 -newkey rsa:2048 -nodes \
    -keyout "$CA_DIR/ca.key" -out "$CA_DIR/ca.crt" \
    -subj "/CN=ingress-nightmare-auth-ca/O=cve-lab" -days 3650 >/dev/null 2>&1
fi
# Idempotent create-or-replace; the secret MUST contain a valid `ca.crt` PEM.
$KCTL -n "$AUTH_SECRET_NS" create secret generic "$AUTH_SECRET_NAME" \
  --from-file=ca.crt="$CA_DIR/ca.crt" \
  --dry-run=client -o yaml | $KCTL apply -f -
# Verify the controller's local SSL store actually ingests it (key ca.crt present).
$KCTL -n "$AUTH_SECRET_NS" get secret "$AUTH_SECRET_NAME" \
  -o jsonpath='{.data.ca\.crt}' | grep -q '.' \
  || { echo "[up] FAIL: auth-tls secret missing ca.crt"; exit 1; }
echo "[up]   seeded auth-tls secret: ${AUTH_SECRET_NS}/${AUTH_SECRET_NAME}  (SECRET_REF for the exploit)"

# --- 5. Stand up the fd-staging surface (live backend + staging Ingress) -----
echo "[up] Applying staging backend + staging Ingress (live upstream Endpoint)..."
$KCTL apply -f "$HERE/manifests/stage-backend.yaml"
$KCTL -n ingress-nginx rollout status deploy/stage-backend --timeout=180s
$KCTL -n ingress-nginx wait --for=condition=ready pod -l app=stage-backend --timeout=120s

echo "[up] Ensuring staging Ingress is admitted..."
for i in $(seq 1 30); do
  if $KCTL -n ingress-nginx get ingress stage >/dev/null 2>&1; then
    break
  fi
  $KCTL apply -f "$HERE/manifests/stage-backend.yaml" >/dev/null 2>&1 || true
  sleep 3
done

echo "[up] Waiting for the controller to render the stage.local upstream..."
for i in $(seq 1 30); do
  if $KCTL -n ingress-nginx get endpoints stage-backend \
       -o jsonpath='{.subsets[0].addresses[0].ip}' 2>/dev/null | grep -q '.'; then
    break
  fi
  sleep 2
done

echo "[up] Environment is up."
echo "[up]   ingress HTTP listener (fd-staging):  http://127.0.0.1:8080  (Host: stage.local)"
echo "[up]   admission webhook (injection):      https://127.0.0.1:8443/networking/v1/ingresses"
echo "[up]   auth-tls secret (SECRET_REF):       ${AUTH_SECRET_NS}/${AUTH_SECRET_NAME}"
echo "[up]   kubeconfig (verifier privileged ch): $KUBECONFIG"
