TLS e proxy reverso¶
Os servidores HTTP do próprio PEART nunca terminam TLS. Uma instalação fica atrás de um proxy reverso que
termina TLS na frente das duas superfícies e encaminha para as portas de loopback delas. Definir
tls: true na configuração diz ao PEART que ele está atrás de um proxy desses e liga o cabeçalho
Strict-Transport-Security.
As duas superfícies¶
O proxy reverso fica na frente de duas superfícies independentes, idealmente em dois hostnames:
- a superfície interna (
peart) na:8080, alcançável só da rede restrita ou VPN da consultoria, - o portal (
peart-portal) na:8081, alcançável pelo cliente pela internet.
Mantê-las em hostnames separados (e exposições separadas) espelha a fronteira de confiança sobre a qual a arquitetura é construída: a superfície interna não deve ser alcançável de onde o cliente alcança o portal.
O tls é uma chave só, para as duas superfícies¶
O tls é uma configuração única do deploy, não uma por superfície. Ligá-lo diz "este deploy está atrás
de algo que termina TLS", e vale igualmente para a superfície interna e para o portal: as duas enviam
Strict-Transport-Security, e cada public_url que você definir precisa ser https. Não há como pôr o
portal atrás de TLS e deixar a interna em HTTP puro, e isso é deliberado: a interna carrega os achados
ainda não publicados, que são a metade mais sensível do sistema.
Um túnel é um front válido, para o portal¶
O proxy não precisa ser um host seu. Um túnel que termina TLS num provedor de borda e alcança a porta de
loopback por dentro (um túnel da Cloudflare, por exemplo) é front legítimo para o portal: o cliente
chega à borda por HTTPS, e o host não precisa de nenhuma porta aberta para entrada. Ponha tls: true e o
public_url do portal no nome público, exatamente como faria com um proxy local.
Não tunele a superfície interna do mesmo jeito. Publicá-la na internet, por túnel ou por porta aberta, leva a superfície do pentester para onde o navegador do cliente alcança o portal, que é justamente a fronteira que a arquitetura de dois processos existe para manter. Alcance a interna pela VPN ou pela rede restrita da consultoria.
Com tls: true as duas superfícies ficam atrás de um terminador, e o da interna é local -- nginx ou
Caddy no próprio host, alcançável apenas pela rede restrita.
Atrás de um terminador, diga qual endereço é o do cliente¶
Um proxy ou um túnel termina a conexão, então toda requisição chega ao PEART vindo do terminador: o endereço de loopback do próprio conector, no caso do túnel, ou o do proxy, no caso local. Sem mais nada, é esse o endereço que o log de acesso registra, o que fica gravado em toda linha de sessão, a chave que o limitador conta e a metade por origem do tranca-conta. Todo cliente parece o mesmo cliente.
A consequência mais séria é a mais silenciosa. Uma investigação começa pela trilha, e atrás de um túnel a trilha diz o conector todas as vezes; ninguém percebe até o dia em que alguém precisa dela.
Duas chaves por superfície resolvem, e as duas vêm desligadas:
portal:
listen_addr: ":8081"
trusted_proxies: ["127.0.0.1"]
trusted_proxy_header: "CF-Connecting-IP"
O trusted_proxies lista os pares cujo cabeçalho encaminhado é acreditado, como endereços avulsos ou
faixas CIDR. Liste endereços que você controla, e não endereços em que você confia genericamente.
Qualquer coisa nessa lista pode declarar o endereço de cliente que quiser, então um par que não seja de
fato o seu terminador devolve esse poder ao cliente, o que é pior do que deixar tudo desligado.
O trusted_proxy_header é o nome a ler, e o PEART nunca o adivinha. Um túnel da Cloudflare envia
CF-Connecting-IP; um nginx envia o que você mandar, comumente X-Forwarded-For. Configurar uma chave
sem a outra é recusado no arranque, porque qualquer uma sozinha é configuração que parece ligada e não
está.
O cabeçalho só é lido de um par que esteja na lista, e nunca fora disso. Requisição que chegue de qualquer outro lugar fica com o endereço dela, carregue o cabeçalho que carregar, e é isso que impede um cliente de simplesmente se declarar outra pessoa.
Se um par da lista não enviar endereço legível, o PEART usa o do próprio par e registra um aviso nomeando o par e o cabeçalho. Ele não recusa a requisição: proxy mal configurado não deve virar indisponibilidade. O aviso é o que impede esse estado de parecer exatamente com sucesso.
Ligar isso muda o que as linhas novas da tabela de sessão significam: as anteriores carregam o endereço do conector, as posteriores o do cliente. Não há migração, há essa leitura.
Exemplo: um proxy reverso¶
Um proxy reverso mínimo termina TLS e encaminha para a porta de loopback. Com o Caddy:
portal.example.com {
reverse_proxy 127.0.0.1:8081
}
internal.example.com {
reverse_proxy 127.0.0.1:8080
}
O Caddy obtém e renova certificados sozinho. Com o nginx, termine TLS num bloco server por hostname e
faça proxy_pass para 127.0.0.1:8081 e 127.0.0.1:8080.
O PEART não descobre pela requisição que o TLS foi terminado na frente dele, e isso é deliberado: um
cabeçalho encaminhado vem do cliente, e um processo que tirasse a própria postura de segurança de um
deles entregaria a chave ao atacante. O tls: true da configuração é tudo, e é ele que torna o cookie de
sessão Secure e envia HSTS. Encaminhe o Host e o que o seu proxy precisar para o registro dele, e
saiba que o X-Forwarded-Proto não é lido: HTTPS na borda com tls: false atrás produz cookie não
seguro numa conexão que parece cifrada, e é a única combinação que vale conferir depois de mexer no
proxy.
Um hostname interno que só resolve na VPN¶
O exemplo acima supõe os dois nomes alcançáveis pela internet, que é como o Caddy obtém certificado para eles sozinho. Um hostname interno que só resolve na VPN da consultoria não consegue responder a um desafio ACME por HTTP, porque a autoridade certificadora não tem rota até ele: ou emita o certificado daquele nome por desafio de DNS, ou use um wildcard que você já tenha para a zona interna e aponte o proxy para os arquivos. Nada mais muda na superfície interna, e em particular ela continua escutando só no loopback e continua sendo alcançada por um terminador na rede restrita.
Rodar sem TLS¶
tls: false recusa subir a menos que accept_insecure_http: true também esteja definido. Essa
combinação existe para uma instalação em HTTP puro deliberada (um laboratório fechado, um teste local),
nunca como default acidental. Não rode uma instalação real assim; ponha o proxy na frente e defina
tls: true.