#!/usr/bin/env bash # ============================================================================= # demo-04 · El borde con HAProxy # ============================================================================= # A qué punto del guion sirve: puntos 16, 17, 18 y 19 # · 16 «HAProxy en bare metal» → el borde corre en el HOST, # fuera del contenedor. # · 17 «Directo al 443» → el puerto privilegiado lo abre # HAProxy, en el metal. # · 18 «Traefik se levanta en el 8443» → el contenedor solo publica un # puerto alto. # · 19 «Y no necesito reglas especiales» → sin ip_unprivileged_port_start, # sin setcap, sin iptables. # # Qué se ve en pantalla: # 1. `config/haproxy.cfg` entero, con las tres líneas clave señaladas. # 2. El recorrido: :443 → HAProxy → `send-proxy-v2` → Traefik :8443. # 3. La validación de sintaxis de verdad, con `haproxy -c` y un control # negativo (un fichero roto a propósito) para demostrar que el chequeo # no dice «OK» a todo. # # Estado: VERIFICADO en local. La validación se ejecuta con HAProxy 3.4.7 # dentro de un contenedor EFÍMERO (`--rm`, sin puertos publicados y sin tocar # el host). Salida real en `verificacion/demo-04-salida.txt`. # En esta máquina NO hay binario `haproxy` ni `docker` instalados: por eso la # validación va por contenedor y ese camino es el que está verificado. # # Si no hay ni `haproxy` ni `podman` ni `docker`, el script NO falla: avisa de # que la sintaxis queda sin validar y muestra el comando exacto para hacerlo. # ============================================================================= set -u AQUI="$(cd "$(dirname "$0")" && pwd)" CFG="$AQUI/config/haproxy.cfg" # Imagen del validador. Se mira primero la caché local y, si no hay ninguna, se # descarga. Son las mismas versiones que el binario oficial de HAProxy: # · docker.io/library/haproxy:lts-alpine → la canónica # · mirror.gcr.io/library/haproxy:lts-alpine → espejo público (el Hub devolvió # error de autenticación al preparar este material, así que el espejo es lo # que acabó funcionando) # · docker.io/library/haproxy:latest → por si ya la tienes por casa CANDIDATAS=( "docker.io/library/haproxy:lts-alpine" "mirror.gcr.io/library/haproxy:lts-alpine" "docker.io/library/haproxy:latest" ) IMAGEN="${HAPROXY_IMAGEN:-${CANDIDATAS[0]}}" TMP="${TMPDIR:-/tmp/opencode}" resolver_imagen() { # elige la primera imagen disponible (caché primero, luego descarga) local img for img in "${CANDIDATAS[@]}"; do if podman image exists "$img" >/dev/null 2>&1; then IMAGEN="$img"; return 0; fi done for img in "${CANDIDATAS[@]}"; do printf ' (descargando %s…)\n' "$img" if podman pull "$img" >/dev/null 2>&1; then IMAGEN="$img"; return 0; fi done return 1 } 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[1mEl borde en bare metal\033[0m (puntos 16, 17, 18 y 19)\n' # ----------------------------------------------------------------------------- titulo "1 · El fichero del borde" # ----------------------------------------------------------------------------- orden "cat config/haproxy.cfg" cat "$CFG" # ----------------------------------------------------------------------------- titulo "2 · Las cuatro líneas que sostienen el diseño" # ----------------------------------------------------------------------------- orden "grep -nE 'mode tcp|bind \\*:443|send-proxy-v2|127.0.0.1:8443' config/haproxy.cfg" grep -nE 'mode tcp|bind \*:443|send-proxy-v2|127.0.0.1:8443' "$CFG" nota "'bind *:443' → el puerto privilegiado lo abre el HOST (punto 17)." nota "'mode tcp' → no descifra: pasa la conexión tal cual." nota "'send-proxy-v2' → avisa al siguiente salto de quién llama." nota "'127.0.0.1:8443' → Traefik, en rootless, en un puerto alto (punto 18)." # Comprobación de que están de verdad (no solo de que el grep los encuentre). for patron in 'bind \*:443' 'send-proxy-v2' '127\.0\.0\.1:8443'; do if grep -qE "$patron" "$CFG"; then ok "presente: $patron" else fallo "falta: $patron" rc_final=1 fi done # ----------------------------------------------------------------------------- titulo "3 · Los tres apaños que NO hacen falta (punto 19)" # ----------------------------------------------------------------------------- orden "grep -nE 'ip_unprivileged_port_start|setcap|iptables|nftables' config/haproxy.cfg # solo aparecen en los comentarios" if grep -nE 'ip_unprivileged_port_start|setcap|iptables|nftables' "$CFG"; then nota "aparecen mencionados en los comentarios, para explicar por qué no se usan;" nota "ninguno está como directiva activa (en HAProxy no existen como directivas)." else nota "ni siquiera se mencionan." fi nota "El motivo, en una frase: el contenedor no abre ningún puerto < 1024," nota "así que no hay nada que habilitar dentro del espacio de nombres." # ----------------------------------------------------------------------------- titulo "4 · Validar la sintaxis con HAProxy de verdad" # ----------------------------------------------------------------------------- validar() { # validar → 0 si HAProxy acepta la configuración if command -v haproxy >/dev/null 2>&1; then haproxy -c -f "$1" elif command -v podman >/dev/null 2>&1; then podman run --rm -v "$1:/usr/local/etc/haproxy/haproxy.cfg:ro" \ "$IMAGEN" haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg elif command -v docker >/dev/null 2>&1; then docker run --rm -v "$1:/usr/local/etc/haproxy/haproxy.cfg:ro" \ "$IMAGEN" haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg else return 127 fi } if command -v haproxy >/dev/null 2>&1; then orden "haproxy -v" haproxy -v | head -2 VALIDADOR="haproxy (binario del sistema)" elif command -v podman >/dev/null 2>&1; then [ -n "${HAPROXY_IMAGEN:-}" ] || resolver_imagen || true if podman run --rm "$IMAGEN" haproxy -v >/dev/null 2>&1; then orden "podman run --rm $IMAGEN haproxy -v" podman run --rm "$IMAGEN" haproxy -v 2>&1 | head -2 VALIDADOR="HAProxy en contenedor efímero (podman)" nota "El contenedor se borra solo (--rm): no deja nada en el host." nota "Imagen usada: $IMAGEN" else VALIDADOR="" fi elif command -v docker >/dev/null 2>&1; then if docker run --rm "$IMAGEN" haproxy -v >/dev/null 2>&1; then orden "docker run --rm $IMAGEN haproxy -v" docker run --rm "$IMAGEN" haproxy -v 2>&1 | head -2 VALIDADOR="HAProxy en contenedor efímero (docker)" else VALIDADOR="" fi else VALIDADOR="" fi if [ -n "$VALIDADOR" ]; then orden "haproxy -c -f config/haproxy.cfg" if validar "$CFG"; then ok "configuración válida (exit 0). Validador: $VALIDADOR" else fallo "HAProxy NO acepta la configuración." rc_final=1 fi # --- control negativo: si el chequeo dijera «OK» a todo, no valdría nada --- orden "control negativo: una copia con 'send-proxy-v9' (no existe)" ROTO="$(mktemp "$TMP/haproxy-roto-XXXXXX.cfg")" sed 's/send-proxy-v2/send-proxy-v9/' "$CFG" > "$ROTO" if validar "$ROTO" >/dev/null 2>&1; then fallo "ha aceptado una directiva inventada: la validación no vale." rc_final=1 else ok "la copia rota se rechaza → el chequeo es real (exit != 0)." fi rm -f "$ROTO" else nota "No hay 'haproxy', ni 'podman', ni 'docker' en esta máquina:" nota "la sintaxis queda SIN VALIDAR. Para validarla, en cualquier equipo:" nota " podman run --rm -v \"\$PWD/config/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro\" \\" nota " $IMAGEN haproxy -c -f /usr/local/etc/haproxy/haproxy.cfg" fi # ----------------------------------------------------------------------------- titulo "5 · El recorrido, para contarlo en voz alta" # ----------------------------------------------------------------------------- cat <<'TEXTO' visitante ──:443──► HAProxy (HOST, servicio del sistema) │ mode tcp: no descifra nada │ send-proxy-v2: antepone quién llama ▼ Traefik :8443 (rootless, en su contenedor) │ proxyProtocol.trustedIPs: solo ese borde │ forwardedHeaders: lee la IP de verdad ▼ tu web, tus logs y la analítica · El 443 no lo pelea el contenedor: lo abre el host. Por eso no hacen falta ip_unprivileged_port_start, ni setcap, ni reglas de iptables. · Sin certificados duplicados: HAProxy no termina TLS, solo lo atraviesa. · Cada cosa en la capa que le toca: el puerto en el metal, el enrutado en el contenedor rootless. TEXTO printf '\n' if [ "$rc_final" -eq 0 ]; then printf '\033[1mResultado: el diseño del borde está completo y validado.\033[0m\n\n' else printf '\033[1mResultado: hay algo que revisar (ver arriba).\033[0m\n\n' fi exit "$rc_final"