#!/usr/bin/env bash
# ca-init.sh — Generate the broad-announce internal PKI: an offline root
# CA and an intermediate CA ready to be imported into cert-manager as a
# ClusterIssuer. Run once per environment (dev, staging, prod). The
# root key/cert stay offline; only the intermediate is imported into K8s.
#
# Usage:
#   scripts/cert-manager/ca-init.sh <env> [<output-dir>]
#
# Examples:
#   scripts/cert-manager/ca-init.sh dev
#   scripts/cert-manager/ca-init.sh prod /secure/pki
#
# Outputs (under <output-dir>/<env>/):
#   root-ca.key         — root CA private key (4096-bit RSA). KEEP OFFLINE.
#   root-ca.crt         — root CA certificate (10y)
#   intermediate-ca.key — intermediate CA private key (ECDSA P-256). Goes into K8s.
#   intermediate-ca.crt — intermediate CA certificate (1y, signed by root)
#   intermediate-ca-chain.pem — intermediate + root, for serving cert chain
#   ca-bundle.json      — metadata for cert-manager (k8s secret manifest + JSON)
#
# Security:
#   - The root key is the trust anchor. If it leaks, the entire PKI is
#     compromised. Generate it on an air-gapped machine or encrypted USB.
#   - The intermediate key is what cert-manager uses. It can be in K8s
#     but should be sealed-secrets / external-secrets encrypted at rest.
#   - This script prompts for an AES-256 passphrase to encrypt both
#     private keys on disk. Use a password manager. Do NOT commit.
#
# Requires: openssl 3.x, jq (for ca-bundle.json).

set -euo pipefail

ENV_NAME="${1:-}"
OUTPUT_DIR="${2:-./pki}"

if [[ -z "$ENV_NAME" ]]; then
  echo "usage: $0 <env> [<output-dir>]" >&2
  echo "  env: dev, staging, prod (or any tag you want to distinguish)" >&2
  exit 1
fi

# Pre-flight
for bin in openssl jq; do
  if ! command -v "$bin" >/dev/null 2>&1; then
    echo "FATAL: $bin not found in PATH" >&2
    exit 1
  fi
done

OUT="$OUTPUT_DIR/$ENV_NAME"
mkdir -p "$OUT"
chmod 700 "$OUT"

echo "=== broad-announce CA init ==="
echo "env:        $ENV_NAME"
echo "output dir: $OUT"
echo
echo "WARNING: the root key generated here is the trust anchor for the"
echo "entire PKI. Store it offline (encrypted USB, air-gapped machine)."
echo "Anyone with this key can mint certificates your systems will trust."
echo

# --- Root CA (offline) -----------------------------------------------------

ROOT_KEY="$OUT/root-ca.key"
ROOT_CRT="$OUT/root-ca.crt"

if [[ -f "$ROOT_KEY" && -f "$ROOT_CRT" ]]; then
  echo "Root CA already exists at $OUT — refusing to overwrite."
  echo "Move/delete the existing dir first, or pass a fresh output dir."
  exit 1
fi

echo "[1/4] Generating root CA (RSA 4096, 10y, AES-256 encrypted on disk)..."

# Passphrase strategy:
#   - If BA_CA_PASSPHRASE is set (env), use it. This is the dev/test path
#     and the CI path. NEVER set it in prod — use the interactive prompt
#     below or load it from a hardware token.
#   - If unset, openssl will prompt interactively (the correct prod path).
if [[ -n "${BA_CA_PASSPHRASE:-}" ]]; then
  openssl genrsa -aes-256-cbc -passout "env:BA_CA_PASSPHRASE" -out "$ROOT_KEY" 4096 2>/dev/null
else
  openssl genrsa -aes-256-cbc -out "$ROOT_KEY" 4096
fi
chmod 600 "$ROOT_KEY"

if [[ -n "${BA_CA_PASSPHRASE:-}" ]]; then
  ROOT_PASS_ARGS=(-passin "env:BA_CA_PASSPHRASE")
else
  ROOT_PASS_ARGS=()
fi
openssl req -new -x509 "${ROOT_PASS_ARGS[@]}" -key "$ROOT_KEY" -sha384 -days 3650 \
  -subj "/CN=broad-announce Root CA ($ENV_NAME)/O=broad-announce/OU=PKI" \
  -addext "basicConstraints=critical,CA:TRUE" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" \
  -addext "subjectKeyIdentifier=hash" \
  -out "$ROOT_CRT"
chmod 644 "$ROOT_CRT"

# --- Intermediate CA (in-cluster) ------------------------------------------

INT_KEY="$OUT/intermediate-ca.key"
INT_CRT="$OUT/intermediate-ca.crt"
INT_CSR="$OUT/intermediate-ca.csr"
INT_CHAIN="$OUT/intermediate-ca-chain.pem"

echo "[2/4] Generating intermediate CA (ECDSA P-256, 1y)..."
openssl ecparam -name prime256v1 -genkey -noout -out "$INT_KEY"
chmod 600 "$INT_KEY"

openssl req -new -sha256 -key "$INT_KEY" \
  -subj "/CN=broad-announce Intermediate CA ($ENV_NAME)/O=broad-announce/OU=PKI" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign,digitalSignature" \
  -addext "extendedKeyUsage=serverAuth,clientAuth" \
  -addext "subjectKeyIdentifier=hash" \
  -out "$INT_CSR"

echo "[3/4] Signing intermediate with root (1y validity)..."
openssl x509 -req -in "$INT_CSR" -CA "$ROOT_CRT" -CAkey "$ROOT_KEY" "${ROOT_PASS_ARGS[@]}" \
  -CAcreateserial -sha384 -days 365 \
  -extfile <(cat <<'EOF'
basicConstraints=critical,CA:TRUE,pathlen:0
keyUsage=critical,keyCertSign,cRLSign,digitalSignature
extendedKeyUsage=serverAuth,clientAuth
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid:always
EOF
) \
  -out "$INT_CRT"
chmod 644 "$INT_CRT"

# Bundle: intermediate + root, for serving cert chain validation
cat "$INT_CRT" "$ROOT_CRT" > "$INT_CHAIN"
chmod 644 "$INT_CHAIN"

# --- Metadata for cert-manager ---------------------------------------------

echo "[4/4] Writing ca-bundle.json for cert-manager import..."
ROOT_FP=$(openssl x509 -in "$ROOT_CRT" -noout -fingerprint -sha256 | cut -d'=' -f2)
INT_FP=$(openssl x509 -in "$INT_CRT" -noout -fingerprint -sha256 | cut -d'=' -f2)
INT_SERIAL=$(openssl x509 -in "$INT_CRT" -noout -serial | cut -d'=' -f2)
INT_NOT_AFTER=$(openssl x509 -in "$INT_CRT" -noout -enddate | cut -d'=' -f2)
ROOT_NOT_AFTER=$(openssl x509 -in "$ROOT_CRT" -noout -enddate | cut -d'=' -f2)

cat > "$OUT/ca-bundle.json" <<EOF
{
  "env": "$ENV_NAME",
  "generated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "root": {
    "cert_pem": "$ROOT_CRT",
    "fingerprint_sha256": "$ROOT_FP",
    "not_after": "$ROOT_NOT_AFTER",
    "path": "$(realpath "$ROOT_CRT")"
  },
  "intermediate": {
    "key_pem": "$INT_KEY",
    "cert_pem": "$INT_CRT",
    "chain_pem": "$INT_CHAIN",
    "fingerprint_sha256": "$INT_FP",
    "serial_hex": "$INT_SERIAL",
    "not_after": "$INT_NOT_AFTER",
    "path": "$(realpath "$INT_CRT")"
  },
  "next_steps": [
    "1. Move $ROOT_KEY and $ROOT_CRT to offline storage. Delete from this machine if not air-gapped.",
    "2. Import $INT_KEY, $INT_CRT, and $ROOT_CRT into a K8s Secret sealed-secrets (or external-secrets).",
    "3. Apply the cert-manager ClusterIssuer manifest (deploy/cert-manager/cluster-issuer-$ENV_NAME.yaml).",
    "4. Issue serving certs: kubectl apply -f deploy/cert-manager/serving-certs.yaml",
    "5. Run scripts/cert-manager/ca-rotate-intermediate.sh in ~364 days to rotate."
  ]
}
EOF

chmod 644 "$OUT/ca-bundle.json"

echo
echo "=== done ==="
echo "files:"
ls -la "$OUT"
echo
echo "rotated-by: $(date -u -d '+365 days' +%Y-%m-%d 2>/dev/null || date -u -v+365d +%Y-%m-%d)"
echo "ROOT FINGERPRINT (verify before trusting): $ROOT_FP"
echo "INTERMEDIATE FINGERPRINT:                    $INT_FP"
echo
echo "next: import the intermediate into cert-manager — see ca-bundle.json"
