diff --git a/Caddyfile b/Caddyfile new file mode 100644 index 0000000..a945d5c --- /dev/null +++ b/Caddyfile @@ -0,0 +1,26 @@ +# Caddyfile +# +# Objetivo: +# - Publicar o serviço e-li.nps em https://nps.idz.one +# - Fazer reverse proxy para o backend em {ip-app}:8080 +# - Preservar IP real do cliente para a aplicação (X-Forwarded-For / X-Real-IP) +# +# Observações importantes: +# - TLS automático requer que o DNS de nps.idz.one aponte para o IP público do Caddy +# e que as portas 80/443 estejam liberadas. +# - O IP real chegará na aplicação via X-Forwarded-For, que é interpretado pelo +# middleware.RealIP do chi (já habilitado no projeto). + +nps.idz.one { + encode gzip + + # Reverse proxy para o backend + reverse_proxy {ip-app}:8080 { + # Cabeçalhos padrão para preservar IP e esquema + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + header_up X-Forwarded-Host {host} + header_up X-Real-IP {remote_host} + } +} + diff --git a/README.md b/README.md index 382eeae..735e2e6 100644 --- a/README.md +++ b/README.md @@ -117,6 +117,45 @@ DATABASE_URL='postgres://usuario:senha@host.docker.internal:5432/seu_banco?sslmo No Linux, o `docker-compose.yml` já inclui `extra_hosts` com `host-gateway` para esse hostname funcionar. +## Publicar com Caddy (reverse proxy) + +Este repositório inclui um exemplo de `Caddyfile` para publicar o serviço em: + +- `https://nps.idz.one` → `{ip-app}:8080` + +### Pré-requisitos + +- O DNS de `nps.idz.one` deve apontar para o **IP público** do servidor onde o Caddy roda. +- Portas **80/443** liberadas para o Caddy (TLS automático). + +### IP real do usuário + +O Caddy repassa o IP do cliente via `X-Forwarded-For` e `X-Real-IP`. +O servidor Go já usa `middleware.RealIP` (chi), então o IP real chega corretamente +e é gravado em `ip_real`. + +### Check do IP real (direto / Docker / Caddy) + +O painel tem um endpoint de debug que mostra o IP que a aplicação está enxergando +e os headers recebidos: + +- `GET /painel/debug/ip` + +Passo a passo: + +1) Faça login no painel em `/painel`. +2) Acesse `/painel/debug/ip`. + +O JSON retornado inclui: +- `remote_addr` (já após o `middleware.RealIP`) +- `x_forwarded_for` +- `x_real_ip` + +Interpretação esperada: +- Rodando **direto** (sem proxy): `remote_addr` deve ser o IP do cliente (ou do seu balanceador). +- Rodando via **Docker**: se você acessar diretamente a porta publicada, o `remote_addr` tende a ser o IP do host/bridge; atrás de proxy (Caddy), o `remote_addr` deve refletir o IP real. +- Rodando via **Caddy**: `x_forwarded_for` deve conter o IP real do cliente e o `remote_addr` deve refletir esse IP após `RealIP`. + Depois acesse: - Home/README: `http://localhost:8080/` - Teste do widget: `http://localhost:8080/teste.html` diff --git a/internal/elinps/painel_handlers.go b/internal/elinps/painel_handlers.go index fd10066..7720b8e 100644 --- a/internal/elinps/painel_handlers.go +++ b/internal/elinps/painel_handlers.go @@ -3,6 +3,7 @@ package elinps import ( "crypto/rand" "encoding/hex" + "encoding/json" "net/http" "github.com/go-chi/chi/v5" @@ -39,6 +40,20 @@ func (p *PainelHandlers) Router() http.Handler { p.auth.handlerPainel(w, r, p.store) }) + // Debug: conferir IP real / headers. + // Protegido pelo mesmo middleware do painel. + r.With(func(next http.Handler) http.Handler { return p.auth.middleware(next) }).Get("/debug/ip", func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(map[string]any{ + "remote_addr": r.RemoteAddr, + "x_forwarded_for": r.Header.Get("X-Forwarded-For"), + "x_real_ip": r.Header.Get("X-Real-IP"), + "x_forwarded_proto": r.Header.Get("X-Forwarded-Proto"), + "x_forwarded_host": r.Header.Get("X-Forwarded-Host"), + "user_agent": r.UserAgent(), + }) + }) + return r }