Como proteger e criptografar a comunicação entre clusters do Anthos usando o Anthos Service Mesh

Neste tutorial, descrevemos como usar os gateways de saída e entrada do Anthos Service Mesh para proteger o tráfego entre clusters usando o Transport Layer Security mútuo (mTLS). O tutorial é destinado a administradores de clusters do Kubernetes responsáveis por aspectos da rede, segurança e plataforma. Os controles descritos aqui podem ser especialmente úteis para organizações com requisitos de segurança mais rigorosos ou para cumprir pré-requisitos regulamentares. Este tutorial é acompanhado por um guia de conceito complementar.

Neste tutorial, presumimos que você esteja familiarizado com o Kubernetes e o Anthos Service Mesh.

Objetivos

  • Usar o Terraform para configurar a infraestrutura:
    • Criar uma rede VPC personalizada com duas sub-redes particulares
    • Criar dois clusters do Kubernetes com o Anthos Service Mesh ativado:
    • Registrar clusters no GKE Hub
  • Implantar um cliente MySQL no cluster do GKE
  • Implantar um servidor MySQL em um cluster do kOps
  • Configurar gateways de saída e entrada para expor um servidor usando mTLS
  • Testar o acesso a um servidor MySQL usando um cliente MySQL em execução em diferentes clusters ou VPCs

Custos

Neste tutorial, usamos os seguintes componentes faturáveis do Google Cloud:

Para gerar uma estimativa de custo baseada na projeção de uso deste tutorial, use a calculadora de preços. Novos usuários do Google Cloud podem ser qualificados para uma avaliação gratuita.

Ao concluir este tutorial, exclua os recursos criados para evitar o faturamento contínuo. Para mais informações, consulte Como fazer a limpeza.

Antes de começar

Para este tutorial, você precisa de um projeto do Google Cloud. É possível criar um novo projeto ou selecionar um que já foi criado:

  1. No Console do Google Cloud, acesse a página do seletor de projetos.

    Acessar o seletor de projetos

  2. Selecione ou crie um projeto do Google Cloud.

  3. Verifique se o faturamento está ativado para seu projeto na nuvem. Saiba como confirmar se o faturamento está ativado para o projeto.

  4. No Console do Cloud, acesse o Cloud Shell.

    Acesse o Cloud Shell

    Na parte inferior do Console do Cloud, uma sessão do Cloud Shell é aberta e exibe um prompt de linha de comando. O Cloud Shell é um ambiente shell com o SDK do Cloud pré-instalado, que inclui a ferramenta de linha de comando gcloud. A inicialização da sessão pode levar alguns segundos.

  5. No Cloud Shell, verifique se você está trabalhando no projeto criado ou selecionado:
    export PROJECT_ID=PROJECT_ID
    gcloud config set project ${PROJECT_ID}
    

    Substitua PROJECT_ID pela ID do seu projeto.

  6. Crie uma variável de ambiente para o endereço de e-mail que você usa no Google Cloud:
    export GOOGLE_CLOUD_EMAIL_ADDRESS=GOOGLE_CLOUD_EMAIL_ADDRESS
    

    Substitua GOOGLE_CLOUD_EMAIL_ADDRESS pelo endereço de e-mail que você usa no Google Cloud.

  7. Defina a região e a zona para seus recursos de computação:
    export REGION=us-central1
    export ZONE=us-central1-b
    gcloud config set compute/region ${REGION}
    gcloud config set compute/zone ${ZONE}
    

    Este tutorial usa us-central1 para a região e us-central1-b para a zona. É possível implantar em uma região de sua escolha.

  8. Defina os papéis necessários de gerenciamento de identidade e acesso (IAM, na sigla em inglês). Se você for um proprietário do projeto, já terá todas as permissões necessárias para concluir a instalação. Caso contrário, peça ao administrador para conceder a você papéis do Identity and Access Management (IAM) executando o seguinte comando no Cloud Shell:
    ROLES=(
    'roles/container.admin' \
    'roles/gkehub.admin' \
    'roles/iam.serviceAccountAdmin' \
    'roles/iam.serviceAccountKeyAdmin' \
    'roles/resourcemanager.projectIamAdmin' \
    'roles/compute.securityAdmin' \
    'roles/compute.instanceAdmin' \
    'roles/storage.admin' \
    'roles/serviceusage.serviceUsageAdmin'
    )
    for role in "${ROLES[@]}"
    do
     gcloud projects add-iam-policy-binding ${PROJECT_ID} \
      --member "user:${GOOGLE_CLOUD_EMAIL_ADDRESS}" \
      --role="$role"
    done
    
  9. Ative as APIs necessárias para o tutorial:
    gcloud services enable \
        anthos.googleapis.com \
        anthosgke.googleapis.com \
        anthosaudit.googleapis.com \
        compute.googleapis.com \
        container.googleapis.com \
        cloudresourcemanager.googleapis.com \
        serviceusage.googleapis.com \
        stackdriver.googleapis.com \
        monitoring.googleapis.com \
        logging.googleapis.com \
        cloudtrace.googleapis.com \
        meshca.googleapis.com \
        meshtelemetry.googleapis.com \
        meshconfig.googleapis.com \
        iamcredentials.googleapis.com \
        gkeconnect.googleapis.com \
        gkehub.googleapis.com
    

Como preparar o ambiente

  1. No Cloud Shell, clone o seguinte repositório:

    git clone https://github.com/GoogleCloudPlatform/anthos-service-mesh-samples
    cd anthos-service-mesh-samples/docs/mtls-egress-ingress
    
  2. Atualize o Terraform para o ambiente. Por padrão, o Console do Cloud vem com o Terraform 0.12. Neste tutorial, é preciso que você tenha o Terraform 0.13.5 ou posterior instalado. É possível usar temporariamente outra versão do Terraform executando os seguintes comandos:

    mkdir ~/bin
    curl https://releases.hashicorp.com/terraform/0.13.5/terraform_0.13.5_linux_amd64.zip -o ~/bin/terraform.zip
    unzip ~/bin/terraform.zip -d ~/bin/
    
  3. Acesse a subpasta terraform e inicialize o Terraform:

    cd terraform/
    ~/bin/terraform init
    
  4. Crie um arquivo terraform.tfvars com base nas variáveis de ambiente que você criou anteriormente:

    cat << EOF > terraform.tfvars
    project_id = "${PROJECT_ID}"
    region = "${REGION}"
    zones = ["${ZONE}"]
    EOF
    

    Na próxima etapa, você criará a infraestrutura inicial. Para isso, crie e aplique o plano de execução do Terraform nessa configuração. Os scripts e módulos nesse plano criam o seguinte:

    • Uma rede VPC personalizada com duas sub-redes particulares
    • Dois clusters do Kubernetes com o Anthos Service Mesh ativado
    • Um cluster do GKE
    • Um cluster do kOps em execução na rede VPC personalizada

    O plano de execução também registra clusters no GKE Hub.

  5. Executar o plano de execução:

    ~/bin/terraform plan -out mtls-terraform-plan
    ~/bin/terraform apply "mtls-terraform-plan"
    

    A resposta será semelhante a:

    Apply complete! Resources: 27 added, 0 changed, 0 destroyed.
    Outputs:
    server_token = <sensitive>
    

    A parte <sensitive> é uma variável de saída do Terraform que não é exibida no console, mas pode ser consultada. Por exemplo: ~/bin/terraform output server_token.

  6. Consiga o arquivo kubeconfig do cluster de servidor no diretório terraform. Em seguida, mescle-o com o arquivo de configuração client-cluster:

    cd ..
    export KUBECONFIG=client-server-kubeconfig
    cp ./terraform/server-kubeconfig $KUBECONFIG
    gcloud container clusters get-credentials client-cluster --zone ${ZONE} --project ${PROJECT_ID}
    

    O arquivo client-server-kubeconfig agora contém a configuração dos dois clusters, que pode ser verificada executando o seguinte comando:

    kubectl config view -ojson | jq -r '.clusters[].name'
    

    A saída é esta:

    gke_PROJECT_ID_us-central1-c_client-cluster
    server-cluster.k8s.local
    
  7. Conseguir o contexto dos três clusters para uso posterior:

    export CLIENT_CLUSTER=$(kubectl config view -ojson | jq -r '.clusters[].name' | grep client)
    export SERVER_CLUSTER=$(kubectl config view -ojson | jq -r '.clusters[].name' | grep server)
    echo -e "${CLIENT_CLUSTER}\n${SERVER_CLUSTER}"
    

    A saída é, novamente, esta:

    gke_PROJECT_ID_us-central1-c_client-cluster
    server-cluster.k8s.local
    

    Agora é possível usar esses nomes de cluster como contexto para outros comandos kubectl.

  8. Fazer referência ao cluster de cliente:

    kubectl --context ${CLIENT_CLUSTER} get pods -n istio-system
    
  9. Fazer referência ao cluster do servidor:

    kubectl --context ${SERVER_CLUSTER} get pods -n istio-system
    

Como configurar o lado do cliente

Conforme mencionado no guia de conceito, o lado do cliente requer que você configure o gateway de saída no Anthos Service Mesh.

Nesta seção, você configura os elementos do Anthos Service Mesh para identificar o tráfego externo com base na origem dele e usar um certificado personalizado na criptografia da comunicação. Além disso, convém rotear especificamente apenas esse tráfego para o destino dele (o banco de dados MySQL em um contêiner). Normalmente, você usa um serviço no Kubernetes para fazer isso. Nesse caso, é necessário capturar esse tráfego dentro da comunicação da malha. Para capturar o tráfego, use elementos do Istio para criar uma definição especial do serviço. Defina os seguintes elementos:

  • Gateway de saída
  • Entrada de serviço
  • Serviço virtual
  • Certificados TLS (como um secret)
  • Regras de destino
  1. No Cloud Shell, consiga o endereço IP do gateway de entrada no lado do servidor. Para isso, consulte o endereço IP do balanceador de carga do serviço istio-ingressgateway usando o contexto do lado do servidor ($SERVER_CLUSTER que você criou anteriormente):

    INGRESS_HOST=$(kubectl -n istio-system --context ${SERVER_CLUSTER} get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    

    Como INGRESS_HOST é apenas a parte do endereço IP do host, você precisa criar um nome de domínio totalmente qualificado (FQDN, na sigla em inglês). Essa etapa é necessária porque os certificados exigem um nome de domínio para funcionar corretamente.

    Neste tutorial, você usa o serviço de DNS curinga nip.io para criar um FQDN para o endereço IP de entrada. Esse serviço permite criar o FQDN sem ter um domínio.

  2. Armazene o URL do serviço FQDN em uma variável de ambiente:

    export SERVICE_URL="${INGRESS_HOST}.nip.io"
    

    Agora, com o SERVICE_URL definido como um FQDN, é possível começar a definir a parte do Istio do cluster de cliente.

Criar o gateway de saída

Comece criando o gateway de saída para detectar o tráfego destinado ao serviço externo.

Gateway de saída detectando tráfego destinado ao serviço externo.

  1. No Cloud Shell, crie o seguinte arquivo YAML e nomeie-o como client-egress-gateway.yaml:

    cat <<EOF > client-egress-gateway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
     name: istio-egressgateway-mysql
    spec:
     selector:
       istio: egressgateway
     servers:
     - port:
         number: 15443
         name: tls
         protocol: TLS
       hosts:
       - $SERVICE_URL
       tls:
         mode: ISTIO_MUTUAL
    EOF
    
  2. Aplique o arquivo YAML anterior ao cluster do cliente:

    kubectl --context ${CLIENT_CLUSTER} apply -f client-egress-gateway.yaml
    

    Preste atenção às portas. Você usou as portas default aqui para a chave dos servidores de saída, que é 15443. Se você quiser usar uma porta diferente, edite o objeto service do gateway de saída para adicionar portas personalizadas.

    A chave hosts define o endpoint, que é aonde o tráfego precisa ir.

Definir a entrada de serviço

A próxima etapa é informar a malha de serviço sobre o serviço externo. O Istio tem um registro próprio em que armazena endpoints de serviço para a malha. Se o Istio for instalado sobre o Kubernetes, os serviços definidos no cluster serão adicionados automaticamente ao registro do Istio. Com a definição de entrada de serviço, você adiciona um novo endpoint ao registro do Istio, conforme mostrado no diagrama a seguir.

Adicionar um endpoint ao registro do Istio usando uma definição de entrada de serviço

  1. No Cloud Shell, crie o seguinte arquivo YAML e nomeie-o como client-service-entry.yaml:

    cat <<EOF > client-service-entry.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: ServiceEntry
    metadata:
     name: mysql-external
    spec:
     hosts:
       - $SERVICE_URL
     location: MESH_EXTERNAL
     ports:
       - number: 3306
         name: tcp
         protocol: TCP
       - number: 13306
         name: tls
         protocol: TLS
     resolution: DNS
     endpoints:
       - address: $SERVICE_URL
         ports:
           tls: 13306
    EOF
    
  2. Aplique o arquivo YAML anterior ao cluster do cliente:

    kubectl --context ${CLIENT_CLUSTER} apply -f client-service-entry.yaml
    

    A definição do serviço de cliente neste arquivo YAML informa ao serviço que tipo de tráfego esperar (MySQL L4 - camada de rede 4, usando porta 3306; link em inglês). Você também define que a comunicação será "malha externa". Na seção de endpoints, você define que o fluxo precisa ir para o endereço FQDN $SERVICE_URL que você definiu anteriormente e que é mapeado para o gateway de entrada no cluster de servidor (kOps).

Definir o serviço virtual

Um serviço virtual é um conjunto de regras de roteamento de tráfego a serem aplicadas quando um host é resolvido. Cada regra de roteamento define critérios correspondentes para o tráfego de um protocolo específico. Se o tráfego for correspondido, ele será enviado para um serviço de destino nomeado, ou para um subconjunto ou versão dele, definido no registro. Para mais informações, consulte a documentação do Istio (em inglês).

Definição de um serviço virtual que informa ao Istio como aplicar o roteamento para o tráfego que alcança o serviço externo.

A definição do serviço virtual informa ao Istio como aplicar o roteamento para o tráfego que está chegando ao serviço externo. Com a definição a seguir, você configura a malha para rotear o tráfego do cliente ao gateway de saída na porta 15443. No gateway de saída, você roteia o tráfego para o host $SERVICE_URL na porta 13306 (onde o gateway de entrada do lado do servidor está detectando).

  1. Crie o seguinte arquivo YAML e nomeie-o como client-virtual-service.yaml:

    cat <<EOF > client-virtual-service.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
     name: direct-mysql-through-egress-gateway
    spec:
     hosts:
       - $SERVICE_URL
     gateways:
       - istio-egressgateway-mysql
       - mesh
     tcp:
       - match:
           - gateways:
               - mesh
             port: 3306
         route:
           - destination:
               host: istio-egressgateway.istio-system.svc.cluster.local
               subset: mysql
               port:
                 number: 15443
             weight: 100
       - match:
           - gateways:
               - istio-egressgateway-mysql
             port: 15443
         route:
           - destination:
               host: $SERVICE_URL
               port:
                 number: 13306
             weight: 100
    EOF
    
  2. Aplique a definição de YAML ao cluster de cliente:

    kubectl --context ${CLIENT_CLUSTER} apply -f client-virtual-service.yaml
    

    É possível especificar em quais gateways aplicar a configuração editando a chave gateways no arquivo YAML.

    A parte importante nesta definição é o uso da palavra reservada mesh, que abrange todos os arquivos secundários na malha. De acordo com a documentação do Istio, quando esse campo é omitido, o gateway padrão (malha) é usado, aplicando a regra a todos os arquivos secundários na malha. Se você fornecer uma lista de nomes de gateway, as regras serão aplicadas apenas aos gateways. Para aplicar as regras a gateways e arquivos secundários, especifique mesh como um dos nomes de gateway.

Na próxima seção, você define como processar o tráfego que está saindo do proxy do produto do cliente match.gateways.mesh. Você também define como rotear o tráfego da saída para o serviço externo usando a chave match.gateways.istio-egressgateway-mysql.

Definir uma regra de destino (do cliente ao gateway de saída)

Agora que você já definiu como rotear o tráfego para o serviço externo, defina quais políticas de tráfego devem ser aplicadas. O serviço virtual que você acabou de definir está processando dois casos de roteamento de uma só vez. Um processa o tráfego do proxy sidecar ao gateway de saída e o outro processa o tráfego do gateway de saída ao serviço externo.

Para corresponder esses casos às regras de destino, são necessárias duas regras separadas. O diagrama a seguir mostra a primeira regra, que processa o tráfego do proxy ao gateway de saída. Nessa definição, você configura o Anthos Service Mesh para usar os certificados padrão da comunicação mTLS.

Regra de destino que define como processar o tráfego do proxy sidecar ao gateway de saída.

  1. No Cloud Shell, crie o seguinte arquivo YAML e nomeie-o como client-destination-rule-to-egress-gateway.yaml:

    cat <<EOF > client-destination-rule-to-egress-gateway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
      name: egressgateway-for-mysql
    spec:
      host: istio-egressgateway.istio-system.svc.cluster.local
      subsets:
        - name: mysql
          trafficPolicy:
            loadBalancer:
              simple: ROUND_ROBIN
            portLevelSettings:
              - port:
                  number: 15443
                tls:
                  mode: ISTIO_MUTUAL
                  sni: $SERVICE_URL
    EOF
    
  2. Aplique a definição YAML anterior ao cluster do cliente:

    kubectl --context ${CLIENT_CLUSTER} apply -f client-destination-rule-to-egress-gateway.yaml
    

    No arquivo YAML anterior, você usou a chave hosts para definir como rotear o tráfego do proxy do cliente ao gateway de saída. Além disso, você configurou a malha para usar ISTIO_MUTUAL na porta 15443, que é uma das portas abertas automaticamente no gateway de saída.

Criar uma regra de destino (do gateway de saída ao serviço externo)

O diagrama a seguir mostra a segunda regra de destino, que informa à malha como processar o tráfego do gateway de saída ao serviço externo.

Segunda regra de destino que define como processar o tráfego do gateway de saída ao serviço externo.

É necessário configurar a malha para usar seus certificados injetados para comunicação TLS mútua com o serviço externo.

  1. No Cloud Shell, no diretório anthos-service-mesh-samples/docs/mtls-egress-ingress, crie os certificados:

     ./create-keys.sh
    

    Forneça uma senha quando o script pedir.

  2. Copie os arquivos gerados para o diretório atual:

    cp ./certs/2_intermediate/certs/ca-chain.cert.pem ca-chain.cert.pem
    cp ./certs/4_client/private/$SERVICE_URL.key.pem client-$SERVICE_URL.key.pem
    cp ./certs/4_client/certs/$SERVICE_URL.cert.pem client-$SERVICE_URL.cert.pem
    
  3. Crie um secret do Kubernetes que armazena os certificados para serem consultados posteriormente no gateway:

     kubectl --context ${CLIENT_CLUSTER} create secret -n istio-system \
      generic client-credential \
      --from-file=tls.key=client-$SERVICE_URL.key.pem \
      --from-file=tls.crt=client-$SERVICE_URL.cert.pem \
      --from-file=ca.crt=ca-chain.cert.pem
    

    O comando anterior adiciona os seguintes arquivos de certificado ao secret:

    client-$SERVICE_URL.key.pem
    client-$SERVICE_URL.cert.pem
    ca-chain.cert.pem
    

    A prática recomendada atual para distribuir certificados é seguida aqui usando o serviço de descoberta de secret (SDS) (em inglês) em vez da montagem de arquivos. Essa prática evita a reinicialização dos pods ao adicionar um novo certificado. A partir do Istio 1.8/1.9, com essa técnica, você não precisa mais dos direitos de acesso de leitura (RBAC, na sigla em inglês) no secret dos gateways.

  4. Adicione os certificados ao DestinationRule e nomeie-o como client-destination-rule-to-external-service.yaml:

    cat <<EOF > client-destination-rule-to-external-service.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: DestinationRule
    metadata:
     name: originate-mtls-for-mysql
    spec:
     host: $SERVICE_URL
     trafficPolicy:
       loadBalancer:
         simple: ROUND_ROBIN
       portLevelSettings:
       - port:
           number: 13306
         tls:
           mode: MUTUAL
           credentialName: client-credential
           sni: $SERVICE_URL
    EOF
    
  5. Aplique a definição YAML anterior ao cluster do cliente:

    kubectl --context ${CLIENT_CLUSTER} apply -f client-destination-rule-to-external-service.yaml
    

    Essa regra só funciona se você tiver criado o secret com antecedência. O secret garante que os certificados sejam usados para a criptografia do mTLS do gateway de saída ao endpoint externo.

Depois de concluir essas etapas, a configuração do lado do cliente está concluída, e é semelhante ao diagrama a seguir.

Configuração final do lado do cliente.

Como configurar o lado do servidor

Conforme mencionado no no guia de conceito, você precisa configurar o gateway de entrada no Anthos Service Mesh no lado do servidor. Antes de criar os arquivos necessários, é recomendável revisar quais componentes são necessários para realizar a parte do servidor da solução.

Resumidamente, você quer identificar o tráfego de entrada com base na origem e no certificado dele. Você também quer rotear especificamente esse tráfego ao destino dele: o banco de dados MySQL em um contêiner. Normalmente, você usa um serviço no Kubernetes para fazer isso, mas neste exemplo, você identifica o tráfego de entrada dentro da comunicação da malha. Portanto, você precisa de uma definição especial de um serviço que inclua o seguinte:

  • Certificados TLS (como um secret)
  • Gateway de entrada
  • Serviço virtual

Criar o secret para o gateway de entrada

Assim como no gateway de saída, os mesmos certificados são necessários para proteger a comunicação do gateway de entrada. Para fazer isso, armazene os certificados em um secret e defina-o com o objeto de gateway de entrada.

  1. No Cloud Shell, copie os arquivos gerados para o local em que você está executando o próximo comando:

    cp ./certs/3_application/private/$SERVICE_URL.key.pem server-$SERVICE_URL.key.pem
    cp ./certs/3_application/certs/$SERVICE_URL.cert.pem server-$SERVICE_URL.cert.pem
    
  2. Crie o secret do servidor:

    kubectl --context ${SERVER_CLUSTER} create secret -n istio-system \
        generic mysql-credential \
        --from-file=tls.key=server-$SERVICE_URL.key.pem \
        --from-file=tls.crt=server-$SERVICE_URL.cert.pem \
        --from-file=ca.crt=ca-chain.cert.pem
    

    Você adicionou os seguintes arquivos de certificado ao secret:

    server-$SERVICE_URL.key.pem
    server-$SERVICE_URL.cert.pem
    ca-chain.cert.pem
    

Definir o gateway de entrada

Para receber o tráfego do cluster do lado do cliente, especifique um gateway de entrada que possa descriptografar e verificar a comunicação TLS usando os certificados.

Definição de um gateway de entrada que inspeciona o tráfego para verificar se ele se atende aos critérios de segurança e encaminhamento.

Esse diagrama mostra onde o gateway de entrada se encontra no cluster. O tráfego passará e será inspecionado se atender aos critérios de segurança e encaminhamento.

  1. No Cloud Shell, use o seguinte arquivo YAML e nomeie-o como server-ingress-gatway.yaml:

    cat <<EOF > server-ingress-gatway.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
     name: gateway-mysql
    spec:
     selector:
       istio: ingressgateway # Istio default gateway implementation
     servers:
     - port:
         number: 13306
         name: tls-mysql
         protocol: TLS
       tls:
         mode: MUTUAL
         credentialName: mysql-credential
       hosts:
       - "$SERVICE_URL"
    EOF
    
  2. Aplique a definição YAML anterior ao cluster do cliente:

    kubectl --context ${SERVER_CLUSTER} apply -f server-ingress-gatway.yaml
    

    Preste atenção à seção tls: porque ela é especialmente importante. Nesta seção, você define que quer o mTLS. Para garantir que isso funcione como esperado, informe o secret criado que contém os certificados.

  3. Ative a porta 13306 aplicando o patch do serviço de entrada. Para ativar essa porta, crie o seguinte arquivo JSON e nomeie-o como gateway-patch.json:

    cat <<EOF > gateway-patch.json
    [{
      "op": "add",
      "path": "/spec/ports/0",
      "value": {
        "name": "tls-mysql",
        "protocol": "TCP",
        "targetPort": 13306,
        "port": 13306
      }
    }]
    EOF
    
  4. Aplique o patch ao serviço do gateway:

    kubectl --context ${SERVER_CLUSTER} -n istio-system patch --type=json svc istio-ingressgateway -p "$(cat gateway-patch.json)"
    

Agora que você abriu a porta no gateway de entrada, será necessário escolher o tráfego proveniente do novo gateway de entrada e direcioná-lo para o pod do banco de dados. Faça isso usando um objeto interno de malha: o serviço virtual.

Definir o serviço virtual

Conforme discutido anteriormente, o serviço virtual é uma definição de padrões de correspondência de tráfego que moldam o tráfego dentro da malha. É necessário identificar e encaminhar corretamente o tráfego do gateway de entrada em direção ao serviço do banco de dados MySQL, conforme mostrado no diagrama a seguir.

Identificação e encaminhamento do tráfego do gateway de entrada ao serviço de banco de dados MySQL.

  1. No Cloud Shell, crie o seguinte arquivo YAML e nomeie-o como server-virtual-service.yaml:

    cat <<EOF > server-virtual-service.yaml
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
     name: mysql-virtual-service
    spec:
     hosts:
       - "$SERVICE_URL"
     gateways:
       - gateway-mysql
     tcp:
       - route:
         - destination:
             port:
               number: 3306
             host: mysql.default.svc.cluster.local
    EOF
    

    É importante que esse serviço virtual faça referência ao gateway de entrada de origem do tráfego. O gateway é chamado de gateway-mysql.

    Como esse serviço virtual é aplicado na L4, você precisa fornecer um sinalizador tcp para descrever o tráfego do MySQL. Basicamente, essa sinalização diz que a L4 é usada para esse tráfego.

    Você pode ter notado que o serviço de entrada está usando a porta 13306 para encaminhar o tráfego. O serviço virtual o seleciona e o traduz de volta para 3306.

    Por fim, você está encaminhando o tráfego para o servidor MySQL no cluster do servidor do Kubernetes. Para este exemplo, o servidor está detectando na porta padrão 3306 do MySQL.

  2. Aplique a definição YAML ao cluster do servidor:

    kubectl --context ${SERVER_CLUSTER} apply -f server-virtual-service.yaml
    

Essas duas definições descriptografam a solicitação do cliente MySQL encapsulado em mTLS e a encaminham para o banco de dados MySQL dentro da malha.

É importante entender que o encaminhamento interno da malha também é feito usando criptografia, mas, neste caso, a criptografia é baseada em certificados internos da malha. O encerramento do mTLS ocorre no gateway.

Agora você tem uma forma completa e mutuamente criptografada de se comunicar com seu servidor MySQL. Essa forma de criptografia é transparente para o cliente e o servidor MySQL. Portanto, nenhuma alteração de aplicativo é necessária. Isso simplifica tarefas como alterar ou fazer rotação de certificados. Além disso, essa forma de comunicação pode ser usada em muitos cenários diferentes.

Testar a configuração

Agora que os lados do cliente e do servidor estão ativos, teste a configuração.

Teste do fluxo de tráfego do lado do cliente ao lado do servidor.

É hora de gerar tráfego do lado do cliente ao lado do servidor. É recomendável seguir o fluxo do tráfego e garantir que ele seja roteado, criptografado e descriptografado como esperado.

  1. No Cloud Shell, implante o servidor MySQL no cluster do servidor:

    kubectl --context ${SERVER_CLUSTER} apply -f server/mysql-server/mysql.yaml
    
  2. Inicie um cliente MySQL no cluster de cliente:

    kubectl run --context ${CLIENT_CLUSTER} --env=SERVICE_URL=$SERVICE_URL -it --image=mysql:5.6 mysql-client-1 --restart=Never -- /bin/bash
    

    Quando o contêiner for iniciado, você verá um shell, que será semelhante ao seguinte:

    root@mysql-client-1:/#
    
  3. Conecte-se ao servidor MySQL:

    mysql -pyougottoknowme -h $SERVICE_URL
    
  4. Use os seguintes comandos para adicionar um banco de dados e uma tabela:

    CREATE DATABASE test_encrypted_connection;
    USE test_encrypted_connection;
    CREATE TABLE message (id INT, content VARCHAR(20));
    INSERT INTO message (id,content) VALUES(1,"Crypto Hi");
    
  5. Depois de conectar e adicionar a tabela, saia da conexão do MySQL e do pod:

    exit
    exit
    

    Você precisa digitar "exit" duas vezes: primeiro para sair da conexão de banco de dados e, depois, para sair do pod. Se o pod parar de responder após a saída, pressione Control+C para cancelar o shell bash.

Ao seguir essas etapas, você terá gerado uma saída de geração de registros significativa que agora pode ser analisada.

Na próxima seção, você verificará se o tráfego do lado do cliente está passando pelo proxy e pelo gateway de saída. Teste também se o tráfego está entrando no lado do servidor pelo gateway de entrada.

Testar o proxy do lado do cliente e o gateway de saída

  1. No Cloud Shell, no proxy do cliente, verifique se é possível ver os registros do proxy do Istio:

    kubectl --context ${CLIENT_CLUSTER} logs -l run=mysql-client-1 -c istio-proxy -f
    

    A resposta será semelhante a:

    [2021-02-10T21:19:08.292Z] "- - -" 0 - "-" "-" 176 115 10 - "-" "-" "-" "-" "192.168.1.4:15443" outbound|15443|mysql|istio-egressgateway.istio-system.svc.cluster.local 192.168.1.12:58614 34.66.165.46:3306 192.168.1.12:39642 - -
    

    Pressione Control+C para sair da saída de registros.

    Nesta entrada de registro, é possível ver que o cliente solicita o servidor em execução no endereço IP 34.66.165.46 na porta 3306. A solicitação é encaminhada (outbound) para istio-egressgateway que detecta o endereço IP 192.168.1.4 na porta 15443. Você definiu esse encaminhamento no serviço virtual (client-virtual-service.yaml).

  2. Leia os registros de proxy do gateway de saída:

    kubectl --context ${CLIENT_CLUSTER} logs -n istio-system -l app=istio-egressgateway -f
    

    A saída de depuração é semelhante a esta:

    [2021-02-10T21:19:08.292Z] "- - -" 0 - "-" "-" 176 115 19 - "-" "-" "-" "-" "34.66.165.46:13306" outbound|13306||34.66.165.46.nip.io 192.168.1.4:53542 192.168.1.4:15443 192.168.1.12:58614 34.66.165.46.nip.io -
    

    Pressione Control+C para sair da saída de registros.

    Nesta entrada de registro, é possível ver que a solicitação do cliente roteada para istio-egressgateway que detecta no endereço IP 192.168.1.4, porta 15443 é roteada para fora da malha de serviço até o serviço externo que detecta no endereço IP 34.66.165.46, porta 13306. Você definiu esse encaminhamento na segunda parte do serviço virtual (client-virtual-service.yaml).

Testar o gateway de entrada do lado do servidor

  1. No Cloud Shell, no lado do servidor, para ver os registros de proxy do gateway de entrada:

    kubectl --context ${SERVER_CLUSTER} logs -n istio-system -l app=istio-ingressgateway -f
    

    A saída no registro é semelhante a esta:

    [2021-02-10T21:22:27.381Z] "- - -" 0 - "-" "-" 0 78 5 - "-" "-" "-" "-" "100.96.4.8:3306" outbound|3306||mysql.default.svc.cluster.local 100.96.1.3:55730 100.96.1.3:13306 100.96.1.1:42244 34.66.165.46.nip.io -
    

    Pressione Control+C para sair da saída de registros.

    Nesta entrada de registro, é possível ver que a solicitação do cliente externo roteada para istio-ingressgateway que detecta no endereço IP 34.66.165.46, porta 13306 é roteada para o serviço MySQL dentro da malha identificada pelo nome do serviço mysql.default.svc.cluster.local na porta 3306. Você definiu esse encaminhamento no gateway de entrada (server-ingress-gateway.yaml).

  2. No servidor MySQL, para ver os registros de proxy do Istio:

    kubectl --context ${SERVER_CLUSTER} logs -l app=mysql -c istio-proxy -f
    

    A resposta será semelhante a:

    [2021-02-10T21:22:27.382Z] "- - -" 0 - "-" "-" 1555 1471 4 - "-" "-" "-" "-" "127.0.0.1:3306" inbound|3306|mysql|mysql.default.svc.cluster.local 127.0.0.1:45894 100.96.4.8:3306 100.96.1.3:55730 outbound_.3306_._.mysql.default.svc.cluster.local -
    

    Pressione Control+C para sair da saída de registros.

    Nesta entrada de registro, é possível ver a chamada de entrada ao servidor de banco de dados MySQL que detecta no endereço IP 100.96.4.8, porta 3306. A chamada é proveniente do pod de entrada com o endereço IP 100.96.1.3.

    Para mais informações sobre o formato de geração de registros do Envoy, consulte Como receber registros de acesso do Envoy (em inglês).

  3. Teste o banco de dados para ver a entrada gerada:

    MYSQL=$(kubectl --context ${SERVER_CLUSTER} get pods -n default | tail -n 1 | awk '{print $1}')
    kubectl --context ${SERVER_CLUSTER} exec $MYSQL -ti -- /bin/bash
    
  4. Verifique o banco de dados criado:

    mysql -pyougottoknowme
    USE test_encrypted_connection;
    SELECT * from message;
    

    A resposta será semelhante a:

    +------+-----------+
    | id   | content   |
    +------+-----------+
    |    1 | Crypto Hi |
    +------+-----------+
    1 row in set (0.00 sec)
    
  5. Para sair do banco de dados MySQL:

    exit
    exit
    

    É necessário digitar exit duas vezes: primeiro para sair da conexão do banco de dados e, depois, para sair do pod.

Testar o acesso ao omitir os certificados

Agora que você testou e verificou se o acesso funciona usando os certificados injetados, teste também o oposto: o que acontece se você omitir o gateway de saída e a injeção de certificado. Esse teste também é chamado de teste negativo.

Para executar esse teste, inicie outro pod em um namespace sem a injeção de proxy secundário ativada.

  1. No Cloud Shell, crie um novo namespace:

    kubectl --context ${CLIENT_CLUSTER} create ns unencrypted
    
  2. Crie um pod e inicie um shell interativo dentro do contêiner:

    kubectl --context ${CLIENT_CLUSTER} run -it --image=mysql:5.6 \
    mysql-client-2 --env=SERVICE_URL=$SERVICE_URL \
    -n unencrypted --restart=Never -- /bin/bash
    
  3. Tente se conectar ao banco de dados quando o shell interativo for iniciado:

    mysql -pyougottoknowme -h $SERVICE_URL
    

    Após 30 segundos, você verá uma saída semelhante a esta:

    Warning: Using a password on the command line interface can be insecure.
    ERROR 2003 (HY000): Can't connect to MySQL server on '104.154.164.12.nip.io' (110)
    

    Esse aviso é esperado porque esse pod está omitindo o gateway de saída e tenta alcançar o gateway de entrada (o $SERVICE_URL) diretamente pela Internet.

  4. Tentar resolver o endereço IP do serviço:

    resolveip $SERVICE_URL
    

    A resposta será semelhante a esta: O endereço IP será diferente.

    IP address of 104.154.164.12.nip.io is 104.154.164.12
    

    Isso prova que o FQDN pode ser resolvido e que a conexão com falha é realmente devido à injeção de certificado ausente.

  5. Para sair da conexão do MySQL e do pod do servidor MySQL:

    exit
    exit
    

Investigação detalhada

Um tópico não abordado neste tutorial é que, normalmente, as configurações de saída pertencem a um papel ou uma organização diferente na sua empresa, porque estão hospedados no namespace istio-system. Configure as permissões do RBAC do Kubernetes para que apenas os administradores de rede possam criar e modificar diretamente os recursos discutidos neste tutorial.

Agora que você sabe como usar uma malha de serviço para garantir a comunicação segura, teste-a com um aplicativo que precisa trocar dados com segurança e onde você quer controlar a criptografia na camada de certificado. Para começar, instale o Anthos Service Mesh.

Tente usar dois clusters do GKE e combiná-los usando a técnica neste tutorial. Essa técnica também funciona na plataforma Anthos entre dois clusters externos do Kubernetes.

As malhas de serviço são uma excelente maneira de aumentar a segurança no cluster, assim como com os serviços externos. Um último caso de uso a ser testado é ter um endpoint mTLS que não seja um segundo cluster do Kubernetes, mas um provedor terceirizado (como um provedor de pagamento).

Limpeza

Para evitar cobranças na sua conta do Google Cloud pelos recursos usados neste tutorial, exclua o projeto.

Exclua o projeto

  1. No Console do Cloud, acesse a página Gerenciar recursos:

    Acessar "Gerenciar recursos"

  2. Na lista de projetos, selecione o projeto que você quer excluir e clique em Excluir .
  3. Na caixa de diálogo, digite o ID do projeto e clique em Encerrar para excluí-lo.

A seguir