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.
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¶
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¶
4. Verificar¶
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:
- Health por HTTPS:
https://sistemapremios.tipre.com/api/salud→{"estado":"ok","base_datos":"ok"}. - Landing: abrí el dominio público en el navegador — debería cargar sin warning de certificado.
- QR de prueba: entrá al gestor (
https://sistemapremios.tipre.com/gestor/, loginSP_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). bootuibloqueado:https://sistemapremios.tipre.com/bootuidebería devolver404.
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_KEYen el YAML es un atajo operativo aceptado para este despliegue; si el cliente pide un manejo de secretos más estricto, se puede inyectar porAppEnvironmentExtrade 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.