#!/usr/bin/env bash
# Onehouse egress check: run from a host in the data plane private subnets before
# deploying the customer stack, to confirm the egress firewall allows every endpoint
# the data plane needs. Read-only: it only opens outbound TLS connections.
#
# Usage:
#   bash onehouse-egress-check.sh --cloud aws [--region us-east-1]
#   bash onehouse-egress-check.sh --cloud gcp
#   bash onehouse-egress-check.sh --cloud azure
# Options:
#   --cloud aws|gcp|azure      Cloud the data plane runs in (required)
#   --region <region>          AWS region of the data plane (default: $AWS_REGION or us-east-1)
#   --control-plane aws|gcp    Onehouse control plane serving your project (default: aws;
#                              your Onehouse contact tells you if it is gcp)
#   --extra <host>:<port>      Also check this endpoint (repeatable), e.g. a Kafka broker
#   --timeout <seconds>        Per-endpoint timeout (default: 8)
#
# Exit code is 0 when every endpoint is reachable, 1 otherwise.

set -u

CLOUD=""
REGION="${AWS_REGION:-us-east-1}"
CONTROL_PLANE="aws"
TIMEOUT=8
EXTRA=()

usage() { sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'; exit 2; }

while [ $# -gt 0 ]; do
  case "$1" in
    --cloud) CLOUD="${2:-}"; shift 2 ;;
    --region) REGION="${2:-}"; shift 2 ;;
    --control-plane) CONTROL_PLANE="${2:-}"; shift 2 ;;
    --extra) EXTRA+=("${2:-}"); shift 2 ;;
    --timeout) TIMEOUT="${2:-}"; shift 2 ;;
    -h|--help) usage ;;
    *) echo "Unknown argument: $1" >&2; usage ;;
  esac
done

case "$CLOUD" in aws|gcp|azure) ;; *) echo "--cloud must be aws, gcp or azure" >&2; usage ;; esac
case "$CONTROL_PLANE" in aws|gcp) ;; *) echo "--control-plane must be aws or gcp" >&2; usage ;; esac
command -v curl >/dev/null 2>&1 || { echo "curl is required" >&2; exit 2; }

# Each entry: "<host> <port> <allowlist entry it exercises>"
CHECKS=(
  "cloud.onehouse.ai 443 .onehouse.ai"
  "dist.onehouse.ai 443 .onehouse.ai"
  "registry-1.docker.io 443 .docker.io"
  "auth.docker.io 443 .docker.io"
  "index.docker.io 443 .docker.io"
  "production.cloudflare.docker.com 443 production.cloudflare.docker.com"
  "production.cloudfront.docker.com 443 production.cloudfront.docker.com"
  "docker-images-prod.6aa30f8b08e16409b46e0173d6de2f56.r2.cloudflarestorage.com 443 docker-images-prod...r2.cloudflarestorage.com"
  "docker-registry-production.d24a988e385e0074d717b6bdaea58f0d.r2.cloudflarestorage.com 443 docker-registry-production...r2.cloudflarestorage.com"
  "gcr.io 443 .gcr.io"
  "us-docker.pkg.dev 443 .pkg.dev"
  "storage.googleapis.com 443 .googleapis.com"
  "registry.k8s.io 443 .k8s.io"
  "dl.k8s.io 443 .k8s.io"
  "ghcr.io 443 ghcr.io"
  "pkg-containers.githubusercontent.com 443 .githubusercontent.com"
  "quay.io 443 .quay.io"
  "cdn01.quay.io 443 .quay.io"
  "registry.terraform.io 443 registry.terraform.io"
  "releases.hashicorp.com 443 releases.hashicorp.com"
  "get.helm.sh 443 get.helm.sh"
  "github.com 443 github.com"
  "api.github.com 443 .github.com"
  "raw.githubusercontent.com 443 .githubusercontent.com"
  "objects.githubusercontent.com 443 .githubusercontent.com"
  "kubernetes-sigs.github.io 443 .github.io"
  "strimzi.io 443 .strimzi.io"
  "events.pagerduty.com 443 .pagerduty.com"
)

# FRP tunnel to the control plane. The AWS control plane also needs TCP 7202.
if [ "$CONTROL_PLANE" = "aws" ]; then
  CHECKS+=("default-frp-connect.onehouse.ai 443 .onehouse.ai (FRP tunnel)")
  CHECKS+=("default-frp-connect.onehouse.ai 7202 .onehouse.ai (FRP tunnel, TCP 7202)")
else
  CHECKS+=("default-frp-connect-gcp.onehouse.ai 443 .onehouse.ai (FRP tunnel)")
fi

case "$CLOUD" in
  aws)
    CHECKS+=("sts.${REGION}.amazonaws.com 443 .amazonaws.com")
    CHECKS+=("ecr.${REGION}.amazonaws.com 443 .amazonaws.com")
    CHECKS+=("eks.${REGION}.amazonaws.com 443 .amazonaws.com")
    CHECKS+=("ecr.us-west-1.amazonaws.com 443 .amazonaws.com")
    CHECKS+=("public.ecr.aws 443 .ecr.aws")
    ;;
  gcp)
    CHECKS+=("container.googleapis.com 443 .googleapis.com")
    ;;
  azure)
    CHECKS+=("management.azure.com 443 .azure.com")
    CHECKS+=("login.microsoftonline.com 443 login.microsoftonline.com")
    ;;
esac

for e in ${EXTRA[@]+"${EXTRA[@]}"}; do
  CHECKS+=("${e%:*} ${e##*:} --extra")
done

# -k: this tests reachability, not certificate trust, so a TLS-inspecting proxy
# still reports OPEN. Any HTTP status (even 4xx) means the firewall let it through.
probe() {
  local host="$1" port="$2" out rc code
  out=$(curl -k -v -o /dev/null --connect-timeout "$TIMEOUT" --max-time "$TIMEOUT" \
        -w 'HTTPCODE=%{http_code}' "https://${host}:${port}/" 2>&1)
  rc=$?
  code="${out##*HTTPCODE=}"
  if [ "$code" != "000" ] && [ -n "$code" ]; then echo "OPEN|HTTP=${code}"; return; fi
  # The Onehouse FRP endpoint requires a client certificate, so it ends the handshake
  # after its Server Hello. Reaching that far means the firewall allowed the connection.
  if echo "$out" | grep -qi "server hello"; then
    echo "OPEN|TLS handshake reached the server (client certificate required)"; return
  fi
  case "$rc" in
    6) echo "NO_DNS|could not resolve host" ;;
    7) echo "REFUSED|connection refused" ;;
    28) echo "TIMEOUT|no response in ${TIMEOUT}s (likely dropped by a firewall)" ;;
    35|56) echo "RESET|connection reset during TLS (likely blocked by a firewall)" ;;
    *) echo "FAILED|curl exit ${rc}: $(echo "$out" | grep -m1 '^curl:')" ;;
  esac
}

printf '%-8s %-6s %-60s %s\n' RESULT PORT HOST "ALLOWLIST ENTRY / DETAIL"
open=0; blocked=0; failed_list=()
for c in "${CHECKS[@]}"; do
  host="${c%% *}"; rest="${c#* }"; port="${rest%% *}"; entry="${rest#* }"
  res=$(probe "$host" "$port")
  status="${res%%|*}"; detail="${res#*|}"
  printf '%-8s %-6s %-60s %s  %s\n' "$status" "$port" "$host" "$entry" "$detail"
  if [ "$status" = "OPEN" ]; then
    open=$((open + 1))
  else
    blocked=$((blocked + 1)); failed_list+=("${host}:${port} (TCP)")
  fi
done

echo
echo "SUMMARY open=${open} not_open=${blocked}"
if [ "$blocked" -gt 0 ]; then
  echo "Ask your network team to allow outbound TCP to these endpoints from the data plane subnets:"
  for f in "${failed_list[@]}"; do echo "  - $f"; done
  exit 1
fi
echo "All endpoints reachable."
