#!/usr/bin/env bash
# ==============================================================================
# Elastic Stack Multi-Server Certificate Renewal & SCP Sync (Bash)
# ==============================================================================
# Handles dual-server topology:
#   - Local Server: Runs Elasticsearch & CA Generation
#   - Remote Server: Runs Kibana and Elastic Agent (Fleet Server)
# SCPs certificates to the remote Kibana host, updates remote kibana.yml,
# restarts remote services, and re-enrolls the remote Fleet Server Elastic Agent.
# ==============================================================================

set -euo pipefail

# Ensure script is run as root
if [ "$EUID" -ne 0 ]; then
  echo -e "\033[1;31mERROR: Please run this script as root (using sudo)\033[0m" >&2
  exit 1
fi

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PYTHON_SCRIPT="$SCRIPT_DIR/update-certs-remote.py"

if command -v python3 >/dev/null 2>&1 && [ -f "$PYTHON_SCRIPT" ]; then
  exec python3 "$PYTHON_SCRIPT" "$@"
fi

# Color & formatting definitions
RED='\033[0;31m'
BOLD_RED='\033[1;31m'
GREEN='\033[0;32m'
BOLD_GREEN='\033[1;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
BOLD_BLUE='\033[1;34m'
CYAN='\033[0;36m'
BOLD_CYAN='\033[1;36m'
DIM='\033[2m'
RESET='\033[0m'

log_section() {
  echo -e "\n${BOLD_CYAN}====================================================${RESET}"
  echo -e " ${BOLD_BLUE}$1${RESET}"
  echo -e "${BOLD_CYAN}====================================================${RESET}"
}

log_step() { echo -e " ${BOLD_CYAN}[+]${RESET} $1"; }
log_sub() { echo -e "    ${CYAN}↳${RESET} $1"; }
log_success() { echo -e "    ${BOLD_GREEN}✔${RESET} $1"; }
log_warn() { echo -e "    ${YELLOW}⚠${RESET} $1"; }
log_error() { echo -e "    ${BOLD_RED}✘${RESET} $1"; }

spin_until() {
  local msg="$1"
  local check_cmd="$2"
  local max_attempts="${3:-30}"
  local delay="${4:-2}"
  local spin_chars=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
  local spin_i=0

  for ((i=1; i<=max_attempts; i++)); do
    if eval "$check_cmd" >/dev/null 2>&1; then
      echo -e "\r    ${BOLD_GREEN}✔${RESET} ${msg} ${BOLD_GREEN}(Ready!)${RESET}                                  "
      return 0
    fi
    for ((s=0; s<delay*10; s++)); do
      local char="${spin_chars[$spin_i]}"
      spin_i=$(( (spin_i + 1) % ${#spin_chars[@]} ))
      echo -ne "\r    ${YELLOW}${char}${RESET} ${msg} ${DIM}(attempt ${i}/${max_attempts})${RESET}   "
      sleep 0.1
    done
  done
  echo -e "\r    ${BOLD_RED}✘${RESET} ${msg} ${BOLD_RED}(Timed out after ${max_attempts}s)${RESET}                                  "
  return 1
}

# Parameters & Environment Variables
KIBANA_HOST="${KIBANA_HOST:-10.1.10.4}"
KIBANA_SSH_USER="${KIBANA_SSH_USER:-root}"
KIBANA_SSH_PORT="${KIBANA_SSH_PORT:-22}"
KIBANA_SSH_KEY="${KIBANA_SSH_KEY:-}"

CERT_DIR="${CERT_DIR:-/etc/elasticsearch/certs}"
ES_HTTP_P12_TARGET="${ES_HTTP_P12_TARGET:-/etc/elasticsearch/certs/http.p12}"
ES_TRANSPORT_P12_TARGET="${ES_TRANSPORT_P12_TARGET:-/etc/elasticsearch/certs/transport.p12}"
ES_HTTP_CA_TARGET="${ES_HTTP_CA_TARGET:-/etc/elasticsearch/certs/http_ca.crt}"

REMOTE_KIBANA_CRT_TARGET="${REMOTE_KIBANA_CRT_TARGET:-/etc/kibana/certs/kibana.crt}"
REMOTE_KIBANA_KEY_TARGET="${REMOTE_KIBANA_KEY_TARGET:-/etc/kibana/certs/kibana.key}"
REMOTE_KIBANA_CA_TARGET="${REMOTE_KIBANA_CA_TARGET:-/var/lib/kibana/ca_1721297493415.crt}"
REMOTE_KIBANA_CONFIG="${REMOTE_KIBANA_CONFIG:-/etc/kibana/kibana.yml}"

FLEET_SERVER_ES_URL="${FLEET_SERVER_ES_URL:-https://10.1.10.3:9200}"
FLEET_SERVER_URL="${FLEET_SERVER_URL:-https://${KIBANA_HOST}:8220}"
FLEET_SERVER_POLICY="${FLEET_SERVER_POLICY:-fleet-server-policy}"
FLEET_SERVER_PORT="${FLEET_SERVER_PORT:-8220}"

ES_SAN_DNS="${ES_SAN_DNS:-localhost cmsrv2024 siem2024}"
ES_SAN_IPS="${ES_SAN_IPS:-127.0.0.1 10.1.10.3}"
KIBANA_SAN_DNS="${KIBANA_SAN_DNS:-localhost kibana-server ${KIBANA_HOST}}"
KIBANA_SAN_IPS="${KIBANA_SAN_IPS:-127.0.0.1 ${KIBANA_HOST}}"

SSH_OPTS=("-o" "StrictHostKeyChecking=no" "-p" "$KIBANA_SSH_PORT")
SCP_OPTS=("-o" "StrictHostKeyChecking=no" "-P" "$KIBANA_SSH_PORT")
if [ -n "$KIBANA_SSH_KEY" ]; then
  SSH_OPTS+=("-i" "$KIBANA_SSH_KEY")
  SCP_OPTS+=("-i" "$KIBANA_SSH_KEY")
fi
REMOTE_TARGET="${KIBANA_SSH_USER}@${KIBANA_HOST}"

write_instances_yml() {
  local target_path="$1"
  cat << EOF > "$target_path"
instances:
  - name: "elasticsearch"
    dns:
EOF
  for d in $ES_SAN_DNS; do echo "      - \"$d\"" >> "$target_path"; done
  echo "    ip:" >> "$target_path"
  for ip in $ES_SAN_IPS; do echo "      - \"$ip\"" >> "$target_path"; done
  cat << EOF >> "$target_path"
  - name: "kibana"
    dns:
EOF
  for d in $KIBANA_SAN_DNS; do echo "      - \"$d\"" >> "$target_path"; done
  echo "    ip:" >> "$target_path"
  for ip in $KIBANA_SAN_IPS; do echo "      - \"$ip\"" >> "$target_path"; done
}

CA_PATH="$CERT_DIR/elastic-stack-ca.p12"
ES_CERTUTIL="/usr/share/elasticsearch/bin/elasticsearch-certutil"

log_section "Multi-Server Elastic Stack Cert Renewal (Local: ES | Remote: $KIBANA_HOST)"

log_step "Ensuring certificate directory exists..."
mkdir -p "$CERT_DIR"

log_step "Checking Certificate Authority (CA) status..."
if [ -f "$CA_PATH" ] && openssl pkcs12 -in "$CA_PATH" -passin pass: -nokeys &>/dev/null; then
  log_success "Existing passwordless CA found."
else
  log_step "Regenerating fresh passwordless CA..."
  rm -f "$CA_PATH"
  "$ES_CERTUTIL" ca --out "$CA_PATH" --pass "" >/dev/null 2>&1
  log_success "New CA generated."
fi

log_step "Extracting HTTP CA certificate..."
openssl pkcs12 -in "$CA_PATH" -passin pass: -nokeys -out "$CERT_DIR/http_ca.crt" >/dev/null 2>&1

log_step "Generating instances configuration..."
write_instances_yml "$CERT_DIR/instances.yml"

log_step "Generating node certificates..."
rm -f "$CERT_DIR/certs.zip"
"$ES_CERTUTIL" cert --ca "$CA_PATH" --ca-pass "" --in "$CERT_DIR/instances.yml" --out "$CERT_DIR/certs.zip" --pass "" >/dev/null 2>&1

log_step "Extracting certificate archive..."
python3 -c "import zipfile; zipfile.ZipFile('$CERT_DIR/certs.zip').extractall('$CERT_DIR')"

log_step "Processing local Elasticsearch certificates..."
mv "$CERT_DIR/elasticsearch/elasticsearch.p12" "$CERT_DIR/http.p12"
cp "$CERT_DIR/http.p12" "$CERT_DIR/transport.p12"

openssl pkcs12 -in "$CERT_DIR/kibana/kibana.p12" -clcerts -nokeys -out "$CERT_DIR/kibana.crt" -passin pass: >/dev/null 2>&1
openssl pkcs12 -in "$CERT_DIR/kibana/kibana.p12" -nocerts -nodes -out "$CERT_DIR/kibana.key.tmp" -passin pass: >/dev/null 2>&1
openssl pkey -in "$CERT_DIR/kibana.key.tmp" -out "$CERT_DIR/kibana.key" >/dev/null 2>&1

rm -rf "$CERT_DIR/elasticsearch" "$CERT_DIR/kibana" "$CERT_DIR/certs.zip" "$CERT_DIR/instances.yml" "$CERT_DIR/kibana.key.tmp"

log_step "Applying permissions & updating local ES symlinks..."
ln -sf "$CERT_DIR/http.p12" "$ES_HTTP_P12_TARGET"
ln -sf "$CERT_DIR/transport.p12" "$ES_TRANSPORT_P12_TARGET"
ln -sf "$CERT_DIR/http_ca.crt" "$ES_HTTP_CA_TARGET"

systemctl restart elasticsearch
log_success "Local Elasticsearch restarted."

log_section "Transferring Certificates via SCP to Remote Host ($KIBANA_HOST)"

REMOTE_CRT_DIR=$(dirname "$REMOTE_KIBANA_CRT_TARGET")
REMOTE_CA_DIR=$(dirname "$REMOTE_KIBANA_CA_TARGET")

ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "mkdir -p $REMOTE_CRT_DIR $REMOTE_CA_DIR /tmp/elastic-certs-staging"
scp "${SCP_OPTS[@]}" "$CERT_DIR/kibana.crt" "$REMOTE_TARGET:/tmp/elastic-certs-staging/kibana.crt"
scp "${SCP_OPTS[@]}" "$CERT_DIR/kibana.key" "$REMOTE_TARGET:/tmp/elastic-certs-staging/kibana.key"
scp "${SCP_OPTS[@]}" "$CERT_DIR/http_ca.crt" "$REMOTE_TARGET:/tmp/elastic-certs-staging/http_ca.crt"
log_success "Certificates uploaded via SCP."

ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "
  cp /tmp/elastic-certs-staging/kibana.crt $REMOTE_KIBANA_CRT_TARGET && \
  cp /tmp/elastic-certs-staging/kibana.key $REMOTE_KIBANA_KEY_TARGET && \
  cp /tmp/elastic-certs-staging/http_ca.crt $REMOTE_KIBANA_CA_TARGET && \
  chown -R root:kibana $REMOTE_CRT_DIR && \
  chmod 644 $REMOTE_KIBANA_CRT_TARGET && \
  chmod 640 $REMOTE_KIBANA_KEY_TARGET && \
  chmod 644 $REMOTE_KIBANA_CA_TARGET && \
  rm -rf /tmp/elastic-certs-staging
"
log_success "Remote certificates installed."

NEW_FINGERPRINT=$(openssl x509 -in "$CERT_DIR/http_ca.crt" -noout -sha256 -fingerprint | cut -d'=' -f2 | tr -d ':' | tr '[:upper:]' '[:lower:]')
log_sub "New CA Fingerprint: ${BOLD_GREEN}$NEW_FINGERPRINT${RESET}"

ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "sed -i 's/ca_trusted_fingerprint:.*/ca_trusted_fingerprint: $NEW_FINGERPRINT/' $REMOTE_KIBANA_CONFIG"
log_success "Remote Kibana fingerprint updated."

ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "systemctl restart kibana"
spin_until "Waiting for remote Kibana..." "ssh ${SSH_OPTS[*]} $REMOTE_TARGET 'curl -k -s https://localhost:5601'"

log_section "Re-enrolling Remote Elastic Agent (Fleet Server)"

TOKEN_NAME="cert-exchange-remote-$(date +%s)"
FLEET_SERVICE_TOKEN=$(/usr/share/elasticsearch/bin/elasticsearch-service-tokens create elastic/fleet-server "$TOKEN_NAME" 2>/dev/null | awk '{print $NF}' || true)

ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "systemctl stop elastic-agent || true"

ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "
  /opt/Elastic/Agent/elastic-agent enroll --force \
    --url='$FLEET_SERVER_URL' \
    --fleet-server-es='$FLEET_SERVER_ES_URL' \
    --fleet-server-es-ca='$REMOTE_KIBANA_CA_TARGET' \
    --fleet-server-service-token='$FLEET_SERVICE_TOKEN' \
    --fleet-server-policy='$FLEET_SERVER_POLICY' \
    --fleet-server-es-ca-trusted-fingerprint='$NEW_FINGERPRINT' \
    --fleet-server-port='$FLEET_SERVER_PORT' \
    --insecure
"
log_success "Remote Elastic Agent re-enrolled."

ssh "${SSH_OPTS[@]}" "$REMOTE_TARGET" "systemctl start elastic-agent"
spin_until "Waiting for remote Fleet Server..." "ssh ${SSH_OPTS[*]} $REMOTE_TARGET 'curl -k -s https://localhost:8220'"

log_section "Multi-Server SSL Verification"
log_step "Remote Kibana ($KIBANA_HOST:5601):"
openssl s_client -connect "$KIBANA_HOST:5601" -showcerts </dev/null 2>/dev/null | openssl x509 -noout -dates || log_warn "Direct remote connect check skipped."

echo -e "\n${BOLD_GREEN}====================================================${RESET}"
echo -e " ${BOLD_GREEN}✔ Multi-server certificate exchange completed!${RESET}"
echo -e "${BOLD_GREEN}====================================================${RESET}\n"
