Proxy em Containers

Saudações.

Vou apresentar a vocês algumas experiências com o Squid Proxy em ambiente de containers.

Pré-requisitos:

  • VM/VPS/Host com Docker (standalone ou Swarm);
  • Internet no servidor (sua VPS ou host);

1 – Conceito de Proxy e software Squid

O Squid (https://www.squid-cache.org/) é um software escrito em C++ cujo objetivo é ser um framework supremo de Proxy.

Foi criado em 1996 e possui décadas de maturidade.

Ele consegue atuar de todas as formas possíveis dentro do escopo dos protocolos HTTP, HTTPs, FTP, ICP e alguns outros.

Principais recursos:

  • Proxy direto: Atende clientes HTTP fornecendo Internet indiretamente;
  • Proxy reverso: Recebe conexões em direção a um site e entrega ao servidor interno correto;
    • Conexão de entrada HTTPs, conexão interna HTTP;
    • Cache ajuda a aliviar a carga no fornecimento de arquivos e assets;
  • Proxy transparente: Captura tráfego de rede e se passa pelo site acessado;
  • Cache: Armazena arquivos e recursos em RAM e/ou disco para acelerar a Internet e economizar banda;
    • Esse era sua principal função antes da migração em massa para HTTPs;
    • Num mundo HTTPs o cache perdeu seu lugar e descontinou o Squid nos provedores;
  • Autenticação: Permite discriminar usuários por login e senha e controlar quais recursos o usuário pode acessar e com quais caractarísticas técnicas (IP de origem, QoS, horário, etc);
  • ACL: Permite discriminar IPs, domínios, usuários, cabeçalhos para decidir permitir, negar, redirecionar ou modificar os detalhes do acesso;

No Docker standalone (servidor solitário) ou no cluster Docker Swarm o Squid encontra a oportunidade de nos ajudar no ingress (conexões que vem da Internet em direção aos nossos serviços) atuando como Proxy Reverso ou no egress, fornecendo acesso controlado e auditado do que nossos containers estão indo buscar na Internet.

2 – Estudo do caso

Observe meu caso:

  • Cluster de servidores Docker Swarm com workers distribuídos em vários datacenters;
  • Containers que precisam acessar a Internet (bots, pesquisas, scrapping, tools de agentes);
  • Cada container navegava com um IP diferente pelo NAT do Linux de cada servidor;
  • Containers workers rodam:
    • Web Scrapping: Transformam os sites acessados em documentos JSON e MarkDown;
    • Web Search: Meus agentes de IA precisam navegar (Agent Tools) em vários sites para realizar pesquisas;
    • API: Acesso a API de provedores de IA, APIs corporativas, scrapping remoto (firecrawl), etc;
Servidores Workers do Docker Swarm em vários datacenters com IPs de saída difernetes.

Problemas que enfrentei:

  • Problema 1 – leve: Sites modernos implementam limite de requisições por IP (RPM – Request per Minute) e limite de banda por IP (Traffic Shapping);
  • Problema 2 – moderado: Sites protegidos por WAF (Web Application Firewall), o WAF aprende o padrão de repetição “like a bot” e bloqueiam o IP de origem;
  • Problema 3 – grave: Alguns containers rodam em países diferentes, o bloqueio por GEOIP impede o funcionamento nesses servidores;
  • Problema 4 – gravíssimo: Para cada IP bloqueado os containers que ainda conseguem navegar passam a fazer muitas requisições e os problemas anteriores se amplificam.

A solução parece óbvia: Precisamos contratar um proxy externo PAGO para balancear essa carga de saída entre milhares de IPs diferentes para que os bloqueios e limites sejam contornados.

Containers usando proxy comercial para balancear e escolher os IPs que os sites enxergam para contornar bloqueios.

Como muitas das minhas ferramentas precisam apenas de controle de uso mais do que volume, optei por usar o Squid como gateway dos meus containers, e no Squid eu implemento toda a lógica do negócio, usando IPs próprios balanceados ou entregando para provedores de Proxy quando necessário.

Fase 1 – No diagrama abaixo o problema parece se agravar, os containers que antes navegavam com os IPs de seus servidores agora navegam com o IP do Squid.

Servidor Squid atenderá os containers e os conduzirá à Internet usando o IP do Squid, centrando os acessos.

Fase 2 – O servidor Squid agora pode ser configurado com a lógica do nosso negócio, indo diretamente na Internet para os serviços menos burocráticos e delegando para provedores Proxy o acesso sensível a IPs de origem, observe:

Servidor Squid atua como intermediário do acesso, permitindo apenas o acesso autorizado (Auth e ACL) e fazendo o direcionamento (Routing) para o serviço adequado que garanta o IP de origem e a técnica adequada de obter o site ou API.

Fase 3 – Quando a rede escala e você precisa balancear seus softwares para navegarem na Internet fazendo centenas a milhares de requisições por segundo:

Containers configurados para usar proxy balanceado por DNS, cada cluster em uma região para maior redundância, clusters de squids balanceiam a saída até os sites finais, com ou sem provedores de proxies acima deles.

3 – Clientes HTTP e Variáveis de Ambiente

Para que você não tenha que configurar cada software e cada navegador para usar o proxy, a industria facilitou com o uso de alguns variáveis de ambiente que se presentes, desviam o tráfego para o proxy.

3.1 – Variáveis de Ambiente

As principais variáveis são:

  • http_proxy: Endereço do servidor proxy para requisições do schema “http://“;
  • https_proxy: Endereço do servidor proxy para requisições do schema “https://“;
  • ftp_proxy: Endereço do servidor proxy para requisições do schema “ftp://“;
  • socks_proxy: Endereço do servidor proxy para conexões SOCKS4/SOCKS5;
  • no_proxy: Registro de excessões separadas por virgula, as excessões não usam proxy e vão direto para o site de destino;
  • all_proxy: Funciona como fallback genérico. Se não houver uma variável específica para o schema do protocolo (HTTP, HTTPS, FTP, etc.), o cliente usa esse endereço;

Você quer cobrir a maioria dos cenários, configurar http_proxy, https_proxy, no_proxy e all_proxy.

3.2 – Declarações com case-sensitive

Infelizmente diferentes softwares e bibliotecas HTTP fazem a leitura nas versões upper-case (maiúsculas) dessas variáveis, assim, recomendo declarar nas duas formas, mesmo a lower-case (minúscula) seja a oficial.

Variável padrãoVariável uper-caseFinalidade
http_proxyHTTP_PROXYProxy para http://
https_proxyHTTPS_PROXYProxy para https://
ftp_proxyFTP_PROXYProxy para ftp://
socks_proxySOCKS_PROXYProxy para socks
no_proxyNO_PROXYDeclaração de destinos sem proxy
all_proxyALL_PROXYProxy fallback para todos os protocolos

3.3 – Bibliotecas de cliente HTTP

Você precisará estudar o manual da biblioteca HTTP que seus programas utilizam para afinar a configuração de proxy. As principais bibliotecas são:

  • Python: requests (a mais popular), urllib3 (baixo nível, avançada), httpx (moderna, HTTP/2), aiohttp (async nativa), pycurl (performance máxima, multi-protocolo), httplib2 (mais antiga, cache, HTTP/2), urllib (stdlib, nativa da linguagem);
  • JavaScript: fetch API (nativa), axios (mais popular), node-fetch (versões antigas do Node), got (rica em funcionalidades), ky (wrapper leve), undici (alta performance), superagent (encadeável, tem plugins), needle (leve, suporta streaming);
  • Go: net/http (stdlib, geral), resty (wrapper da net/http), heimdall (avançada), gentleman (baseado em plugins), grequests (inspirada na requests do Python), go-retryablehttp (HashiCorp, net/http com retries automáticos e backoff).

3.4 – Declarando e usando variáveis de proxy

Exemplos de como declarar variáveis de ambiente e seus efeitos.

Usando endereço de proxy universal para todos os protocolos:

Bash
# Usando all_proxy:
    export all_proxy="http://proxy.empresa.com:3128"
    unset  ftp_proxy;
    unset  http_proxy;
    unset  https_proxy;
    
# Todas as requisicoes abaixo vao via HTTP até o proxy (sem criptografia)
    curl  http://api.ipify.org;  echo;  # Usa o proxy (http => proxy => http)
    curl  https://api.ipify.org; echo;  # Usa o proxy (http => proxy => https)
    curl  ftp://api.ipify.org;   echo;  # Usa o proxy (http => proxy => ftp)

Proxy apenas para HTTP ou apenas para HTTPs:

Bash
# Apenas HTTP
    # - Declarar proxy HTTP
    export http_proxy="http://proxy0.empresa.com:3128";
    # - Sem proxy HTTPs
    export https_proxy=""; # declarar vazio, ou
    unset  https_proxy;    # remover variavel de ambiente

    # Requisicao HTTP => Proxy HTTP
    curl  http://api.ipify.org;  echo; # Usar proxy na porta 3128 do proxy0

    # Requisicao HTTPs => direto na Internet
    curl  https://api.ipify.org; echo; # Nao passa pelo proxy

# Apenas HTTPs
    # - Sem proxy HTTP
    export http_proxy=""; # declarar vazio, ou
    unset  http_proxy;    # remover variavel de ambiente
    # - Declarar proxy HTTPs
    export https_proxy="https://proxy0.empresa.com:8443";

    # Requisicao HTTP => direto na Internet
    curl  http://api.ipify.org;  echo; # Nao passa pelo proxy

    # Requisicao HTTPs => Proxy HTTPs
    curl  https://api.ipify.org; echo; # Usar proxy na porta 8443 do proxy0

CURL com proxy HTTP e HTTPs em endereços diferentes:

Bash
# Declarar enderecos dos servidores proxy:
# - Proxy HTTP
export http_proxy="http://proxy1.empresa.com:3128"
# - Proxy HTTPs
export https_proxy="http://proxy2.empresa.com:3129"

# Acessar sites:
curl  http://api.ipify.org;  echo;  # Usa proxy na porta 3128 do proxy1
curl  https://api.ipify.org; echo;  # Usa proxy na porta 3129 do proxy2

# Ignora o uso de proxy:
curl --no-proxy  http://api.ipify.org;  echo; # Acesso direto na Internet sem proxy
curl --no-proxy  https://api.ipify.org; echo; # Acesso direto na Internet sem proxy

CURL com proxy geral de fallback:

Bash
# Declarar enderecos dos servidores proxy:
# - Proxy HTTP, HTTPs, Sockets, FTP, ...
export all_proxy="http://proxy3.empresa.com:3128"

# Acessar sites:
curl  http://api.ipify.org;  echo;  # Usa proxy na porta 3128 do proxy3
curl  https://api.ipify.org; echo;  # Usa proxy na porta 3128 do proxy3

CURL com proxy geral de fallback e HTTPs (mais específico vence):

Bash
# Declarar proxy de HTTPs:
export https_proxy="http://proxy4.empresa.com:8443";

# Declarar proxy geral:
export all_proxy="http://proxy5.empresa.com:3128";

# Acessar sites:
curl  http://api.ipify.org;  echo;  # Usa proxy na porta 3128 do proxy5
curl  https://api.ipify.org; echo;  # Usa proxy na porta 8443 do proxy4

CURL especificando proxy na linha de comando (sem variável de ambiente):

Bash
# Proxy explicito, canal HTTP acessando site HTTP:
curl  -m 5  -x http://proxy6.empresa.com:1080  http://api.ipify.org; echo;

# Proxy explicito, canal HTTP (insecuro) acessando site HTTPs:
curl  -m 5  -x http://proxy7.empresa.com:3128  https://api.ipify.org; echo;

4 – Canais e segurança

Existem 3 canais (protocolos) para comunicação com um proxy:

  • HTTP (RFC 2616): Transporta o protocolo em puro texto sobre TCP (padrão) ou UDP;
  • HTTPs (RFC 2818): Transporta o protocolo HTTP dentro de uma conexão TCP protegida por TLS (padrão) ou SSL;
    • Envolve o uso de certificados x509;
    • Certificados auto-assinados não possuem reconhecimento dos clientes;
    • Certificados assinados tem preço, os gratuitos possuem validade curta (3 meses);
  • ICP (RFC 2186): Utiliza o Internet Cache Protocol como canal de comunicação, normalmente somente entre servidores proxy (essa adjacência se chama cache peer).

4.1 – Combinações de diferentes canais

O cliente pode se comunicar com o Proxy usando as seguintes combinações de protocolos cliente=>proxy=>site:

HTTPs fim-a-fim – Combinação perfeita: Cliente se conecta via HTTPs no Proxy, solicita método CONNECT e é conduzido de forma transparente até o site HTTPs. Apenas o IP é alterado (o site enxerga o IP do Proxy apenas).

HTTPs é usado nos dois canais, Cliente para Proxy, Proxy para Site.

HTTP até o Proxy: Cliente se conecta via HTTP com o Proxy, não há criptografia nesse canal. O proxy se conecta via HTTPs no site. Essa combinação é ideal apenas quando o Proxy está na rede privada com o cliente ou a comunicação entre o cliente e o proxy é protegida na camada inferior (VPN, IPSec, VXLAN+IPSec, Wireguard, OpenVPN, etc).

x

x

x

4.2 – Certificados x509

Conexões HTTPs (HTTP seguro) requer criptografia utilizando certificados x509. Esses certificados possuem atributos para tornar o uso exclusivo seu uso no recurso: IP ou nome de DNS (Common Name). Um certificado emitido para patrickbrandao.com não pode ser usado em um site como o ajustefino.com, cada site precisa do seu certificado.

O proxy, ao entrar no meio de uma conexão entre o cliente e o site, precisa:

  • Ter seu próprio certificado, o proxy que serve ao cliente usando HTTPs deve possuir o certificado para seu nome de DNS (FQDN = x509 Common Name);
  • Não se intrometer entre o cliente e o site HTTPs final, o Common Name do Proxy é diferente do Common Name do site.

x

5 – Squid no Docker

Para usar o Squid o processo é muito simples, você pode construir um container do zero. Você poderá incluir sua configuração personalizada e embutir sua regra de negócios nele.

Nesse capítulo vou demonstrar uma POC (prof of concept) de como rodar. Esse projetinho não é exatamente completo e perfeito mas serve para a didática da técnica.

Crie uma pasta para esse projeto, vou chamar de “squid-basic“. Na pasta coloque o arquivo Dockerfile:

Dockerfile – squid-basic/Dockerfile
# Container do Squid com base Alpine
FROM        alpine:3.23.3

# Variaveis globais
ENV         TZ=America/Sao_Paulo
ENV         PS1='\u@\h:\w\$ '
ENV         LANG=en_US.UTF-8
ENV         LANGUAGE=en_US.UTF-8

# Atualizar base
RUN         apk update && apk upgrade

# Instalar pacotes
RUN         apk add squid curl openssl

# Envs de proxy, ontainer usando o proprio squid como proxy
ENV         http_proxy=http://localhost:1080/

# https requer projetar uso de certificados
#ENV         https_proxy=https://localhost:443/

# Portas internas do Squid
EXPOSE      443/tcp
EXPOSE      1080/tcp
EXPOSE      8080/udp
EXPOSE      8443/tcp

# Processo do container
CMD         [ "/usr/sbin/squid", \
              "-n", \
              "proxy", \
              "-f", \
              "/etc/squid/squid.conf", \
              "--foreground" \
            ]

Dentro da pasta do projeto, construa a imagem “squid-basic” com tag padrão “latest“:

Bash
# Criar imagem squid-basic
    docker build . \
        --pull \
        --no-cache \
        -f Dockerfile \
        -t squid-basic:latest;

A imagem está pronta para uso, mas infelizmente, usando a configuração padrão do squid em /etc/squid/squid.conf (caminho dentro do container).

Vamos criar um arquivo squid.conf na pasta do projeto para mapear como volume de arquivo dentro do container:

squid.conf
# Portas HTTP
http_port 1080
http_port 3128
http_port 8080

# Portas HTTPs, precisa projetar a forma de gerar e usar certificados
#https_port 8443 cert=/certs/fullchain.pem key=/certs/privkey.pem
#https_port 443 cert=/certs/fullchain.pem key=/certs/privkey.pem

# Lista de prefixos para controle de acesso
# - Localhost
acl LOCALHOSTv4 src 127.0.0.1/32
acl LOCALHOSTv6 src ::1/128
acl LOCALHOST src 127.0.0.1/32
acl LOCALHOST src ::1/128

# - Redes locais conhecidas
acl LOCALNETSv4 src 10.117.0.0/16
acl LOCALNETSv6 src 2001:db8:10:117::/64
acl LOCALNETSv6 src fe80::/64

# - Redes privadas (todas)
# - IPv4:
acl PRIVATESv4 src 10.0.0.0/8
acl PRIVATESv4 src 169.254.0.0/16
acl PRIVATESv4 src 172.16.0.0/12
acl PRIVATESv4 src 192.0.0.0/24
acl PRIVATESv4 src 192.0.2.0/24
acl PRIVATESv4 src 192.88.99.0/24
acl PRIVATESv4 src 192.168.0.0/16
acl PRIVATESv4 src 198.18.0.0/15
acl PRIVATESv4 src 198.51.100.0/24
acl PRIVATESv4 src 203.0.113.0/24
acl PRIVATESv4 src 224.0.0.0/4
acl PRIVATESv4 src 240.0.0.0/4
# - IPv6:
acl PRIVATESv6 src ::/3
acl PRIVATESv6 src 2001:db8::/32
acl PRIVATESv6 src 4000::/2
acl PRIVATESv6 src 8000::/1

# - Redes publicas confiaveis (seus servidores, VPs, VM)
# - IPv4
acl ALLOWED_NETWORKSv4 src 172.233.34.15
acl ALLOWED_NETWORKSv4 src 45.255.128.0/22
# - IPv4
acl ALLOWED_NETWORKSv6 src 2600:3c0d::f04d:a4ef:fc13:7cb8/128
acl ALLOWED_NETWORKSv6 src 2804:cafe::/32

# Controle de acesso
# - Sempre permitir localhost
http_access allow LOCALHOSTv4
http_access allow LOCALHOSTv6

# - Sempre permitir redes locais (docker networks)
http_access allow LOCALNETSv4
http_access allow LOCALNETSv6

# - Sempre permitir redes privadas
http_access allow PRIVATESv4
http_access allow PRIVATESv6

# - Sempre permitir IPs publicos conhecidos
http_access allow ALLOWED_NETWORKSv4
http_access allow ALLOWED_NETWORKSv6

# - NEGAR TODO O RESTO
http_access deny all

# Opcoes gerais
pid_filename        /run/squid.pid
icon_directory      /run
err_page_stylesheet none

# Ajustes de parametros
forward_max_tries  25
connect_timeout     2 minutes
read_timeout       15 minutes
request_timeout     5 minutes
shutdown_lifetime  30 seconds

# Arquivos de logs
access_log      daemon:/data/logs/squid-access.log squid !LOCALHOST
cache_log       /dev/null
cache_store_log none
logfile_rotate 10

# Cache
cache_replacement_policy heap GDSF
cache_mem 0 MB
maximum_object_size_in_memory 8 MB
minimum_object_size 0 KB
maximum_object_size 4 MB

# Modo oculto, nao encaminhar cabecalhos que revelam a existencia do proxy
# - Desativar cabecalhos nativos
via off
forwarded_for delete
follow_x_forwarded_for deny all
httpd_suppress_version_string on
visible_hostname localhost
strip_query_terms off
uri_whitespace strip
# - Retirar cabecalhos do Squid
reply_header_access X-Cache deny all
reply_header_access X-Cache-Lookup deny all
reply_header_access X-Squid-Error deny all
request_header_access X-xproxy-role deny all
request_header_access X-Forwarded-For deny all
request_header_access Forwarded deny all
request_header_access Proxy-Connection deny all
request_header_access Proxy-Authorization deny all

Agora vamos rodar o container. Na pasta do projeto onde está o squid.conf, execute:

Bash
# Rede de containers squid (ipv4 only)
    docker network create proxynet \
        -d bridge \
        -o com.docker.network.bridge.name=br-proxynet \
        -o com.docker.network.driver.mtu=1500 \
        -o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected;

# Pasta do volume
    # - Pasta principal
    mkdir -p /storage/squid-basic;

    # - Pasta para logs
    mkdir -p /storage/squid-basic/logs;
    chmod 777 /storage/squid-basic/logs;

# Renovar/rodar container squid-basic:
    # Remover atual
    docker rm -f squid-basic 2>/dev/null;

    # Criar e rodar
    docker run -d \
        --restart=always \
        --name=squid-basic \
        --hostname squid-basic.intranet.br \
        \
        --user=root --cap-add=ALL --privileged \
        \
        --cpus=1 \
        --memory 512m --memory-swap 512m --shm-size 512m \
        \
        --tmpfs /run:rw,size=8m \
        -v /storage/squid-basic:/data \
        -v ./squid.conf:/etc/squid/squid.conf \
        \
        --network proxynet \
        \
        -p 1080:1080 \
        -p 3128:3128 \
        \
        squid-basic;

Container rodando, vamos entrar no shell do container:

Bash
# Entrar no shell ash do container:
docker exec -it squid-basic ash;

Testando proxy:

Bash
# Testar com proxy HTTP via variavel http_proxy
curl -v http://api.ipify.org; echo;

    # * Uses proxy env variable http_proxy == 'http://localhost:1080/'
    # * Host localhost:1080 was resolved.
    # * IPv6: ::1
    # * IPv4: 127.0.0.1
    # *   Trying [::1]:1080...
    # * Established connection to localhost (::1 port 1080) from ::1 port 36930 
    # * using HTTP/1.x
    # > GET http://api.ipify.org/ HTTP/1.1
    # > Host: api.ipify.org
    # > User-Agent: curl/8.17.0
    # > Accept: */*
    # > Proxy-Connection: Keep-Alive
    # > 
    # * Request completely sent off
    # < HTTP/1.1 200 OK
    # < Date: Sun, 12 Apr 2026 15:15:07 GMT
    # < Content-Type: text/plain
    # < Content-Length: 14
    # < Server: cloudflare
    # < Vary: Origin
    # < cf-cache-status: DYNAMIC
    # < CF-RAY: 9eb32f424ab1c6ce-GRU
    # < Cache-Status: localhost;detail=mismatch
    # < Connection: keep-alive
    # < 
    # * Connection #0 to host localhost:1080 left intact
    # 45.255.128.158

Observe a primeira linha “http_proxy == ‘http://localhost:1080/’“.

Temos um proxy muito básico e funcional que infelizmente não atende HTTPs.

5 – Projeto Squid Gateway

Criei um projeto chamado “squid-gateway” capaz de prover um Squid pronto para produção e replicação e com suporte HTTPs com certificados do Traefik.

Pré-requisitos:

  • Container do Traefik;
  • Container do Traefik-Certs (minha autoria);

5.1 – Rede Docker

É necessário criar a rede Docker para definir como seu proxy irá lidar com o protocolo IP.

Rede somente IPv4 – Proxy não fará acessos em IPv6

Bash
# Rede de containers Squid - Somente IPv4
    docker network create squidnet -d bridge \
        -o com.docker.network.bridge.name=br-squidnet \
        -o com.docker.network.driver.mtu=1500 \
        -o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected;

Rede dual-stack – IPv4 e IPv6, o proxy escolherá por conveniência do DNS e rapidez

Bash
# Rede de containers Squid - Dual-stack IPv4 e IPv6
    docker network create squidnet -d bridge --ipv6 \
        -o com.docker.network.bridge.name=br-squidnet \
        -o com.docker.network.driver.mtu=1500 \
        -o com.docker.network.bridge.gateway_mode_ipv4=nat-unprotected;

5.2 – Certificado

x

x

(parei aqui, ainda estou escrevendo)

x

x

Todos os meus movimentos
foram friamente calculados.
Chapolim Colorado

Terminamos por hoje!

Patrick Brandão, patrickbrandao@gmail.com