Protege y encripta la comunicación entre los clústeres de Anthos mediante Anthos Service Mesh

En este instructivo, se describe cómo usar las puertas de enlace de entrada y salida de Anthos Service Mesh para ayudar a proteger el tráfico entre clústeres mediante la seguridad de la capa de transporte mutua (mTLS). El instructivo está dirigido a los administradores de clústeres de Kubernetes responsables de los aspectos de red, de seguridad y de plataforma. Los controles que se describen aquí pueden ser especialmente útiles para organizaciones con requisitos de seguridad muy estrictos o a fin de cumplir con los requisitos regulatorios. Este instructivo está acompañado por una guía de conceptos complementaria.

En este instructivo, se supone que estás familiarizado con Kubernetes y Anthos Service Mesh.

Objetivos

  • Usar Terraform para configurar la infraestructura:
    • Crear una red de VPC personalizada con dos subredes privadas.
    • Crear dos clústeres de Kubernetes con Anthos Service Mesh habilitada:
    • Registrar clústeres en GKE Hub
  • Implementar un cliente MySQL en un clúster de GKE.
  • Implementar un servidor MySQL en un clúster de kOps
  • Configurar las puertas de enlace de entrada y salida para exponer un servidor mediante mTLS.
  • Probar el acceso a un servidor MySQL mediante un cliente MySQL que se ejecute en diferentes clústeres o VPC.

Costos

En este instructivo, se usan los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud sean aptos para obtener una prueba gratuita.

Cuando finalices este instructivo, podrás borrar los recursos creados para evitar que se te siga facturando. Para obtener más información, consulta cómo hacer una limpieza.

Antes de comenzar

Para este instructivo, necesitas un proyecto de Cloud. Puedes crear uno nuevo o seleccionar un proyecto que ya hayas creado:

  1. En Google Cloud Console, ve a la página del selector de proyectos.

    Ir al selector de proyectos

  2. Selecciona o crea un proyecto de Google Cloud.

  3. Comprueba que la facturación esté habilitada en tu proyecto.

    Descubre cómo puedes habilitar la facturación

  4. En Cloud Console, ve a Cloud Shell:

    Ir a Cloud Shell

    En la parte inferior de Cloud Console, se abre una sesión de Cloud Shell en la que se muestra una ventana emergente de línea de comandos. Cloud Shell es un entorno de shell con el SDK de Cloud ya instalado, incluida la herramienta de línea de comandos de gcloud. La sesión puede tardar unos segundos en inicializarse.

  5. En Cloud Shell, asegúrate de estar trabajando en el proyecto que creaste o seleccionaste:
    export PROJECT_ID=PROJECT_ID
    gcloud config set project ${PROJECT_ID}
    

    Reemplaza PROJECT_ID con el ID del proyecto.

  6. Crea una variable de entorno para la dirección de correo electrónico que usas en Google Cloud:
    export GOOGLE_CLOUD_EMAIL_ADDRESS=GOOGLE_CLOUD_EMAIL_ADDRESS
    

    Reemplaza GOOGLE_CLOUD_EMAIL_ADDRESS por la dirección de correo electrónico que usas en Google Cloud.

  7. Establece la región y la zona para tus recursos de procesamiento:
    export REGION=us-central1
    export ZONE=us-central1-b
    gcloud config set compute/region ${REGION}
    gcloud config set compute/zone ${ZONE}
    

    En este instructivo, se usa us-central1 para la región y us-central1-b para la zona. Puedes implementar en una región de tu elección.

  8. Configura las funciones de administración de identidades y accesos (IAM) requeridas. Si eres el propietario del proyecto, tienes todos los permisos necesarios para completar la instalación. De lo contrario, pídele al administrador que te otorgue las funciones de administración de identidades y accesos (IAM) mediante la ejecución del siguiente comando en 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. Habilita las API necesarias para el instructivo:
    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
    

Prepara tu entorno

  1. En Cloud Shell, clona el siguiente repositorio:

    git clone https://github.com/GoogleCloudPlatform/anthos-service-mesh-samples
    cd anthos-service-mesh-samples/docs/mtls-egress-ingress
    
  2. Actualiza Terraform para tu entorno. De forma predeterminada, Cloud Console incluye Terraform 0.12. En este instructivo, suponemos que tienes instalado Terraform 0.13.5 o una versión posterior. Puedes usar otra versión de Terraform de forma temporal mediante la ejecución de los siguientes 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. Ve a la subcarpeta terraform y, luego, inicializa Terraform:

    cd terraform/
    ~/bin/terraform init
    
  4. Crea un archivo terraform.tfvars (basado en las variables de entorno que creaste antes):

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

    En el siguiente paso, crearás la infraestructura inicial. A fin de hacerlo, debes crear y aplicar el plan de ejecución de Terraform para esta configuración. Las secuencias de comandos y los módulos de este plan crean lo siguiente:

    • Una red de VPC personalizada con dos subredes privadas
    • Dos clústeres de Kubernetes con Anthos Service Mesh habilitado
    • Un clúster de GKE
    • Un clúster de kOps que se ejecuta en la red de VPC personalizada

    El plan de ejecución también registra clústeres en GKE Hub.

  5. Ejecuta el plan de ejecución:

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

    El resultado es similar al siguiente:

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

    La parte <sensitive> es una variable de resultado de Terraform que no se muestra en la consola, pero que se puede consultar. Por ejemplo, ~/bin/terraform output server_token.

  6. Obtén el archivo kubeconfig del clúster de tu servidor desde el directorio terraform. Luego, debes combinarlo con el archivo de configuración 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}
    

    El archivo client-server-kubeconfig ahora contiene la configuración de ambos clústeres, que puedes verificar si ejecutas el siguiente comando:

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

    Este es el resultado:

    gke_PROJECT_ID_us-central1-c_client-cluster
    server-cluster.k8s.local
    
  7. Obtén el contexto de los dos clústeres para usarlo más adelante:

    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}"
    

    El resultado es (otra vez) el siguiente:

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

    Ahora puedes usar estos nombres de clúster como contexto para otros comandos de kubectl.

  8. Haz referencia al clúster del cliente:

    kubectl --context ${CLIENT_CLUSTER} get pods -n istio-system
    
  9. Haz referencia al clúster del servidor:

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

Configura el cliente

Como se mencionó en la guía de conceptos, del lado del cliente, se requiere que configures la puerta de enlace de salida en Anthos Service Mesh.

En esta sección, debes configurar los elementos de Anthos Service Mesh a fin de identificar el tráfico externo según su origen y usar un certificado personalizado para encriptar la comunicación. Además, solo quieres enrutar ese tráfico de forma específica a su destino (la base de datos de MySQL en un contenedor). Por lo general, se hace mediante un servicio en Kubernetes. En este caso, debes capturar ese tráfico dentro de la comunicación de malla. Para capturar el tráfico, usa los elementos de Istio a fin de crear una definición especial del servicio. Debes definir los siguientes elementos:

  • Puerta de enlace de salida
  • Entrada de servicio
  • Servicio virtual
  • Certificados TLS (como un secreto)
  • Reglas de destino
  1. En Cloud Shell, obtén la dirección IP de la puerta de enlace de entrada del servidor mediante una consulta a la dirección IP del balanceador de cargas del servicio istio-ingressgateway mediante el contexto del servidor ($SERVER_CLUSTER, que creó antes):

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

    Debido a que INGRESS_HOST es solo la parte de la dirección IP del host, debes crear un nombre de dominio completamente calificado (FQDN). Este paso es necesario porque, para funcionar de forma correcta, los certificados requieren un nombre de dominio.

    En este instructivo, se usa el servicio de DNS comodín nip.io a fin de crear un FQDN para la dirección IP de entrada. Este servicio te permite crear el FQDN sin tener un dominio.

  2. Almacene la URL del servicio FQDN en una variable de entorno:

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

    Ahora, con el SERVICE_URL definido como FQDN, puedes comenzar a definir la parte de Istio del clúster del cliente.

Crea la puerta de enlace de salida

Primero, crea la puerta de enlace de salida para escuchar el tráfico destinado al servicio externo.

Puerta de enlace de salida que escucha el tráfico destinado al servicio externo.

  1. En Cloud Shell, crea el siguiente archivo YAML y asígnale el nombre 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. Aplica el archivo YAML anterior al clúster del cliente:

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

    Presta atención a los puertos. Usaste los puertos default aquí para el interruptor de los servidores de salida, que es 15443. Si deseas usar un puerto diferente, debes editar el objeto service de la puerta de enlace de salida para agregar los puertos personalizados.

    El interruptor hosts define el extremo, que es adonde debe dirigirse el tráfico.

Define la entrada de servicio

El siguiente paso es informar a la malla de servicios sobre el servicio externo. Istio tiene su propio registro, en el que se almacenan extremos de servicio para la malla. Si Istio se instala en Kubernetes, los servicios definidos en el clúster se agregan de forma automática al registro de Istio. Con la definición de la entrada de servicio, agregas un extremo nuevo al registro de Istio, como se muestra en el siguiente diagrama.

Agrega un extremo al registro de Istio mediante una definición de entrada de servicio

  1. En Cloud Shell, crea el siguiente archivo YAML y asígnale el nombre 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. Aplica el archivo YAML anterior al clúster del cliente:

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

    La definición del servicio del cliente en este archivo YAML le indica al servicio qué tipo de tráfico esperar (MySQL L4: capa cuatro de red, mediante el puerto 3306). También debes definir que la comunicación será “malla externa”. En la sección de extremos, debes definir que el flujo debe ir hacia la dirección FQDN $SERVICE_URL que configuraste antes y que se asigna a la puerta de enlace de entrada en el clúster del servidor (kOps).

Define el servicio virtual

Un servicio virtual es un conjunto de reglas de enrutamiento de tráfico que se aplican cuando se dirige un host. Cada regla de enrutamiento define los criterios de coincidencia para el tráfico de un protocolo específico. Si se detecta una coincidencia con el tráfico, se envía a un servicio de destino con nombre (o a un subconjunto o una versión) que se define en el registro. Para obtener más información, consulta la documentación de Istio.

Define un servicio virtual que le indique a Istio cómo aplicar enrutamiento para el tráfico que llega al servicio externo

La definición del servicio virtual le indica a Istio cómo aplicar el enrutamiento del tráfico que llega al servicio externo. Con la siguiente definición, le indicas a la malla que enrute el tráfico del cliente a la puerta de enlace de salida en el puerto 15443. Desde la puerta de enlace de salida, enruta el tráfico al host $SERVICE_URL en el puerto 13306 (donde la puerta de enlace de entrada del servidor escucha).

  1. Crea el siguiente archivo YAML y asígnale el nombre 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. Aplica la definición YAML al clúster cliente:

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

    Para especificar a qué puertas de enlace se debe aplicar la configuración, edita el interruptor gateways en el archivo YAML.

    La parte importante de esta definición es el uso de la palabra reservada mesh, que implica todos los archivos adicionales en la malla. Según la documentación de Istio, cuando se omite este campo, se usa la puerta de enlace predeterminada (malla) y se aplica la regla a todos los archivos adicionales en la malla. Si proporcionas una lista de nombres de puertas de enlace, las reglas se aplican solo a las puertas de enlace. Para aplicar las reglas a las puertas de enlace y los archivos adicionales, especifica mesh como uno de los nombres de la puerta de enlace.

En la siguiente sección, se define cómo controlar el tráfico que sale del proxy de producción del cliente, match.gateways.mesh. También puedes definir cómo enrutar el tráfico desde la salida al servicio externo mediante el interruptor match.gateways.istio-egressgateway-mysql.

Define una regla de destino (del cliente a la puerta de enlace de salida)

Ahora que definiste cómo enrutar el tráfico al servicio externo, debes definir qué políticas de tráfico se deben aplicar. El servicio virtual que acabas de definir controla dos casos de enrutamiento a la vez. Uno controla el tráfico desde el proxy de sidecar hasta la puerta de enlace de salida y el otro controla el tráfico desde la puerta de enlace de salida al servicio externo.

Para hacer coincidir estos casos con las reglas de destino, necesitas dos reglas por separado. En el siguiente diagrama, se muestra la primera regla, que controla el tráfico del proxy a la puerta de enlace de salida. En esta definición, le indicas a Anthos Service Mesh que use sus certificados predeterminados para la comunicación con mTLS.

Regla de destino que define cómo manejar el tráfico desde el proxy de sidecar a la puerta de enlace de salida.

  1. En Cloud Shell, crea el siguiente archivo YAML y asígnale el nombre 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. Aplica la definición YAML anterior al clúster del cliente:

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

    En el archivo YAML anterior, usaste el interruptor de hosts para definir cómo enrutar el tráfico desde proxy de cliente hasta la puerta de enlace de salida. Además, configuraste la malla para que use ISTIO_MUTUAL en el puerto 15443, que es uno de los puertos que se abren automáticamente en la puerta de enlace de salida.

Crea una regla de destino (de la puerta de enlace de salida a un servicio externo)

En el siguiente diagrama, se muestra la segunda regla de destino, que indica a la malla cómo controlar el tráfico desde la puerta de enlace de salida hasta el servicio externo.

Segunda regla de destino que define cómo manejar el tráfico desde la puerta de enlace de salida hasta el servicio externo.

Debes indicarle a la malla que use tus certificados incorporados para la comunicación de TLS mutua con el servicio externo.

  1. En Cloud Shell, desde el directorio anthos-service-mesh-samples/docs/mtls-egress-ingress, crea los certificados:

     ./create-keys.sh
    

    Asegúrate de proporcionar una contraseña cuando la secuencia de comandos la solicite.

  2. Copia los archivos generados en el directorio actual:

    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. Crea un secreto de Kubernetes que almacene los certificados para que se pueda hacer referencia a ellos más adelante en la puerta de enlace:

     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
    

    El comando anterior agrega los siguientes archivos de certificado al secreto:

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

    La práctica recomendada actual para distribuir certificados se sigue aquí mediante el servicio de descubrimiento secreto (SDS) en lugar de activaciones de archivos. Esta práctica evita reiniciar los Pods cuando se agrega un certificado nuevo. A partir de Istio 1.8/1.9, con esta técnica ya no necesitas derechos de acceso de lectura (RBAC) para el secreto de las puertas de enlace.

  4. Agrega los certificados a DestinationRule y asígnale el nombre 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. Aplica la definición YAML anterior al clúster del cliente:

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

    Esta regla solo funciona si creaste el secreto antes. El secreto garantiza que los certificados se usen para la encriptación mTLS de la puerta de enlace de salida al extremo externo.

Cuando hayas completado estos pasos, la configuración del cliente habrá terminado y se verá como el siguiente diagrama.

Configuración final del cliente.

Configura el servidor

Como se explicó en la guía de conceptos, del servidor debes configurar la puerta de enlace de entrada en Anthos Service Mesh. Antes de crear los archivos necesarios, es una buena idea revisar qué componentes son necesarios para realizar la parte del servidor de la solución.

En resumen, quieres identificar el tráfico entrante según su origen y certificado. También quieres enrutar de forma específica solo ese tráfico a su destino: tu base de datos MySQL en un contenedor. Por lo general, para hacerlo, usas un servicio en Kubernetes, pero en este ejemplo, debes identificar el tráfico entrante dentro de la comunicación de malla, por lo que necesitas una definición especial de un servicio que incluya lo siguiente:

  • Certificados TLS (como un secreto)
  • Puerta de enlace de entrada
  • Servicio virtual

Crea el secreto para la puerta de enlace de entrada

Como lo hiciste con la puerta de enlace de salida, necesitas los mismos certificados para proteger la comunicación de la puerta de enlace de entrada. Para lograr esto, almacena los certificados en un secreto y defínelo con tu objeto de puerta de enlace de entrada.

  1. En Cloud Shell, copia los archivos generados en la ubicación desde la que ejecutas el siguiente 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. Crea el secreto del 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
    

    Acaba de agregar los siguientes archivos de certificado al secreto:

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

Define la puerta de enlace de entrada

Para recibir tráfico del clúster del lado del cliente, debes especificar una puerta de enlace de entrada que pueda desencriptar y verificar la comunicación TLS mediante los certificados.

Define una puerta de enlace de entrada que inspeccione el tráfico para ver si se ajusta a los criterios de seguridad y reenvío

Este diagrama muestra dónde se encuentra la puerta de enlace de entrada en su clúster. El tráfico pasa a través y se inspecciona si se ajusta a los criterios de seguridad y reenvío.

  1. En Cloud Shell, usa el siguiente archivo YAML y asígnale el nombre 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. Aplica la definición YAML anterior al clúster del cliente:

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

    Presta atención a la sección tls: porque este es muy importante. En esta sección, definirás que deseas mTLS. Para garantizar que esto funcione como se espera, debes proporcionar el secreto que creaste que contiene los certificados.

  3. Para habilitar el puerto 13306, aplica un parche al servicio de entrada. Para habilitar este puerto, crea el siguiente archivo JSON y asígnale el nombre 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. Aplica el parche al servicio de puerta de enlace:

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

Ahora que abriste el puerto en la puerta de enlace de entrada, debes recoger el tráfico proveniente de tu nueva puerta de enlace de entrada y dirigirlo al Pod de base de datos. Para ello, usa un objeto interno de malla: el servicio virtual.

Define el servicio virtual

Como se explicó antes, el servicio virtual es una definición de patrones de coincidencia de tráfico que definen el tráfico dentro de tu malla. Debes identificar y reenviar de forma correcta el tráfico de tu puerta de enlace de entrada a tu servicio de base de datos MySQL, como se muestra en el siguiente diagrama.

Identifica y reenvía tráfico desde la puerta de enlace de entrada al servicio de base de datos MySQL.

  1. En Cloud Shell, crea el siguiente archivo YAML y asígnale el nombre 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
    

    Es importante que este servicio virtual haga referencia a la puerta de enlace de entrada de la que proviene el tráfico. La puerta de enlace se llama gateway-mysql.

    Debido a que este servicio virtual se aplica en L4, debes proporcionar una marca tcp para describir el tráfico de MySQL. Esta marca, básicamente, le indica a la malla que se usa L4 en este tráfico.

    Quizá hayas notado que el servicio de entrada usa el puerto 13306 para reenviar el tráfico. El servicio virtual lo capta y lo convierte en 3306.

    Por último, reenvía el tráfico al servidor de MySQL en el clúster de Kubernetes del servidor. En este ejemplo, el servidor escucha en el puerto estándar de MySQL 3306.

  2. Aplica la definición YAML al clúster del servidor:

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

Estas dos definiciones desencriptan la solicitud del cliente MySQL encapsulada en mTLS y la reenvían a la base de datos MySQL dentro de la malla.

Es importante comprender que el reenvío interno de malla también se realiza mediante la encriptación, pero en este caso la encriptación se basa en certificados internos de malla. La finalización de mTLS se produce en la puerta de enlace.

Ahora tienes una manera mutua y completamente encriptada de comunicarte con tu servidor MySQL. Esta forma de encriptación es transparente para el cliente y el servidor MySQL, por lo que no es necesario cambiar la aplicación. Esto facilita las tareas como cambiar o rotar certificados. Además, esta forma de comunicación puede usarse para muchas situaciones diferentes.

Prueba la configuración

Ahora que tienes los clientes y el servidor implementados, puedes probar la configuración.

Prueba el controlador de tráfico del cliente al servidor.

Llegó el momento de generar tráfico del cliente al servidor. Deseas seguir el flujo de tráfico y asegurarte de que el tráfico se enrute, encripte y desencripte como desees.

  1. En Cloud Shell, implementa el servidor MySQL en el clúster del servidor:

    kubectl --context ${SERVER_CLUSTER} apply -f server/mysql-server/mysql.yaml
    
  2. Inicia un cliente MySQL en el clúster del cliente:

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

    Cuando se inicia el contenedor, se presenta una shell, que debería verse de la siguiente manera:

    root@mysql-client-1:/#
    
  3. Conéctate a tu servidor MySQL:

    mysql -pyougottoknowme -h $SERVICE_URL
    
  4. Usa los siguientes comandos para agregar una base de datos y una tabla:

    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. Después de conectar y agregar la tabla, sal de la conexión de MySQL y del Pod:

    exit
    exit
    

    Debes escribir exit dos veces: primero, a fin de salir de la conexión de la base de datos y, luego, para salir del Pod. Si el Pod deja de responder tras la salida, presiona Control+C para cancelar fuera de la shell Bash.

Si sigues estos pasos, deberías haber generado un resultado de registro significativo que podrás analizar con más detalle.

En la siguiente sección, verificarás que el tráfico del cliente pase el proxy y la puerta de enlace de salida. También puedes probar si puedes ver el tráfico que ingresa al lado del servidor a través de la puerta de enlace de entrada.

Prueba el proxy del cliente y la puerta de enlace de salida

  1. En Cloud Shell, en el proxy del lado del cliente, verifica que puedas ver los registros del proxy de Istio:

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

    El resultado de depuración es similar al siguiente:

    [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 - -
    

    Presiona Control+C para salir del resultado del registro.

    En esta entrada de registro, puedes ver que el cliente solicita el servidor que se ejecuta en la dirección IP 34.66.165.46 en el puerto 3306. La solicitud se reenvía (outbound) a istio-egressgateway que escucha en el puerto 15443 de la dirección IP 192.168.1.4. Define este reenvío en tu servicio virtual (client-virtual-service.yaml).

  2. Lee los registros del proxy de la puerta de enlace de salida:

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

    El resultado de depuración es similar al siguiente:

    [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 -
    

    Presiona Control+C para salir del resultado del registro.

    En esta entrada de registro, puedes ver que la solicitud del cliente enrutada a istio-egressgateway mediante la escucha en la dirección IP 192.168.1.4 en el puerto 15443 se enruta aún más afuera de la malla de servicios al servicio externo que escucha en la dirección IP 34.66.165.46 en el puerto 13306.. Debes definir este reenvío en la segunda parte de tu servicio virtual (client-virtual-service.yaml).

Prueba la puerta de enlace de entrada del servidor

  1. En Cloud Shell, del servidor, visualiza los registros del proxy de la puerta de enlace de entrada:

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

    El resultado en el registro es similar al siguiente:

    [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 -
    

    Presiona Control+C para salir del resultado del registro.

    En esta entrada de registro, puedes ver que la solicitud del cliente externo enrutada a istio-ingressgateway que escucha en el puerto 34.66.165.46 de la dirección IP 13306 que se enruta aún más al servicio de MySQL dentro de la malla identificada por el nombre del servicio mysql.default.svc.cluster.local en el puerto 3306.. Debes definir este reenvío en la puerta de enlace de entrada (server-ingress-gateway.yaml).

  2. Para el servidor MySQL, consulta los registros del proxy de Istio:

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

    El resultado es similar al siguiente:

    [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 -
    

    Presiona Control+C para salir del resultado del registro.

    En esta entrada de registro, puedes ver la llamada entrante al servidor de base de datos MySQL que escucha en la dirección IP 100.96.4.8 en el puerto 3306. La llamada proviene del pod de entrada con la dirección IP 100.96.1.3.

    Para obtener más información sobre el formato de registro de Envoy, consulta Obtén los registros de acceso de Envoy.

  3. Prueba tu base de datos para ver la entrada generada:

    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. Usa este comando para verificar la base de datos creada:

    mysql -pyougottoknowme
    USE test_encrypted_connection;
    SELECT * from message;
    

    El resultado es similar al siguiente:

    +------+-----------+
    | id   | content   |
    +------+-----------+
    |    1 | Crypto Hi |
    +------+-----------+
    1 row in set (0.00 sec)
    
  5. Sal de la base de datos MySQL:

    exit
    exit
    

    Debes escribir exit dos veces: primero, a fin de salir de la conexión de la base de datos y, luego, para salir del Pod.

Omite los certificados para probar el acceso

Ahora que probaste y verificaste que el acceso funciona mediante los certificados inyectados, prueba lo contrario: qué sucede si omites la puerta de enlace de salida y la inyección del certificado. Esta prueba también se denomina prueba negativa.

Puedes realizar esta prueba si inicias otro Pod en un espacio de nombres sin la inyección del proxy habilitada.

  1. En Cloud Shell, crea un espacio de nombres nuevo:

    kubectl --context ${CLIENT_CLUSTER} create ns unencrypted
    
  2. Crea un Pod y, luego, inicia una shell interactiva dentro del contenedor:

    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. Intenta conectarte a la base de datos una vez que se haya iniciado la shell interactiva:

    mysql -pyougottoknowme -h $SERVICE_URL
    

    Después de 30 segundos, verás un resultado similar al siguiente:

    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)
    

    Se espera esta advertencia porque este Pod omite la puerta de enlace de salida y trata de llegar a la puerta de enlace de entrada ( $SERVICE_URL) directamente a través de Internet.

  4. Intenta resolver la dirección IP del servicio:

    resolveip $SERVICE_URL
    

    El resultado es similar al siguiente. Tu dirección IP será diferente.

    IP address of 104.154.164.12.nip.io is 104.154.164.12
    

    Esto demuestra que el FQDN se puede resolver y que la conexión con errores se debe a la inserción de certificados faltantes.

  5. Sal de la conexión de MySQL y del Pod del servidor MySQL:

    exit
    exit
    

Nuevas investigaciones

Un tema que no se explica en este instructivo es que, por lo general, los parámetros de configuración de salida son propiedad de una organización o función diferente en tu empresa, porque están alojadas en el espacio de nombres istio-system. Configura los permisos de RBAC de Kubernetes para que solo los administradores de red puedan crear y modificar directamente los recursos mencionados en este instructivo.

Ahora que sabes cómo usar una malla de servicios para ayudar a garantizar una comunicación segura, te recomendamos probarlo con una aplicación que deba intercambiar datos de forma segura y en la que quieres controlar la encriptación hasta la capa del certificado. Para comenzar, puedes instalar Anthos Service Mesh.

Prueba usar dos clústeres de GKE y combínalos con la técnica de este instructivo. Esta técnica también funciona en la plataforma Anthos entre dos clústeres de Kubernetes externos.

Las mallas de servicios son una excelente manera de aumentar la seguridad en tu clúster y los servicios externos. Un último caso de uso a tratar es tener un extremo mTLS que no sea un segundo clúster de Kubernetes, sino un proveedor externo (como un proveedor de pagos).

Limpia

Puedes borrar tu proyecto para evitar que se apliquen cargos a tu cuenta de Google Cloud por los recursos usados en este instructivo.

Borra el proyecto

  1. En Cloud Console, ve a la página Administrar recursos.

    Ir a Administrar recursos

  2. En la lista de proyectos, elige el proyecto que quieres borrar y haz clic en Borrar.
  3. En el diálogo, escribe el ID del proyecto y, luego, haz clic en Cerrar para borrar el proyecto.

¿Qué sigue?