Olá, Habr! Meu nome é Valentin, sou engenheiro DevOps na equipe Platform V Kintsugi. Trabalhamos no desenvolvimento de um serviço em nuvem e, na prática, encontramos regularmente tanto desafios arquiteturais na construção de sistemas distribuídos quanto questões de segurança.
Na parte anterior, analisamos detalhadamente o mecanismo de delegação de conexão TLS para o nível do Service Mesh e mostramos como o Egress Gateway pode atuar como um participante completo no handshake do PostgreSQL. No entanto, esse cenário foi considerado em uma configuração simplificada – um serviço, um certificado, uma conexão. Em um sistema real, tudo é organizado de forma diferente: diferentes serviços usam diferentes contas, diferentes certificados e acessam os mesmos sistemas externos. Isso leva à necessidade de selecionar dinamicamente a política TLS e rotear corretamente o tráfego TCP com base na origem. É aqui que as coisas ficam interessantes!
Imagine um cenário em que vários serviços acessam um único servidor PostgreSQL, mas cada serviço:
- Usa uma conta técnica separada;
- Possui seu próprio certificado de cliente;
- Opera com seu próprio banco de dados lógico.
Vamos analisar como a interação é construída nessas condições e quais limitações surgem ao tentar centralizar o gerenciamento de conexões seguras.
Modelaremos a situação descrita implantando dois serviços – psql-postgres e psql-kintsugi. Cada um deles estabelecerá uma conexão segura com seu banco de dados, localizado no servidor SGBD, enquanto a criptografia do tráfego será realizada no lado do Egress Gateway usando sua própria conta técnica e o certificado de cliente correspondente.
Nesta fase, surge a questão crucial: como o Egress Gateway deve determinar qual certificado específico usar para uma determinada conexão? Suponha que o serviço psql-postgres precise se autenticar no servidor de banco de dados usando a conta postgres (CN=postgres), e para o serviço psql-kintsugi, criaremos uma conta separada kintsugi e emitiremos o certificado correspondente (CN=kintsugi). Tentaremos resolver essa tarefa usando os recursos do Service Mesh, dividindo o tráfego no nível do sidecar usando sourceLabels e subsets, e associando uma política TLS a cada fluxo.
Para isso, descreveremos os recursos necessários sequencialmente. Registramos o servidor PostgreSQL externo no registro do Istio:
yamlapiVersion: networking.istio.io/v1beta1 kind: ServiceEntry metadata: name: postgres-se-tls-origin spec: endpoints: - address: 10.40.20.72 exportTo: - . hosts: - postgres.solution.test location: MESH_EXTERNAL ports: - name: postgres-5432 number: 5432 protocol: postgres resolution: STATIC
Como resultado, o serviço externo se torna acessível dentro da mesh e pode participar do roteamento e da aplicação de políticas.
Definimos o serviço para proxy de tráfego através do Egress Gateway:
yamlkind: Service apiVersion: v1 metadata: name: postgres-egress-service-tls-origin spec: ports: - name: tcp-5001 protocol: TCP port: 5001 targetPort: 5001 type: ClusterIP selector: app: egressgateway
O tráfego de todos os serviços para o Egress Gateway passará pela porta 5001. Adicionamos um ponto de entrada no Egress Gateway:
yamlapiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: postgres-gw-tls-origin-psql-postgres spec: selector: app: egressgateway servers: - hosts: - postgres.solution.test port: name: postgres-5001 number: 5001 protocol: TCP
O Gateway recebe tráfego TCP na porta 5001 e o encaminha para a mesh. Para cada canal, um subset separado é criado no DestinationRule, que é usado ao selecionar a política de tratamento de tráfego. Definimos os conjuntos de políticas que usaremos para selecionar a configuração TLS.
Para tráfego interno (da aplicação para o Egress Gateway):
yamlapiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: postgres-internal-dr-tls-origin-postgres spec: exportTo: - . host: postgres-egress-service-tls-origin subsets: - name: postgres-internal-tls-origin-postgres
Para tráfego externo (do Egress Gateway para o servidor SGBD PostgreSQL):
yamlapiVersion: networking.istio.io/v1beta1 kind: DestinationRule metadata: name: postgres-external-dr-tls-origin spec: exportTo: - . host: postgres.solution.test subsets: - name: postgres-external-tls-origin-postgres trafficPolicy: tls: caCertificates: /secrets/istio/egressgateway-certs/ca.crt clientCertificate: /secrets/istio/egressgateway-certs/postgres.crt privateKey: /secrets/istio/egressgateway-certs/postgres.key mode: MUTUAL - name: postgres-external-tls-origin-kintsugi trafficPolicy: tls: caCertificates: /secrets/istio/egressgateway-certs/ca.crt clientCertificate: /secrets/istio/egressgateway-certs/kintsugi.crt privateKey: /secrets/istio/egressgateway-certs/kintsugi.key mode: MUTUAL workloadSelector: matchLabels: app: egressgateway
Cada subset corresponde a uma política TLS separada e a um certificado de cliente específico.
Agora vamos configurar o roteamento. Tentaremos dividir o tráfego no nível do sidecar usando sourceLabels e direcioná-lo para diferentes subsets:
yamlapiVersion: networking.istio.io/v1beta1 kind: VirtualService metadata: name: postgres-vs-tls-origin-psql-postgres spec: exportTo: - . gateways: - postgres-gw-tls-origin-psql-postgres - mesh hosts: - postgres.solution.test tcp: - match: - gateways: - mesh port: 5432 sourceLabels: app: psql-postgres route: - destination: host: postgres-egress-service-tls-origin port: number: 5001 subset: postgres-internal-tls-origin-postgres - match: - gateways: - postgres-gw-tls-origin-psql-postgres port: 5001 route: - destination: host: postgres.solution.test port: number: 5432 subset: postgres-external-tls-origin-postgres
Assim, no nível do sidecar, dividimos o tráfego por sourceLabels e o direcionamos para diferentes subsets, cada um dos quais corresponde à sua própria configuração TLS.
A primeira vista, o esquema parece correto, então vamos verificar e ver como ele se comporta na prática.
Executamos a conexão do serviço psql-postgres:
bash10001@psql-postgres-8f7584f7d-j97dt:/$ psql "host=postgres.solution.test user=postgres port=5432 dbname=postgres sslmode=disable" postgres=# SELECT ssl, version, cipher FROM pg_stat_ssl WHERE pid = pg_backend_pid(); ssl | version | cipher -----+---------+----------------------------- t | TLSv1.2 | ECDHE-RSA-AES256-GCM-SHA384 (1 row)
Agora executamos uma consulta semelhante do serviço psql-kintsugi:
bash10001@psql-kintsugi-79db99ff5d-dghpz:/$ psql "host=postgres.solution.test user=kintsugi dbname=kintsugi port=5432 sslmode=disable" psql: error: connection to server at "postgres.solution.test" (10.40.20.72), port 5432 failed: FATAL: certificate authentication failed for user "kintsugi"
O resultado não foi exatamente o que esperávamos.
Para o serviço psql-postgres, a conexão foi bem-sucedida – a conexão foi estabelecida, o TLS é usado, o que é confirmado pela saída de pg_stat_ssl. No entanto, ao tentar conectar do serviço psql-kintsugi, recebemos um erro:
FATAL: certificate authentication failed for user "kintsugi"
Vamos prestar atenção aos logs do Egress Gateway:
[2026-04-26T21:28:47.690Z] "- - -" 0 - - - "-" 89 510 564 - "-" "-" "-" "-" "10.40.20.72:5432" outbound|5432|postgres-external-tls-origin-postgres|postgres.solution.test 172.21.15.211:38966 172.21.15.211:5001 172.21.14.161:53470 - -
[2026-04-26T21:29:56.079Z] "- - -" 0 - - - "-" 84 108 14 - "-" "-" "-" "-" "10.40.20.72:5432" outbound|5432|postgres-external-tls-origin-postgres|postgres.solution.test 172.21.15.211:53216 172.21.15.211:5001 172.21.21.113:35306 - -
Apesar de as requisições virem de serviços diferentes, em ambos os casos é usado o mesmo subset – postgres-external-tls-origin-postgres. Consequentemente, o Egress Gateway sempre aplica a mesma política TLS e o mesmo certificado de cliente para todas as conexões, independentemente da origem. É por isso que a conexão para o usuário kintsugi falha: o servidor PostgreSQL recebe o certificado com CN=postgres e rejeita a tentativa de autenticação.
Por que isso aconteceu?
No lado do proxy sidecar, o contexto de origem (incluindo sourceLabels) está realmente disponível, e é por isso que podemos dividir o tráfego com sucesso nesta etapa. No entanto, ao fazer proxy através do Egress Gateway, um novo segmento de conexão TCP é formado, no qual o contexto de roteamento original já não está disponível, ou seja, os rótulos do serviço usados para roteamento no sidecar não entram no próximo segmento da conexão. Como resultado, no nível do Egress Gateway, todas as conexões de entrada parecem um fluxo homogêneo de tráfego TCP, e a seleção da política TLS se torna impossível sem mecanismos adicionais. É por isso que, apesar do roteamento correto no nível do sidecar, a mesma política TLS é aplicada no Egress Gateway para todas as conexões.
Surge uma questão lógica: se o contexto não é transmitido, é possível fixá-lo na própria conexão?
Em vez de tentar transmitir o contexto diretamente, podemos codificá-lo explicitamente nas características L4 do tráfego – por exemplo, direcionando conexões de diferentes serviços para diferentes portas internas do Egress Gateway. Como a porta permanece inalterada durante todo o caminho do pacote, o Egress Gateway obtém a capacidade de distinguir os fluxos e aplicar diferentes políticas a eles. Como resultado, transferimos a lógica de seleção do nível de abstração do Service Mesh para o nível de parâmetros de transporte disponíveis para o proxy.
Para dividir o tráfego no nível do Egress Gateway, introduzimos um sinalizador de transporte adicional – uma porta dedicada.
Primeiro, adicionamos uma nova porta ao serviço através do qual o tráfego do serviço psql-kintsugi será processado:
yamlspec: ports: - name: tcp-5001 protocol: TCP port: 5001 targetPort: 5001 - name: tcp-5002 protocol: TCP port: 5002 targetPort: 5002
Em seguida, definimos o Gateway correspondente que receberá as conexões nesta porta:
yamlapiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: postgres-gw-tls-origin-psql-kintsugi spec: selector: app: egressgateway servers: - hosts: - postgres.solution.test port: name: postgres-tls-origin number: 5002 protocol: TCP
Após isso, fazemos alterações no VirtualService postgres-vs-tls-origin-psql-kintsugi criado anteriormente: em vez da porta comum 5001 (TCP), usaremos a porta dedicada 5002 (TCP), direcionando assim o tráfego para um canal de transporte separado.
yamltcp: - match: - gateways: - mesh port: 5432 sourceLabels: app: psql-kintsugi route: - destination: host: postgres-egress-service-tls-origin port: number: 5002 subset: postgres-internal-tls-origin-kintsugi - match: - gateways: - postgres-gw-tls-origin-psql-kintsugi port: 5002 route: - destination: host: postgres.solution.test port: number: 5432 subset: postgres-external-tls-origin-kintsugi
Em outras palavras, dividimos explicitamente o tráfego no nível L4: cada serviço corresponde a uma porta dedicada do Egress Gateway, e, portanto, a uma política TLS própria. Como resultado, fluxos de transporte independentes são formados, que não se misturam mais no nível do gateway.
Assim como no cenário anterior, verificaremos a conexão com o banco de dados para cada serviço. Para isso, executaremos o comando apropriado dentro do pod de cada aplicação.
bash10001@psql-postgres-8f7584f7d-j97dt:/$ psql "host=postgres.solution.test user=postgres port=5432 dbname=postgres sslmode=disable" postgres=# SELECT ssl, version, cipher FROM pg_stat_ssl WHERE pid = pg_backend_pid(); ssl | version | cipher -----+---------+----------------------------- t | TLSv1.2 | ECDHE-RSA-AES256-GCM-SHA384 (1 row)
Para o serviço psql-postgres, a conexão foi bem-sucedida; verificaremos de forma semelhante para o serviço psql-kintsugi:
bash10001@psql-kintsugi-79db99ff5d-dghpz:/$ psql "host=postgres.solution.test user=kintsugi port=5432 dbname=kintsugi sslmode=disable" kintsugi=> SELECT ssl, version, cipher FROM pg_stat_ssl WHERE pid = pg_backend_pid(); ssl | version | cipher -----+---------+----------------------------- t | TLSv1.2 | ECDHE-RSA-AES256-GCM-SHA384 (1 row)
O resultado é o esperado: ambas as conexões são estabelecidas com sucesso.
Nos logs do Egress Gateway, você pode ver como o tráfego é proxyado e quais subsets são usados para roteamento:
[2026-04-26T21:41:45.957Z] "- - -" 0 - - - "-" 169 664 36540 - "-" "-" "-" "-" "10.40.20.72:5432" outbound|5432|postgres-external-tls-origin-postgres|postgres.solution.test 172.21.15.211:36170 172.21.15.211:5001 172.21.14.161:45982 - -
[2026-04-26T21:45:06.743Z] "- - -" 0 - - - "-" 89 511 8024 - "-" "-" "-" "-" "10.40.20.72:5432" outbound|5432|postgres-external-tls-origin-kintsugi|postgres.solution.test 172.21.15.211:37830 172.21.15.211:5002 172.21.21.113:34314 - -
Assim, conseguimos resolver o problema da conexão multiusuário: cada serviço estabelece uma conexão com o mesmo SGBD, enquanto no nível do Egress Gateway, a política TLS correta é aplicada e o certificado de cliente apropriado é usado. A ideia chave da solução é que o contexto da conexão é codificado nos parâmetros de transporte – neste caso, através da porta. Isso permite contornar a limitação do roteamento TCP no Service Mesh e garantir a seleção da política no lado da infraestrutura. No entanto, essa abordagem tem uma característica arquitetônica importante que deve ser considerada no projeto.
Com o aumento do número de serviços, o número de canais de transporte independentes aumenta:
- Para cada serviço, uma porta dedicada é necessária no Egress Gateway;
- Surgem recursos adicionais que descrevem as políticas de roteamento;
- O volume total de configuração no Service Mesh aumenta.
Isso leva não apenas à complexidade de manutenção, mas também a um potencial aumento da carga no proxy (Envoy): o número de listeners, clusters e regras de roteamento que precisam ser processados aumenta. Como resultado, ao escalar o sistema, essa abordagem pode se tornar um fator que afeta a utilização de recursos e a gerenciabilidade da configuração.
No entanto, este esquema continua sendo uma solução viável e prática para cenários onde:
- É necessário gerenciamento centralizado de TLS;
- É importante remover o trabalho com certificados das aplicações;
- O número de serviços e conexões permanece controlável.
Na prática, tais cenários raramente permanecem isolados. À medida que o sistema cresce, abordagens combinadas ou a busca por mecanismos alternativos de roteamento e autenticação podem ser necessárias – especialmente quando o número de serviços e opções de conexão começa a aumentar.
No entanto, mesmo na variante considerada, a solução já oferece um benefício tangível: o gerenciamento de TLS é centralizado, o trabalho com certificados é transferido para o nível da infraestrutura, e as próprias aplicações se livram de complexidade desnecessária. Como resultado, a arquitetura se torna mais simples e sua manutenção, mais previsível.
Percorremos o caminho desde a conexão básica através do Service Mesh até um cenário multiusuário mais complexo, analisamos as limitações do roteamento TCP e vimos como elas afetam o design da solução. Este é um bom exemplo de como as abstrações de infraestrutura facilitam a vida – mas ao mesmo tempo exigem a compreensão de seus limites.
Tradicionalmente, forneço um link para o repositório com exemplos de configuração discutidos no artigo: https://gitverse.ru/spbvalentine/istio-demo/tag/v1.2.0
Obrigado pela atenção!





