Kubilab — Mutual TLS no Ingress-Nginx no Kubernetes, é possivel?

Sem service mesh? Sim e te mostro

6 min readJun 30, 2022

--

Tanto se fala em Mutual TLS com service meshs como Istio, Linkerd, Kuma, Consul e que isso é necessario pra fazer esse tipo de Zero Trust Policy, que nada mais é só confiar na requisição de quem realmente pode fazê-la.

Muitas vezes, o service mesh com todo seu poder, pode ser muita coisa quando só queremos garantir que quem requisite nossa URL seja sempre confiável e limitado a certo número de quem são. Então, o ingress NGINX no kubernetes faz isso, e vou te mostrar como.

Primeiro um fluxograma para mostrar no papel como é:

Exemplo (esdrúxulo) de uma conexão básica entre um client e um ingress fechando o MTLS.

Vamos precisar de algumas coisas para validar nossa experiência:

  • um kubernetes ( pode ser kind )
  • um ingress-nginx controller nesse kubernetes
  • criar uma autoridade Certificadora
  • criar os certificados de correspondencia entre client e server

Kubernetes com kind

Ja escrevi um artigo dando essa dica, mas relembrar é viver, entao crie seu kubernetes com kind .

$ cat basic-kind.yaml
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
extraPortMappings:
- containerPort: 80
hostPort: 80
protocol: TCP
- containerPort: 443
hostPort: 443
protocol: TCP
$ kind create cluster --name demo --config basic-kind.yaml

Instale o ingress-nginx controller

$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
$ helm repo update ingress-nginx
$ helm install ingress-nginx -n ingress-nginx ingress-nginx/ingress-nginx --create-namespace --set controller.hostPort.enabled=trueq

Criando uma stack básica com deploy, service, ingress e teste a resposta

$ kubectl create deploy teste --image nginx
$ kubectl expose deploy teste --port 80 --target-port 80
$ kubectl create ingress teste --class=nginx --rule="teste.domain.com/*=teste:80"
$ curl localhost -H "Host: teste.domain.com"
<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>
(...)

Vamos pra o que interessa TLS, CA, certificados, secrets e bla bla bla

Criando a CA e o certificado para nosso ingress:

$ openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout tls-teste.key -out tls-teste.crt -subj "/CN=teste.domain.com/O=teste.domain.com"$ kubectl create secret tls tls-teste --key tls-teste.key --cert tls-teste.crt$ kubectl patch ingress teste -p '{"spec":{"tls":[{"hosts":["teste.domain.com"],"secretName":"tls-teste"}]}}'

Adicione a entrada teste.domain.com no seu /etc/hosts para 127.0.0.1

$ curl -kv https://teste.domain.com
(...)
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):

* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=teste.domain.com; O=teste.domain.com
* start date: Jun 30 15:44:49 2022 GMT
* expire date: Jun 30 15:44:49 2023 GMT
* issuer: CN=teste.domain.com; O=teste.domain.com
* SSL certificate verify result: self signed certificate (18), continuing anyway.

(...)
(...)
<head>
<title>Welcome to nginx!</title>
<style>

Habilitando o Mutual TLS, até que enfim

Criando uma CA “Autoridade Certificadora”, voce agora é dono e assina seus certificados

$ openssl req -x509 -sha256 -newkey rsa:4096 -keyout ca.key -out ca.crt -days 365 -nodes -subj '/CN=Eu sou o maximo babao'

Adicione esse certificado CA, que agora voce tem o poder, no namespace do seu ingress

$ kubectl create secret generic ca-secret --from-file=ca.crt=ca.crt

Adiantando o passo para nosso cliente, que fará as requisiçoes para nosso ingress, vamos criar a chave e certificado que ele vai usar para chamar o https://teste.domain.com

$ openssl req -new -newkey rsa:4096 -keyout cliente.key -out cliente.csr -nodes -subj '/CN=Cliente Remoto'

Agora, voce como mestre e dono da CA, vai assinar o certificado pro seu cliente:

$ openssl x509 -req -sha256 -days 365 -in cliente.csr -CA ca.crt -CAkey ca.key -set_serial 02 -out cliente.crt

Vamos fazer com que o ingress “teste”, só aceite conexões de quem tem o certificado cliente.crt que acabamos de criar

adicione as annotations no ingress$ kubectl edit ingress teste 
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-tls-pass-certificate-to-upstream: "true"
nginx.ingress.kubernetes.io/auth-tls-secret: default/ca-secret
nginx.ingress.kubernetes.io/auth-tls-verify-client: "on"
nginx.ingress.kubernetes.io/auth-tls-verify-depth: "1"

1,2,3,4,5,6 testando…

$ curl -kv https://teste.domain.com
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=teste.domain.com; O=teste.domain.com
* start date: Jun 30 15:44:49 2022 GMT
* expire date: Jun 30 15:44:49 2023 GMT
* issuer: CN=teste.domain.com; O=teste.domain.com

* SSL certificate verify result: self signed certificate (18), continuing anyway.
(...)
> Host: teste.domain.com
> user-agent: curl/7.68.0
> accept: */*
(...)
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
< HTTP/2 400
< date: Thu, 30 Jun 2022 16:27:44 GMT
< content-type: text/html
< content-length: 230
<<html>
<head><title>400 No required SSL certificate was sent</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>

<hr><center>nginx</center>
</body>
</html>

Veja que o certificado do ingress é um ponto que foi negociado pelo nosso curl, mas o ingress não fecha a conversa com o ingress porque voce não forneceu o certificado e chave que ele pede para o MTLS.

Lembre-se sao dois certificados diferentes em fases diferentes;

O primeiro é relativo a exposição da URL teste.domain.com, o segundo é a negociação com o ingress controller ( maravilhoso, salve, salve nginx ) que precisa saber quem está fazendo a requisição para fechar o tunnel tls.

Faça um teste agora, passando o certificado assinado pela autoridade CA, você, que o ingress espera para liberar o tunel TLS e fechar a conexão.

$ curl -kv https://teste.domain.com --key cliente.key --cert cliente.crt
(...)
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Request CERT (13):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Certificate (11):
* TLSv1.3 (OUT), TLS handshake, CERT verify (15):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: CN=teste.domain.com; O=teste.domain.com
* start date: Jun 30 15:44:49 2022 GMT
* expire date: Jun 30 15:44:49 2023 GMT
* issuer: CN=teste.domain.com; O=teste.domain.com
* SSL certificate verify result: self signed certificate (18), continuing anyway.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x5592d26182f0)
> GET / HTTP/2
> Host: teste.domain.com
> user-agent: curl/7.68.0
> accept: */*
>
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* old SSL session ID is stale, removing
* Connection state changed (MAX_CONCURRENT_STREAMS == 128)!
<
HTTP/2 200
< date: Thu, 30 Jun 2022 16:35:54 GMT
< content-type: text/html
< content-length: 615
< last-modified: Tue, 21 Jun 2022 14:25:37 GMT
< etag: "62b1d4e1-267"
< accept-ranges: bytes
< strict-transport-security: max-age=15724800; includeSubDomains
<
<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style> (...)

É a mesma comunicação criptografada 2x para atender a solicitação de origem e ainda criptografar o tráfego.

Finalmente:

Assim voce faz o que o servicemesh faria, sem o peso de seu controlplane no cluster. Por fim, o service mesh faz exatamente isso dentro do seu cluster, distribui certificados a cada 24horas para garantir o MTLS entre pods e services alem de observar as regras de quem pode acessar o que.

Seu ingress controller ainda serve outros sites sem exigir essa criptografia restrita por origem aceita, portanto, é uma boa ideia pensar nisso se deseja limitar o acesso.

Não sugiro o uso de um certificado valido para o MTLS pois assim voce pode abrir a qualquer requisição sua aplicação, com sua CA interna voce consegue limitar somente quem voce queira acessando sua URL. Ainda assim, pode utilizar do whitelist-source-range para fechar ainda mais o acesso.

Controle o tempo de vida do certificado fornecido para o cliente e da sua CA.

Caso tenha dúvidas ou deseje incrementar segurança nos seus kubernetes, subir e descer service mesh, melhorar seus ingress, turbinar seu k8s ou ainda precise de profissionais trabalhando no núcleo do seu ambiente fale com a Getup, são a melhor fonte de conhecimento e serviço em kubernetes/BR

--

--

Adonai Costa
Adonai Costa

Written by Adonai Costa

CKA, CKS, SRE, DevOps, IT and Cloud pathfinder and k8s evangelizer.

No responses yet