#!/usr/bin/env bash
# =============================================================================
#  demo-02 · Las correcciones de configuración
# =============================================================================
#  A qué punto del guion sirve: punto 13
#    «Y se puede tratar en Traefik y en Umami»
#
#  Qué se ve en pantalla: los dos fragmentos correctos, con la línea que importa
#  señalada, y una comprobación de que lo son de verdad.
#    1. Traefik  → `config/traefik-entrypoints.yml`
#                   forwardedHeaders.trustedIPs y proxyProtocol.trustedIPs
#                   apuntando SOLO al proxy correcto.
#    2. Umami    → `config/umami.env`
#                   CLIENT_IP_HEADER=x-real-ip
#
#  La frase que resume las dos: una cabecera que el proxy REESCRIBE frente a una
#  cabecera que CUALQUIERA PUEDE ESCRIBIR.
#
#  Estado: VERIFICADO en local. El YAML se parsea y el fichero de entorno se
#  comprueba con comandos; la salida real está en
#  `verificacion/demo-02-salida.txt`.
#
#  Seguridad: solo lee los ficheros de `config/`. No hay ningún secreto en ellos
#  (el token de Cloudflare, si algún día se usa, no vive aquí: va en un `podman
#  secret`). La única dirección de la confianza es la del borde de este montaje
#  (loopback); donde se usa un valor de mentira se marca como rango de
#  documentación (RFC 5737).
# =============================================================================

set -u

AQUI="$(cd "$(dirname "$0")" && pwd)"
TRAEFIK="$AQUI/config/traefik-entrypoints.yml"
UMAMI="$AQUI/config/umami.env"

titulo() { printf '\n\033[1m── %s\033[0m\n' "$1"; }
orden()  { printf '\n\033[1m$ %s\033[0m\n' "$1"; }
nota()   { printf '   ↳ %s\n' "$1"; }
ok()     { printf '   \033[1m✔ %s\033[0m\n' "$1"; }
fallo()  { printf '   \033[1m✘ %s\033[0m\n' "$1"; }

rc_final=0

printf '\033[1mLas dos correcciones del punto 13\033[0m\n'

# -----------------------------------------------------------------------------
titulo "1 · Traefik: de quién se fía el proxy"
# -----------------------------------------------------------------------------
# El error de partida (lo que se cuenta en voz alta, no hace falta fichero):
#   proxyProtocol:   trustedIPs: [el rango de tu vecindario de contenedores]
#                                  ← confía en TODO el vecindario: cualquiera
#                                    que alcance el puerto puede contar que
#                                    viene de donde le apetezca
#   forwardedHeaders: insecure: true    ← se cree cualquier cabecera
# Con eso, cualquiera que alcance el puerto de Traefik entra diciendo que viene de
# donde quiera.
orden "cat config/traefik-entrypoints.yml"
cat "$TRAEFIK"

# ¿Se parsea? Un YAML roto en el arranque deja el proxy sin servicios.
if command -v python3 >/dev/null 2>&1 && python3 -c 'import yaml' 2>/dev/null; then
    orden "python3 -c 'yaml.safe_load(...)'   # ¿el YAML es válido y dice lo que creemos?"
    python3 - "$TRAEFIK" <<'PY'
import sys, yaml
with open(sys.argv[1]) as f:
    cfg = yaml.safe_load(f)
ep = cfg["entryPoints"]["websecure"]
print("address                        :", ep["address"])
print("proxyProtocol.trustedIPs       :", ep["proxyProtocol"]["trustedIPs"])
print("forwardedHeaders.insecure      :", ep.get("forwardedHeaders", {}).get("insecure", False))
print("forwardedHeaders.trustedIPs    :", ep["forwardedHeaders"]["trustedIPs"])
PY
    ok "YAML válido y con las listas presentes."
else
    nota "python3 + PyYAML no disponibles: se salta la comprobación de sintaxis."
fi

# Auditoría: lo que NO debe haber. Solo miran las líneas ACTIVAS (sin '#').
orden "grep -vE '^[[:space:]]*#' config/traefik-entrypoints.yml | grep -nE 'insecure: true|/[0-9]{1,2}\"' "
if grep -vE '^[[:space:]]*#' "$TRAEFIK" | grep -E 'insecure: true|/(0|[0-9]|1[0-9]|2[0-3])"'; then
    fallo "hay un rango demasiado ancho o un 'insecure' activo."
    rc_final=1
else
    ok "ni 'insecure: true' ni rangos anchos activos: solo la IP del borde."
fi

# La misma auditoría, pero entendiendo el YAML (si hay python3). Es la que manda.
if command -v python3 >/dev/null 2>&1 && python3 -c 'import yaml' 2>/dev/null; then
    orden "python3 -c 'auditar el YAML: insecure y prefijos de red'"
    if python3 - "$TRAEFIK" <<'PY'
import sys, ipaddress, yaml
ep = yaml.safe_load(open(sys.argv[1]))["entryPoints"]["websecure"]
problemas = []
for nombre in ("forwardedHeaders", "proxyProtocol"):
    bloque = ep.get(nombre) or {}
    if bloque.get("insecure"):
        problemas.append(f"{nombre}.insecure: true  ← se cree a cualquiera")
    for cidr in bloque.get("trustedIPs") or []:
        red = ipaddress.ip_network(cidr, strict=False)
        minimo = 24 if red.version == 4 else 64
        if red.prefixlen < minimo:
            problemas.append(f"{nombre}.trustedIPs demasiado ancho: {cidr}")
        else:
            print(f"   {nombre}.trustedIPs → {cidr}  (red pequeña, correcto)")
if problemas:
    for p in problemas:
        print("  ✘", p)
    sys.exit(1)
print("   ✔ sin 'insecure' y sin rangos anchos.")
PY
    then
        ok "el YAML auditado no tiene confianzas excesivas."
    else
        fallo "el YAML confía en más gente de la que debería."
        rc_final=1
    fi
fi

# -----------------------------------------------------------------------------
titulo "2 · Umami: qué cabecera lee"
# -----------------------------------------------------------------------------
# Mismo razonamiento, al revés: aquí no se restringe QUIÉN puede llegar, se elige
# QUÉ cabecera creerse. Se elige la que el proxy reescribe.
orden "cat config/umami.env"
cat "$UMAMI"

orden "grep -n '^CLIENT_IP_HEADER' config/umami.env"
if grep -q '^CLIENT_IP_HEADER=x-real-ip' "$UMAMI"; then
    ok "CLIENT_IP_HEADER=x-real-ip → la cabecera que Traefik reconstruye."
else
    fallo "CLIENT_IP_HEADER no apunta a x-real-ip."
    rc_final=1
fi

orden "grep -nE 'cf-connecting-ip|true-client-ip' config/umami.env   # no debe haber ninguna activa"
if grep -E '^[[:space:]]*CLIENT_IP_HEADER=.*(cf-connecting-ip|true-client-ip)' "$UMAMI"; then
    fallo "queda una cabecera que cualquiera puede escribir."
    rc_final=1
else
    ok "ninguna cabecera de cliente (cf-connecting-ip / true-client-ip) activa."
fi

# -----------------------------------------------------------------------------
titulo "Por qué, en una frase (para decir en el aire)"
# -----------------------------------------------------------------------------
cat <<'TEXTO'
   · En el proxy: no te fíes de todos. 'trustedIPs' con la IP exacta del borde;
     si te fías del rango entero, cualquiera que llegue te cuenta de dónde viene.
   · En la analítica: lee la cabecera que el proxy reescribe (x-real-ip), no una
     que pueda rellenar el primero que pase (cf-connecting-ip, x-forwarded-for).
   · Y recuerda el límite: esto ordena la casa, no tapa el agujero. Si el 8443
     está abierto al mundo, la fiabilidad la da el firewall, no la cabecera.
   · Y ojo con el alcance: esto funcionaba… hasta que un sábado por la tarde el
     bloqueo judicial tumbó el rango entero del proxy que había delante.
TEXTO

printf '\n'
if [ "$rc_final" -eq 0 ]; then
    printf '\033[1mResultado: las dos correcciones están en su sitio.\033[0m\n\n'
else
    printf '\033[1mResultado: hay algo que corregir (ver arriba).\033[0m\n\n'
fi
exit "$rc_final"
