Saltar a contenido

Publicar con HTTPS (Caddy + NSSM)

Esta guía expone al público la app que dejaste corriendo en Instalar en el servidor (escuchando en 127.0.0.1:28880), poniendo Caddy delante como reverse proxy que termina TLS con un certificado real de Let's Encrypt, y corriéndolo también como servicio Windows con NSSM.

Internet ──443/TLS──▶ Caddy (cert Let's Encrypt) ──HTTP──▶ app :28880 (solo 127.0.0.1)

Por qué HTTPS real y no un certificado autofirmado

La landing usa la cámara del celular para escanear el DNI del cliente (lector de código de barras PDF417). Los navegadores móviles solo habilitan getUserMedia (acceso a cámara) en un secure context — HTTPS con un certificado válido, o localhost. Con un certificado autofirmado, el navegador muestra un warning de seguridad y en muchos casos bloquea directamente el acceso a la cámara. Un dominio público con cert de Let's Encrypt es lo único que garantiza que el escaneo funcione en cualquier teléfono, de cualquier cliente, sin fricción.

Prerrequisitos

  • Caddy (binario estándar, sin necesidad de módulos DNS extra).
  • Un registro DNS A apuntando el dominio de la campaña a la IP pública/elástica del servidor.
  • Puerto 443/tcp abierto hacia el servidor (en el Security Group de AWS, o el firewall que corresponda). El puerto 80 no hace falta — Caddy emite el certificado por TLS-ALPN, sin pasar por HTTP.
  • Windows Firewall con el puerto 443 permitido:
netsh advfirewall firewall add rule name="Caddy HTTPS 443" dir=in action=allow protocol=TCP localport=443

1. Escribir el Caddyfile

Creá el Caddyfile junto al binario caddy.exe (por ejemplo, D:\tipre\caddy\Caddyfile):

{
    email admin@tipre.com
    auto_https disable_redirects
    storage file_system {
        root D:\tipre\caddy\data
    }
}

sistemapremios.tipre.com {
    tls {
        issuer acme {
            disable_http_challenge     # TLS-ALPN-01 por 443; Caddy nunca toca el 80
        }
    }

    @bootui path /bootui /bootui/*     # consola de dev — nunca pública
    respond @bootui 404

    reverse_proxy 127.0.0.1:28880 {
        header_up X-Forwarded-For {remote_host}   # pisa el XFF entrante: el cliente no puede falsificar su IP
    }
}

Reemplazá sistemapremios.tipre.com por el dominio real de la campaña, y admin@tipre.com por el email que Let's Encrypt usa para avisos de renovación.

header_up X-Forwarded-For {remote_host} no es opcional

Detrás de un reverse proxy, todas las requests le llegan al backend con la misma IP de origen si no se reescribe el header — el rate limiting (30 req/60s por IP) colapsaría en un solo balde compartido por todos los clientes. Peor: sin este pisado, un cliente malicioso podría falsificar su propia IP mandando su propio X-Forwarded-For y esquivar el límite. Caddy tiene que pisar el header entrante con la IP real del socket ({remote_host}), nunca reenviar el que venga del cliente sin tocar.

El bloqueo de /bootui es la mitigación de infraestructura, no de código

La consola de desarrollo boot-ui se autodesactiva por código solo con el perfil prod — pero prod mata el modo prueba (fail-fast), y necesitamos modo prueba prendido para poder generar QRs de prueba en el propio servidor. Por eso corremos sin spring.profiles.active=prod, lo que significa que boot-ui sigue activo a nivel de la app. El @bootui → 404 de este Caddyfile es lo que evita que esa consola quede expuesta públicamente.

2. Probar Caddy a mano antes de instalarlo como servicio

cd D:\tipre\caddy
caddy run --config Caddyfile

Mirá la consola: el primer arranque tarda unos segundos en emitir el certificado. Cuando veas que levantó sin errores, Ctrl+C y pasá al servicio.

3. Instalar Caddy como servicio con NSSM

nssm install caddy "D:\tipre\caddy\caddy.exe" "run --config D:\tipre\caddy\Caddyfile"
nssm set caddy AppDirectory D:\tipre\caddy
nssm set caddy Start SERVICE_AUTO_START
nssm start caddy

4. Verificar

nssm status caddy
curl.exe -v https://sistemapremios.tipre.com/api/salud

El primer arranque puede tardar unos segundos en emitir el certificado — si el curl falla apenas arrancás el servicio, esperá un momento y reintentá antes de asumir que algo está mal.

5. Smoke test completo

Con los dos servicios (SistemaPremios y caddy) arriba, confirmá el circuito de punta a punta:

  1. Health por HTTPS: https://sistemapremios.tipre.com/api/salud{"estado":"ok","base_datos":"ok"}.
  2. Landing: abrí el dominio público en el navegador — debería cargar sin warning de certificado.
  3. QR de prueba: entrá al gestor (https://sistemapremios.tipre.com/gestor/, login SP_GESTOR_USER / SP_GESTOR_PASSWORD) → Modo prueba → generá un QR y escaneálo desde un celular real, fuera de la red del servidor. Ver el detalle en Generar vouchers de prueba (QR).
  4. bootui bloqueado: https://sistemapremios.tipre.com/bootui debería devolver 404.

Publicación completa

Si los cuatro puntos cierran, el sistema está publicado con HTTPS real, el rate limit funciona por IP real de cada cliente, y la consola de desarrollo no queda expuesta.

Seguridad — resumen de las guardas activas

  • Gestor bajo autenticación básica: /api/gestor/** exige usuario y password en cada request.
  • Actuator cerrado salvo /health, y ese health no muestra detalle a un caller anónimo.
  • Secretos nunca en el repo ni en el Caddyfile: la carpeta de deploy con los valores reales (config/application.yml) vive fuera de git, según ADR-029. SP_MASTER_KEY en el YAML es un atajo operativo aceptado para este despliegue; si el cliente pide un manejo de secretos más estricto, se puede inyectar por AppEnvironmentExtra de NSSM en lugar de dejarla en el archivo.

Con la app publicada y el smoke test en verde, el siguiente paso operativo es cargar la campaña real desde el gestor — ver Preparar una campaña.