Utilize um Gateway para gerenciar o tráfego de entrada e saída para a sua malha (mesh), permitindo especificar qual tráfego você deseja que entre ou saia da mesh.
Este artigo demonstra como expor os serviços do Kubernetes implantados na Malha de Serviço para tráfego externo usando endpoints HTTP e HTTPS.
Motivação
No projeto em que estou atualmente envolvido, assumi a tarefa de implementar o Istio Service Mesh. Meu objetivo inicial envolveu a migração de dois Ingresses do Kubernetes, previamente configurados com o NGINX Ingress, para o Istio Gateway, garantindo a criptografia TLS/SSL.
Como esta é a minha primeira experiência com essa tarefa, comecei consultando a documentação oficial do Istio e estabeleci um ambiente de teste local para validar cada etapa.
Ambiente de Testes
O ambiente de testes utilizou a seguinte pilha de tecnologia:
macOS Monterey versão 12.3.1
Apple M1 Pro
minikube versão v1.26.0
K8s versão v1.22.7
Istio versão 1.13.3
OpenSSL 3.0.5 5 Jul 2022 (Biblioteca: OpenSSL 3.0.5 5 Jul 2022)
Consulte a documentação oficial para saber como instalar o Istio.
Antes de começar
1. Configure a máquina local para resolver o Nome de Domínio Completo (FQDN) do aplicativo.
sudo vi /etc/hosts
127.0.0.1 httpbin.example.com
2. Inicie o minikube com uma versão específica do Kubernetes:
minikube start — kubernetes-version=v1.22.7
3. (em um shell diferente) Inicie minikube tunnel
minikube tunnel
Mantenha esta shell aberta até o final dos testes.
4. Implante o aplicativo de exemplo httpbin:
Certifique-se de que seu diretório atual seja o diretório do Istio.
Inicie o exemplo httpbin.
kubectl apply -f samples/httpbin/httpbin.yaml
Determinando o IP e as portas de entrada
1. Execute o seguinte comando para determinar o IP externo do balanceador de carga do istio-ingressgateway (EXTERNAL-IP):
kubectl get svc istio-ingressgateway -n istio-system
Saída:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
istio-ingressgateway LoadBalancer 10.97.117.233 127.0.0.1 15021:31373/TCP,80:32343/TCP,443:32525/TCP,31400:32279/TCP,15443:31536/TCP 21d
2. Configure o IP e as portas de entrada:
export INGRESS_HOST=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=’{.status.loadBalancer.ingress[0].ip}’)
export INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=’{.spec.ports[?(@.name==”http2")].port}’)
export SECURE_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=’{.spec.ports[?(@.name==”https”)].port}’)
export TCP_INGRESS_PORT=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath=’{.spec.ports[?(@.name==”tcp”)].port}’)
Essas variáveis são usadas posteriormente ao testar o acesso às aplicações usando o comando curl.
Cenário 1 — Acesso usando HTTP
Configurando o ingress usando um gateway do Istio
Um ingress gateway descreve um balanceador de carga que opera na borda da mesh e recebe conexões HTTP/TCP. Ele configura portas expostas, protocolos, entre outros, mas, ao contrário do ingress do Kubernetes, não inclui qualquer configuração de roteamento de tráfego. O roteamento de tráfego é configurado usando regras de roteamento do Istio, exatamente da mesma forma que para solicitações de serviço internos.
Configure um Gateway na porta 80 para o tráfego HTTP declarado no VirtualService:
kubectl apply -f - <<EOF
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: httpbin-gateway
spec:
selector:
istio: ingressgateway # use Istio default gateway implementation
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- "*"
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "httpbin.example.com"
gateways:
- httpbin-gateway
http:
- match:
- uri:
prefix: /status
- uri:
prefix: /delay
route:
- destination:
port:
number: 8000
host: httpbin
EOF
Acessando a aplicação httpbin
curl -s -I -HHost:httpbin.example.com "http://$INGRESS_HOST:$INGRESS_PORT/status/200"
Saída:
HTTP/1.1 200 OK
server: istio-envoy
date: Fri, 08 Jul 2022 18:00:47 GMT
content-type: text/html; charset=utf-8
access-control-allow-origin: *
access-control-allow-credentials: true
content-length: 0
x-envoy-upstream-service-time: 34
Entendendo o que fizemos
Por meio das etapas descritas anteriormente, implantei com sucesso o aplicativo "httpbin" dentro da Service Mesh e estabeleci uma conexāo HTTP acessível na porta 8080. Ao utilizar o serviço "istio-ingressgateway", a porta 80 é aberta para acomodar o tráfego externo da Service Mesh. A configuração do Gateway facilita a entrada de tráfego externo na Service Mesh do Istio, enquanto o Virtual Service assume a responsabilidade de gerenciar o tráfego e aplicar regras de acesso para o aplicativo "httpbin". Essa abordagem garante que o aplicativo permaneça acessível ao tráfego externo.
Removendo as configurações
kubectl delete gateways.networking.istio.io httpbin-gateway
kubectl delete virtualservices.networking.istio.io httpbin
kubectl delete -f samples/httpbin/httpbin.yaml
Cenário 2 — Acesso usando HTTPS
Visão Geral do Cenário
Para implementar TLS/SSL usando o istio-ingress gateway, siga os passos abaixo:
Defina o domínio para os hosts, por exemplo, *.abctest.com, test.xyz.local.
Gere um certificado digital e chaves para o domínio. Mostrarei como usar certificados digitais para um único domínio e certificados wildcard.
Crie um K8s Secret no namespace istio-system com a chave e o certificado gerados no passo 2.
Crie um Gateway por namespace com uma seção servers: para a porta 443 e especifique valores para credentialName que sejam iguais ao segredo criado no passo anterior.
Configure as rotas de tráfego de entrada do gateway . Defina o Virtual Service correspondente para cada recurso em cada namespace.
As duas próximas seções apresentarão a configuração de TLS/SSL para um único domínio e o uso de um certificado wildcard.
Domínio Único
Esta tarefa mostra como expor um serviço HTTPS seguro usando TLS simples.
Certifique-se de realizar as etapas nas seções Antes de começar e Determinando o IP e as portas de entrada.
Gerar certificados e chaves de cliente e servidor
Para esta tarefa, você pode usar sua ferramenta favorita para gerar certificados e chaves. Os comandos abaixo usam o OpenSSL.
Desde a versão 58, o Chrome exige que certificados SSL usem SAN (Nome Alternativo do Assunto) em vez do popular Nome Comum (CN). Passos adicionais são necessários para fazer um certificado autoassinado funcionar. Os comandos abaixo foram baseados em: Corrigindo o Chrome 58+ [missing_subjectAltName] com OpenSSL ao usar certificados autoassinados.
Crie um certificado raiz e uma chave privada para assinar os certificados dos seus serviços:
mkdir -p temp/certs
openssl genrsa -des3 -out temp/certs/rootCA.key 2048
openssl req -x509 -new -nodes -key temp/certs/rootCA.key -sha256 -days 1024 -out temp/certs/rootCA.pem
Você terá que inserir uma passphrase para os certificados raiz e as informações do certificado.
Enter pass phrase for temp/certs/rootCA.key:
Verifying - Enter pass phrase for temp/certs/rootCA.key:
Enter pass phrase for temp/certs/rootCA.key:
You are about to be asked to enter information that will be incorporated into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:UK
State or Province Name (full name) []:England
Locality Name (eg, city) []:London
Organization Name (eg, company) []:mycompany
Organizational Unit Name (eg, section) []:section
Common Name (eg, fully qualified host name) []:httpbin.example.com
Email Address []:your-administrative-address@your-awesome-existing-domain.com
Crie o arquivo de configuração do OpenSSL temp/certs/server.csr.cnf :
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[dn]
C=UK
ST=England
L=London
O=mycompany
OU=section
emailAddress=your-administrative-address@your-awesome-existing-domain.com
CN = httpbin.example.com
Crie o arquivo temp/certs/v3.ext para criar o certificado X509 v3 invés do v1 que ee o padrāo:
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = httpbin.example.com
Crie um certificado e uma chave privada para httpbin.example.com:
openssl req -new -sha256 -nodes -out temp/certs/httpbin.example.com.csr -newkey rsa:2048 -keyout temp/certs/httpbin.example.com.key -config temp/certs/server.csr.cnf
openssl x509 -req -in temp/certs/httpbin.example.com.csr -CA temp/certs/rootCA.pem -CAkey temp/certs/rootCA.key -CAcreateserial -out temp/certs/httpbin.example.com.crt -days 500 -sha256 -extfile temp/certs/v3.ext
Configure um gateway de entrada TLS para um único domínio
Crie um segredo para o gateway de entrada:
kubectl create -n istio-system secret tls httpbin-credential --key=temp/certs/httpbin.example.com.key --cert=temp/certs/httpbin.example.com.crt
Defina um gateway com uma seção servers: para a porta 443 e especifique os valores para o campo credentialName como httpbin-credential. Os valores são os mesmos que o nome do segredo. O modo TLS deve ter o valor SIMPLE.
É uma melhor prática de segurança do Istio evitar configurações de hosts excessivamente amplas.
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # use istio default ingress gateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: httpbin-credential # must be the same as secret
hosts:
- httpbin.example.com
EOF
Configure as rotas de tráfego de entrada do gateway. Defina o Virtual Service correspondente.
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "httpbin.example.com"
gateways:
- mygateway
http:
- match:
- uri:
prefix: /status
- uri:
prefix: /delay
route:
- destination:
port:
number: 8000
host: httpbin
EOF
Acessando a aplicaçāo httpbin usando HTTPS
Enviar uma solicitação HTTPS para acessar o serviço httpbin:
curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" \
--cacert temp/certs/httpbin.example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
Saída:
* Added httpbin.example.com:443:127.0.0.1 to DNS cache
* Hostname httpbin.example.com was found in DNS cache
* Trying 127.0.0.1:443...
* Connected to httpbin.example.com (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: temp/certs/httpbin.example.com.crt
* CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: C=UK; ST=England; L=London; O=mycompany; OU=section; emailAddress=your-administrative-address@your-awesome-existing-domain.com; CN=httpbin.example.com
* start date: Jul 31 15:34:56 2022 GMT
* expire date: Dec 13 15:34:56 2023 GMT
* subjectAltName: host "httpbin.example.com" matched cert's "httpbin.example.com"
* issuer: C=UK; ST=England; L=London; O=mycompany; OU=section; CN=httpbin.example.com; emailAddress=your-administrative-address@your-awesome-existing-domain.com
* SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* 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 0x12d80e800)
> GET /status/418 HTTP/2
> Host:httpbin.example.com
> user-agent: curl/7.79.1
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)!
< HTTP/2 418
< server: istio-envoy
< date: Sun, 31 Jul 2022 15:36:46 GMT
< x-more-info: http://tools.ietf.org/html/rfc2324
< access-control-allow-origin: *
< access-control-allow-credentials: true
< content-length: 135
< x-envoy-upstream-service-time: 32
<
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`
* Connection #0 to host httpbin.example.com left intact
Acesse via navegador e veja o certificado:
Entendemos o que fizemos
Nos passos anteriores, utilizei o OpenSSL para gerar certificados raiz e usar minha máquina local como Autoridade de Certificação. Um certificado foi criado especificamente para o domínio httpbin.example.com e adicionado à minha chave local. Esses processos permitem o uso do TLS/SSL localmente.
Um segredo do K8s é criado para armazenar o certificado e a chave privada no cluster.
Um Gateway é configurado para ouvir na porta segura 443 com TLS como SIMPLE. Isso significa que a comunicação TLS é encerrada no istio-ingressgateway. O ingress gateway recupera informações do certificado correspondentes a um credentialName específico.
O Virtual Service realiza o gerenciamento do tráfego e recursos de política para o aplicativo httpbin.
Removendo a configuraçāo
Remova o certificado da Keychain.
# istio resources
kubectl delete gateway mygateway
kubectl delete virtualservice httpbin
kubectl delete -n istio-system secret httpbin-credential
kubectl delete -f samples/httpbin/httpbin.yaml
# certificate files
rm -rf temp/certs/rootCA.*
rm -rf temp/certs/httpbin.example.com.*
Certificado Wildcard
A tarefa anterior descreve como configurar um gateway de entrada para expor um serviço HTTPS específico usando TLS simples. Esta tarefa mostra como expor serviços usando um certificado curinga.
Certifique-se de realizar as etapas nas seções Antes de começar e Determinando o IP e as portas de entrada.
Além disso, para esta tarefa, implante a aplicação de exemplo helloworld:
kubectl apply -f samples/helloworld/helloworld.yaml
e atualize o arquivo /etc/hosts:
sudo vi /etc/hosts
127.0.0.1 helloworld.example.com
Gerar certificados e chaves de cliente e servidor
Para esta tarefa, você pode usar sua ferramenta favorita para gerar certificados e chaves. Os comandos abaixo usam o OpenSSL.
Desde a versão 58, o Chrome exige que certificados SSL usem SAN (Nome Alternativo do Assunto) em vez do popular Nome Comum (CN). Passos adicionais são necessários para fazer um certificado autoassinado funcionar. Os comandos abaixo foram baseados em: Corrigindo o Chrome 58+ [missing_subjectAltName] com OpenSSL ao usar certificados autoassinados.
Crie um certificado raiz e uma chave privada para assinar os certificados dos seus serviços:
mkdir -p temp/certs
openssl genrsa -des3 -out temp/certs/rootCA.key 2048
openssl req -x509 -new -nodes -key temp/certs/rootCA.key -sha256 -days 1024 -out temp/certs/rootCA.pem
Você terá que inserir uma passphrase para os certificados raiz e as informações do certificado.
Country Name (2 letter code) [AU]:UK
State or Province Name (full name) [Some-State]:England
Locality Name (eg, city) []:London
Organization Name (eg, company) [Internet Widgits Pty Ltd]:mycompany
Organizational Unit Name (eg, section) []:section
Common Name (e.g. server FQDN or YOUR name) []:*.example.comEmail Address []:your-administrative-address@your-awesome-existing-domain.com
Crie o arquivo de configuração do OpenSSL temp/certs/server.csr.cnf:
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
[dn]
C=UK
ST=LN
L=London
O=Beazley
OU=mda
emailAddress=your-administrative-address@your-awesome-existing-domain.com
CN = *.example.com
Crie o arquivo temp/certs/v3.ext para criar um certificado X509 v3 em vez de um v1, que é o padrão:
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = httpbin.example.com
DNS.2 = helloworld.example.com
Crie um certificado e uma chave privada para *.example.com:
openssl req -new -sha256 -nodes -out temp/certs/wildcard.example.com.csr -newkey rsa:2048 -keyout temp/certs/wildcard.example.com.key -config temp/certs/server.csr.cnf
openssl x509 -req -in temp/certs/wildcard.example.com.csr -CA temp/certs/rootCA.pem -CAkey temp/certs/rootCA.key -CAcreateserial -out temp/certs/wildcard.example.com.crt -days 500 -sha256 -extfile temp/certs/v3.ext
Configure um gateway de entrada TLS com domínio curinga
Crie um segredo para o gateway de entrada:
kubectl create -n istio-system secret tls wildcard-credential --key=temp/certs/wildcard.example.com.key --cert=temp/certs/wildcard.example.com.crt
Defina um gateway com uma seção servers: para a porta 443 e especifique os valores para o campo credentialName como httpbin-credential. Os valores são os mesmos que o nome do segredo. O modo TLS deve ter o valor SIMPLE.
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: Gateway
metadata:
name: mygateway
spec:
selector:
istio: ingressgateway # use istio default ingress gateway
servers:
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: wildcard-credential # must be the same as secret
hosts:
- '*.example.com'
EOF
Configure as rotas de tráfego de entrada do gateway. Defina os Virtual Services correspondentes para o httpbin e helloworld.
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: httpbin
spec:
hosts:
- "httpbin.example.com"
gateways:
- mygateway
http:
- match:
- uri:
prefix: /status
- uri:
prefix: /delay
route:
- destination:
port:
number: 8000
host: httpbin
---
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
name: helloworld
spec:
hosts:
- "helloworld.example.com"
gateways:
- mygateway
http:
- match:
- uri:
exact: /hello
route:
- destination:
host: helloworld
port:
number: 5000
EOF
Acessando a aplicaçāo httpbin usando HTTPS
Enviar uma solicitação HTTPS para acessar o serviço httpbin:
curl -v -HHost:httpbin.example.com --resolve "httpbin.example.com:$SECURE_INGRESS_PORT:$INGRESS_HOST" --cacert temp/certs/wildcard.example.com.crt "https://httpbin.example.com:$SECURE_INGRESS_PORT/status/418"
Saída:
Atenção ao Server Certificate onde você pode ver que estamos usando o certificado wildcard.
* Added httpbin.example.com:443:127.0.0.1 to DNS cache
* Hostname httpbin.example.com was found in DNS cache
* Trying 127.0.0.1:443...
* Connected to httpbin.example.com (127.0.0.1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
* CAfile: temp/certs/wildcard.example.com.crt
* CApath: none
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* (304) (IN), TLS handshake, Unknown (8):
* (304) (IN), TLS handshake, Certificate (11):
* (304) (IN), TLS handshake, CERT verify (15):
* (304) (IN), TLS handshake, Finished (20):
* (304) (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / AEAD-CHACHA20-POLY1305-SHA256
* ALPN, server accepted to use h2
* Server certificate:
* subject: C=UK; ST=England; L=London; O=mycompany; OU=section; emailAddress=your-administrative-address@your-awesome-existing-domain.com; CN=*.example.com
* start date: Jul 31 16:00:33 2022 GMT
* expire date: Dec 13 16:00:33 2023 GMT
* subjectAltName: host "httpbin.example.com" matched cert's "httpbin.example.com"
* issuer: C=UK; ST=England; L=London; O=mycompany; OU=section; CN=*.example.com; emailAddress=your-administrative-address@your-awesome-existing-domain.com
* SSL certificate verify ok.
* Using HTTP2, server supports multiplexing
* 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 0x12d012800)
> GET /status/418 HTTP/2
> Host:httpbin.example.com
> user-agent: curl/7.79.1
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 2147483647)!
< HTTP/2 418
< server: istio-envoy
< date: Sun, 31 Jul 2022 16:05:44 GMT
< x-more-info: http://tools.ietf.org/html/rfc2324
< access-control-allow-origin: *
< access-control-allow-credentials: true
< content-length: 135
< x-envoy-upstream-service-time: 5
<
-=[ teapot ]=-
_...._
.' _ _ `.
| ."` ^ `". _,
\_;`"---"`|//
| ;/
\_ _/
`"""`
* Connection #0 to host httpbin.example.com left intact
Acesse via navegador e veja o certificado:
Você pode repetir os passos com o aplicativo helloworld e confirmar que ele está usando o mesmo certificado.
Entendendo o que fizemos
Esta tarefa é uma alternativa à anterior. A principal diferença é que estou criando um certificado que pode ser usado para vários hosts.
Ao criar o certificado e a chave privada, o CN=*.example.com é usado, o que significa que ele aceita todos os hosts para o domínio example.com. Isso é correspondido na entrada dos hosts para o Gateway.
Removendo a configuraçāo
Remove the certificate from the Keychain
# istio resources
kubectl delete gateway mygateway
kubectl delete virtualservice httpbin
kubectl delete virtualservice helloworld
kubectl delete -n istio-system secret wildcard-credential
kubectl delete -f samples/httpbin/httpbin.yaml
kubectl delete -f samples/helloworld/helloworld.yaml
# certificate files
rm -rf temp/certs/rootCA.*
rm -rf temp/certs/wildcard.example.com.*
Resumo
Neste artigo, guiei você pelas configurações necessárias para expor serviços dentro de uma Service Mesh para o tráfego externo. O primeiro cenário abordou uma conexāo HTTP. O segundo cenário examinou as configurações HTTPS. No segundo cenário, apresentei dois caminhos possíveis; um usando um único domínio e outro com um certificado wildcard.
Referências
Comments