MTLS com Ingress-Nginx no Kubernetes | by Adonai Costa | Jul, 2022 – صحيفة الصوت

Você não precisa de Service Mesh e eu posso provar!

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

O service mesh, com todo seu poder e complexidade, pode ser muita coisa. Mas, às vezes, só queremos gerência sobre nossas API’s e garantia de que quem requisita a nossa URL seja sempre confiável e limitado à origem. Para sua surpresa, o Ingress-Nginx, que você já conhece, faz isso e vou te mostrar como!

Coloco abaixo 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 Cluster Kubernetes ( pode ser kind, k3s, minikube)
  • um ingress-nginx controller nesse Kubernetes
  • criar uma Autoridade Certificadora
  • criar os certificados de correspondência entre client e server https

Kubernetes com Kind

Já escrevi um artigo dando essa dica, mas relembrar é viver, então crie seu kubernetes com Kind .

$ cat basic-kind.yamlkind: 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=true

Crie 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 blá blá blá

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”, você 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 este certificado CA, que agora você 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ções para nosso ingress, vamos criar a chave e o 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, você, 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 você não forneceu o certificado e a chave que ele pede para o MTLS.

Lembre-se: são dois certificados diferentes em fases diferentes!

O primeiro é relativo à 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ê mesmo é a autoridade CA que assinou seu certificado, e é esse certificado que o objeto ingress espera para aceitar e liberar 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, você faz o que o service mesh faria, sem o peso de seu control plane no cluster. Por fim, o service mesh faz exatamente isso dentro do seu cluster: distribui certificados a cada 24 horas para garantir o MTLS entre pods e services, além de observar as regras de quem pode acessar o quê.

Seu ingress-controller ainda serve a 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 válido para o MTLS, pois assim você pode abrir a qualquer requisição sua aplicação, com sua CA interna você consegue limitar somente quem você queira que acesse a sua URL. Ainda assim, pode utilizar a annotation 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. Sou suspeito para falar, mas os caras de lá são a melhor fonte de conhecimento e serviço em Kubernetes/BR!

التعليقات

اترك تعليقاً

لن يتم نشر عنوان بريدك الإلكتروني. الحقول الإلزامية مشار إليها بـ *