Sécuriser et chiffrer la communication entre les clusters Anthos à l'aide d'Anthos Service Mesh

Ce tutoriel explique comment utiliser les passerelles d'entrée et de sortie Anthos Service Mesh pour sécuriser le trafic entre les clusters à l'aide de l'authentification TLS mutuelle (mTLS). Il est destiné aux administrateurs de cluster Kubernetes chargés des aspects réseau, sécurité et plate-forme. Les contrôles décrits ici peuvent être particulièrement utiles pour les organisations ayant des exigences de sécurité accrues ou pour satisfaire des prérequis réglementaires. Ce tutoriel est accompagné d'un guide conceptuel complémentaire.

Dans ce tutoriel, nous partons du principe que vous connaissez bien Kubernetes et Anthos Service Mesh.

Objectifs

  • Utilisez Terraform pour configurer l'infrastructure :
    • Créez un réseau VPC personnalisé avec deux sous-réseaux privés.
    • Créez deux clusters Kubernetes avec Anthos Service Mesh activé :
    • Enregistrer des clusters dans GKE Hub.
  • Déployez un client MySQL sur un cluster GKE.
  • Déployez un serveur MySQL sur un cluster kOps.
  • Configurez les passerelles de sortie et d'entrée pour exposer un serveur à l'aide de mTLS.
  • Testez l'accès à un serveur MySQL à l'aide d'un client MySQL qui s'exécute dans différents clusters ou VPC.

Coûts

Ce tutoriel utilise les composants facturables suivants de Google Cloud :

Obtenez une estimation des coûts en fonction de votre utilisation prévue à l'aide du simulateur de coût. Les nouveaux utilisateurs de Google Cloud peuvent bénéficier d'un essai gratuit.

Une fois que vous avez terminé ce tutoriel, vous pouvez éviter de continuer à payer des frais en supprimant les ressources que vous avez créées. Consultez la page Effectuer un nettoyage pour en savoir plus.

Avant de commencer

Pour ce tutoriel, vous avez besoin d'un projet Google Cloud. Vous pouvez en créer un ou sélectionner un projet existant :

  1. Dans Google Cloud Console, accédez à la page de sélection du projet.

    Accéder au sélecteur de projet

  2. Sélectionnez ou créez un projet Google Cloud.

  3. Assurez-vous que la facturation est activée pour votre projet Cloud. Découvrez comment vérifier que la facturation est activée pour votre projet.

  4. Dans Cloud Console, accédez à Cloud Shell.

    Accéder à Cloud Shell

    En bas de la fenêtre de Cloud Console, une session Cloud Shell s'ouvre et affiche une invite de ligne de commande. Cloud Shell est un environnement shell dans lequel le SDK Cloud est déjà installé, y compris l'outil de ligne de commande gcloud. L'initialisation de la session peut prendre quelques secondes.

  5. Dans Cloud Shell, assurez-vous que vous travaillez dans le projet que vous avez créé ou sélectionné :
    export PROJECT_ID=PROJECT_ID
    gcloud config set project ${PROJECT_ID}
    

    Remplacez PROJECT_ID par l'ID du projet.

  6. Créez une variable d'environnement pour l'adresse e-mail que vous utilisez pour Google Cloud :
    export GOOGLE_CLOUD_EMAIL_ADDRESS=GOOGLE_CLOUD_EMAIL_ADDRESS
    

    Remplacez GOOGLE_CLOUD_EMAIL_ADDRESS par l'adresse e-mail que vous utilisez dans Google Cloud.

  7. Définissez la région et la zone pour vos ressources de calcul :
    export REGION=us-central1
    export ZONE=us-central1-b
    gcloud config set compute/region ${REGION}
    gcloud config set compute/zone ${ZONE}
    

    Ce tutoriel utilise us-central1 pour la région et us-central1-b pour la zone. Vous pouvez effectuer le déploiement dans la région de votre choix.

  8. Définissez les rôles IAM (gestion de l'authentification et des accès). Si vous êtes un propriétaire de projet, vous disposez de toutes les autorisations nécessaires pour terminer l'installation. Sinon, demandez à votre administrateur de vous attribuer des rôles IAM (Identity and Access Management) en exécutant la commande suivante dans 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. Activez les API requises pour le tutoriel :
    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
    

Préparer l'environnement

  1. Dans Cloud Shell, clonez le dépôt suivant :

    git clone https://github.com/GoogleCloudPlatform/anthos-service-mesh-samples
    cd anthos-service-mesh-samples/docs/mtls-egress-ingress
    
  2. Mettez à jour Terraform pour votre environnement. Par défaut, Cloud Console est fourni avec Terraform 0.12. Dans ce tutoriel, nous partons du principe que vous disposez de Terraform 0.13.5 ou d'une version ultérieure. Vous pouvez utiliser temporairement une autre version de Terraform en exécutant les commandes suivantes :

    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. Accédez au sous-dossier terraform et initialisez Terraform :

    cd terraform/
    ~/bin/terraform init
    
  4. Créez un fichier terraform.tfvars (en fonction des variables d'environnement que vous avez créées précédemment) :

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

    À l'étape suivante, vous allez créer l'infrastructure initiale. Pour ce faire, vous devez créer et appliquer le plan d'exécution Terraform pour cette configuration. Les scripts et les modules de ce plan créent les éléments suivants :

    • Un réseau VPC personnalisé avec deux sous-réseaux privés.
    • Deux clusters Kubernetes avec Anthos Service Mesh activé
    • Un cluster GKE
    • Un cluster kOps s'exécutant dans le réseau VPC personnalisé

    Le plan d'exécution enregistre également les clusters dans GKE Hub.

  5. Exécutez le plan d'exécution :

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

    Le résultat ressemble à ce qui suit :

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

    La partie <sensitive> est une variable de sortie Terraform qui n'est pas affichée sur la console mais qui peut être interrogée, par exemple ~/bin/terraform output server_token.

  6. Récupérez le fichier kubeconfig du cluster de serveur à partir du répertoire terraform. Ensuite, fusionnez-le avec le fichier de configuration 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}
    

    Le fichier client-server-kubeconfig contient désormais la configuration des deux clusters, que vous pouvez vérifier en exécutant la commande suivante :

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

    Le résultat est le suivant :

    gke_PROJECT_ID_us-central1-c_client-cluster
    server-cluster.k8s.local
    
  7. Obtenez le contexte des deux clusters pour une utilisation ultérieure :

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

    Le résultat est (à nouveau) le suivant :

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

    Vous pouvez maintenant utiliser ces noms de cluster comme contexte pour d'autres commandes kubectl.

  8. Référencez le cluster client :

    kubectl --context ${CLIENT_CLUSTER} get pods -n istio-system
    
  9. Référencez le cluster serveur :

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

Configurer le côté client

Comme indiqué dans le guide conceptuel, la partie client nécessite la configuration de la passerelle de sortie dans Anthos Service Mesh.

Dans cette section, vous configurez les éléments de Anthos Service Mesh afin d'identifier le trafic externe en fonction de son origine et d'utiliser un certificat personnalisé pour chiffrer la communication. En outre, vous ne souhaitez acheminer que ce trafic vers sa destination (la base de données MySQL dans un conteneur). Pour ce faire, vous utilisez généralement un service dans Kubernetes. Dans ce cas, vous devez intercepter ce trafic dans les communication du maillage. Pour intercepter le trafic, vous devez utiliser des éléments Istio afin de créer une définition spéciale du service. Vous définissez les éléments suivants :

  • Passerelle de sortie
  • Entrée de service
  • Service virtuel
  • Certificats TLS (dans un secret)
  • Règles de destination
  1. Dans Cloud Shell, récupérez l'adresse IP de la passerelle d'entrée côté serveur en interrogeant l'adresse IP de l'équilibreur de charge de la istio-ingressgateway en utilisant le contexte côté serveur ($SERVER_CLUSTER, que vous avez créé précédemment) :

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

    Comme INGRESS_HOST n'est que la partie d'adresse IP de votre hôte, vous devez créer un nom de domaine complet. Cette étape est nécessaire car pour fonctionner correctement, les certificats requièrent un nom de domaine.

    Dans ce tutoriel, vous utilisez le service DNS générique nip.io pour créer un nom de domaine complet pour l'adresse IP d'entrée. Ce service vous permet de créer le nom de domaine complet sans posséder de domaine.

  2. Stockez l'URL de service du nom de domaine complet dans une variable d'environnement :

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

    Maintenant que le SERVICE_URL est défini comme nom de domaine complet, vous pouvez commencer à définir la partie Istio du cluster client.

Créer la passerelle de sortie

Vous allez commencer par créer la passerelle de sortie afin d'écouter le trafic à destination du service externe.

Passerelle de sortie écoutant le trafic à destination du service externe.

  1. Dans Cloud Shell, créez le fichier YAML suivant et nommez-le 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. Appliquez le fichier YAML précédent au cluster client :

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

    Portez une attention particulière aux ports. Vous avez ici utilisé les ports default pour le commutateur de serveurs de sortie, à savoir le port 15443. Si vous souhaitez utiliser un port différent, vous devez modifier l'objet service de la passerelle de sortie pour ajouter vos ports personnalisés.

    Le commutateur hosts définit le point de terminaison, c'est-à-dire l'emplacement vers lequel le trafic doit être dirigé.

Définir l'entrée de service

L'étape suivante consiste à informer le maillage de services du service externe. Istio dispose de son propre registre dans lequel il stocke les points de terminaison de service pour le maillage. Si Istio est installé sur Kubernetes, les services définis dans le cluster sont automatiquement ajoutés au registre Istio. Avec la définition de l'entrée de service, vous ajoutez un nouveau point de terminaison au registre Istio comme indiqué dans le schéma suivant.

Ajout d&#39;un point de terminaison au registre Istio à l&#39;aide d&#39;une définition d&#39;entrée de service.

  1. Dans Cloud Shell, créez le fichier YAML suivant et nommez-le 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. Appliquez le fichier YAML précédent au cluster client :

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

    La définition de service client dans ce fichier YAML indique au service le type de trafic à attendre (MySQL L4, couche réseau 4, sur le port 3306). Vous définissez également que la communication sera externe au maillage ("mesh external"). Dans la section des points de terminaison, vous définissez que le flux doit être dirigé vers l'adresse du nom de domaine complet $SERVICE_URL que vous avez définie précédemment et qui est mappée sur la passerelle d'entrée du cluster de serveur (kOps).

Définir le service virtuel

Un service virtuel est un ensemble de règles de routage qui s'appliquent au trafic à destination d'un hôte spécifique. Chaque règle de routage définit des critères de correspondance pour le trafic d'un protocole spécifique. Si le trafic est mis en correspondance, il est envoyé à un service de destination nommé (ou à un sous-ensemble ou à une version de celui-ci) qui est défini dans le registre. Pour plus d'informations, consultez la documentation d'Istio.

Définition d&#39;un service virtuel indiquant à Istio comment appliquer le routage pour le trafic qui atteint le service externe.

La définition de service virtuel indique à Istio comment appliquer le routage pour le trafic qui atteint le service externe. Avec la définition suivante, vous demandez au maillage de diriger le trafic du client vers la passerelle de sortie sur le port 15443. Depuis la passerelle de sortie, vous routez le trafic vers l'hôte $SERVICE_URL sur le port 13306 (qui est écouté par la passerelle d'entrée côté serveur).

  1. Créez le fichier YAML suivant et nommez-le 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. Appliquez la définition YAML au cluster client :

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

    Vous pouvez indiquer les passerelles auxquelles la configuration doit s'appliquer en modifiant le commutateur gateways dans le fichier YAML.

    La partie importante de cette définition est l'utilisation du mot réservé mesh, qui implique tous les side-cars du maillage. Selon la documentation d'Istio, lorsque ce champ est omis, la passerelle par défaut (maillage) est utilisée et applique la règle à tous les side-cars du maillage. Si vous fournissez une liste de noms de passerelles, les règles ne s'appliquent qu'aux passerelles. Pour appliquer les règles aux passerelles et aux side-cars, spécifiez mesh comme nom de passerelle.

Dans la section suivante, vous allez définir comment gérer le trafic sortant du proxy client de production (match.gateways.mesh). Vous définissez également comment acheminer le trafic de la sortie vers le service externe à l'aide du commutateur match.gateways.istio-egressgateway-mysql.

Définir une règle de destination (du client à la passerelle de sortie)

Maintenant que vous avez défini comment acheminer le trafic vers le service externe, vous devez définir quelles règles de trafic s'appliquent. Le service virtuel que vous venez de définir gère deux cas de routage à la fois. L'un gère le trafic du proxy side-car vers la passerelle de sortie et l'autre gère le trafic de la passerelle de sortie vers le service externe.

Pour faire correspondre ces cas avec les règles de destination, vous devez utiliser deux règles distinctes. Le schéma suivant illustre la première règle, qui gère le trafic du proxy vers la passerelle de sortie. Dans cette définition, vous indiquez à Anthos Service Mesh d'utiliser ses certificats par défaut pour la communication mTLS.

Règle de destination qui définit comment gérer le trafic du proxy side-car vers la passerelle de sortie.

  1. Dans Cloud Shell, créez le fichier YAML suivant et nommez-le 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. Appliquez la définition YAML précédente au cluster client :

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

    Dans le fichier YAML précédent, vous avez utilisé le commutateur hosts pour définir comment acheminer le trafic du proxy client vers la passerelle de sortie. Vous avez également configuré le maillage pour utiliser ISTIO_MUTUAL sur le port 15443, qui est l'un des ports ouverts automatiquement sur la passerelle de sortie.

Créer une règle de destination (de la passerelle de sortie au service externe)

Le schéma suivant illustre la deuxième règle de destination qui indique au maillage comment gérer le trafic de la passerelle de sortie vers le service externe.

Deuxième règle de destination qui définit comment gérer le trafic de la passerelle de sortie vers le service externe.

Vous devez indiquer au maillage d'utiliser vos certificats injectés pour la communication TLS mutuelle avec le service externe.

  1. Dans Cloud Shell, créez les certificats à partir du répertoire anthos-service-mesh-samples/docs/mtls-egress-ingress :

     ./create-keys.sh
    

    Assurez-vous de fournir un mot de passe lorsque le script l'exige.

  2. Copiez les fichiers générés dans le répertoire actuel :

    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. Créez un secret Kubernetes qui stocke les certificats afin de pouvoir les référencer ultérieurement dans la passerelle :

     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
    

    La commande précédente ajoute les fichiers de certificat suivants au secret :

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

    Nous appliquons ici la bonne pratique recommandée pour la distribution des certificats, à savoir l'utilisation du service de détection de secrets (SDS) plutôt que les montages de fichiers. Cette pratique évite de redémarrer les pods lors de l'ajout d'un nouveau certificat. À partir de la version 1.8/1.9 d'Istio et avec cette technique, vous n'avez plus besoin de droits d'accès en lecture (RBAC) pour le secret des passerelles.

  4. Ajoutez les certificats à la DestinationRule et nommez-la 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. Appliquez la définition YAML précédente au cluster client :

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

    Cette règle ne fonctionne que si vous avez créé le secret au préalable. Le secret garantit que les certificats sont utilisés pour le chiffrement mTLS de la passerelle de sortie vers le point de terminaison externe.

Une fois ces étapes terminées, la configuration côté client est terminée et est reflétée par le schéma suivant.

Configuration finale côté client.

Configurer le côté serveur

Comme indiqué dans le guide de concept, vous devez configurer la passerelle d'entrée pour le côté serveur dans Anthos Service Mesh. Avant de créer les fichiers nécessaires, il est judicieux d'examiner les composants requis pour exécuter la partie serveur de la solution.

En résumé, vous souhaitez identifier le trafic entrant en fonction de son origine et de son certificat. Vous souhaitez également acheminer uniquement ce trafic vers sa destination : votre base de données MySQL dans un conteneur. Pour ce faire, vous utilisez généralement un service dans Kubernetes. Toutefois, dans cet exemple, vous allez identifier le trafic entrant dans les communications du maillage. Vous avez donc besoin d'une définition spéciale d'un service incluant les éléments suivants :

  • Certificats TLS (dans un secret)
  • Passerelle d'entrée
  • Service virtuel

Créer le secret de la passerelle d'entrée

Comme pour la passerelle de sortie, vous aurez besoin des mêmes certificats pour sécuriser les communications de la passerelle d'entrée. Pour ce faire, stockez les certificats dans un secret et définissez ce secret avec votre objet de passerelle d'entrée.

  1. Dans Cloud Shell, copiez les fichiers générés à l'emplacement où vous exécuterez la commande suivante :

    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. Créez le secret du serveur :

    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
    

    Vous venez d'ajouter les fichiers de certificat suivants au secret :

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

Définir la passerelle d'entrée

Pour recevoir le trafic du cluster côté client, vous devez spécifier une passerelle d'entrée capable de déchiffrer et de vérifier les communications TLS en utilisant les certificats.

Définition d&#39;une passerelle d&#39;entrée qui inspecte le trafic pour voir s&#39;il répond à des critères de sécurité et de transfert.

Ce schéma indique l'emplacement de la passerelle d'entrée dans votre cluster. Le trafic est transmis puis examiné s'il satisfait les critères de sécurité et de transfert.

  1. Dans Cloud Shell, utilisez le fichier YAML suivant et nommez-le 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. Appliquez la définition YAML précédente au cluster client :

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

    Portez une attention particulière à la section tls: car elle est particulièrement importante. Dans cette section, vous allez définir l'authentification mTLS. Pour que tout cela fonctionne comme prévu, vous devez fournir le secret que vous avez créé et qui contient les certificats.

  3. Activez le port 13306 en appliquant un correctif au service d'entrée. Pour activer ce port, créez le fichier JSON suivant et nommez-le 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. Appliquez le correctif au service de passerelle :

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

Maintenant que vous avez ouvert le port sur la passerelle d'entrée, vous devez intercepter le trafic provenant de votre nouvelle passerelle d'entrée et l'acheminer vers votre pod de base de données. Pour ce faire, vous allez utiliser un objet interne de maillage : le service virtuel.

Définir le service virtuel

Comme indiqué précédemment, le service virtuel est une définition de modèles de correspondance de trafic qui façonnent le trafic au sein de votre maillage. Vous devez identifier et transférer correctement le trafic de votre passerelle d'entrée vers votre service MySQL DB, comme illustré dans le schéma suivant.

Identification et transfert du trafic de la passerelle d&#39;entrée vers le service MySQL DB.

  1. Dans Cloud Shell, créez le fichier YAML suivant et nommez-le 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
    

    Il est important que ce service virtuel fasse référence à la passerelle d'entrée d'où provient le trafic. La passerelle est nommée gateway-mysql.

    Comme ce service virtuel est appliqué sur la L4, vous devez utiliser l'option tcp pour décrire votre trafic MySQL. Cette option permet d'indiquer au maillage que la L4 doit être utilisée pour ce trafic.

    Vous avez peut-être remarqué que le service d'entrée utilise le port 13306 pour transférer le trafic. Le service virtuel récupère cette valeur et la traduit en 3306.

    Enfin, vous transférez le trafic vers le serveur MySQL dans le cluster Kubernetes de serveur. Pour cet exemple, le serveur écoute sur le port MySQL standard 3306.

  2. Appliquez la définition YAML au cluster de serveur :

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

Ces deux définitions déchiffrent la requête client MySQL encapsulée par mTLS avant de la transférer vers la base de données MySQL à l'intérieur du maillage.

Il est important de comprendre que les transferts internes au maillage sont également chiffrés. Cependant, dans le cas présent, le chiffrement est basé sur les certificats internes du maillage. La résiliation mTLS se produit au niveau de la passerelle.

Vous disposez à présent d'une méthode de communication entièrement et mutuellement chiffrée pour communiquer avec votre serveur MySQL. Cette forme de chiffrement étant transparente pour le client et le serveur MySQL, aucune modification de votre application n'est nécessaire. Cela facilite les tâches comme la modification ou la rotation des certificats. De plus, ce mode de communication peut être utilisé dans de nombreux scénarios différents.

Tester la configuration

Maintenant que le côté client et le côté serveur sont tous deux correctement déployés, vous pouvez tester la configuration.

Test du flux de trafic depuis le côté client vers le côté serveur.

Il est temps de générer du trafic côté client à destination du côté serveur. Votre objectif est de suivre le flux de trafic pour vous assurer que le trafic est bien acheminé, chiffré et déchiffré comme vous le souhaitez.

  1. Dans Cloud Shell, déployez le serveur MySQL dans le cluster de serveur :

    kubectl --context ${SERVER_CLUSTER} apply -f server/mysql-server/mysql.yaml
    
  2. Démarrez un client MySQL sur le cluster client :

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

    Une fois le conteneur démarré, une interface système s'affiche. Elle doit ressembler à ceci :

    root@mysql-client-1:/#
    
  3. Connectez-vous à votre serveur MySQL :

    mysql -pyougottoknowme -h $SERVICE_URL
    
  4. Utilisez les commandes suivantes pour ajouter une base de données et une table :

    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. Après vous être connecté et avoir ajouté la table, fermez la connexion MySQL et le pod :

    exit
    exit
    

    Vous devez utiliser la commande "exit" deux fois : d'abord pour fermer la connexion de base de données, puis pour quitter le pod. Si le pod cesse de répondre après la fermeture de la connexion, appuyez sur Control+C pour quitter l'interface système bash.

En suivant ces étapes, vous devriez générer un résultat de journalisation pertinent que vous pourrez alors analyser plus en détail.

Dans la section suivante, vous allez vérifier que le trafic côté client passe bien par le proxy et par la passerelle de sortie. Vous pouvez également vérifier que vous voyez bien le trafic entrant côté serveur qui passe par la passerelle d'entrée.

Tester le proxy côté client et la passerelle de sortie

  1. Dans Cloud Shell, sur le proxy côté client, vérifiez que vous pouvez afficher les journaux du proxy Istio :

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

    La sortie de débogage doit ressembler à ceci :

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

    Appuyez sur Ctrl+C pour quitter le journal.

    Dans cette entrée de journal, vous pouvez voir que le client envoie une requête au serveur qui s'exécute sur l'adresse IP 34.66.165.46, sur le port 3306. La requête est transmise (outbound) à istio-egressgateway qui écoute sur le port 15443 de l'adresse IP 192.168.1.4. Vous avez défini ce transfert dans votre service virtuel (client-virtual-service.yaml).

  2. Lisez les journaux du proxy de la passerelle de sortie :

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

    La sortie de débogage doit ressembler à ceci :

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

    Appuyez sur Ctrl+C pour quitter le journal.

    Dans cette entrée de journal, vous pouvez voir que la requête client est acheminée vers istio-egressgateway qui écoute sur le port 192.168.1.4 de l'adresse IP 15443 avant d'être transférée vers l'extérieur du maillage de services vers le service externe qui écoute sur le port 13306. de l'adresse IP 34.66.165.46 (vous avez défini ce transfert dans la deuxième partie de votre service virtuel, client-virtual-service.yaml).

Tester la passerelle d'entrée côté serveur

  1. Dans Cloud Shell, côté serveur, affichez les journaux du proxy de passerelle d'entrée :

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

    La sortie dans le journal ressemble à ceci :

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

    Appuyez sur Ctrl+C pour quitter le journal.

    Dans cette entrée de journal, vous pouvez voir que la requête client externe est acheminée vers istio-ingressgateway qui écoute sur le port 34.66.165.46 de l'adresse IP 13306 avant d'être transférée vers le service MySQL à l'intérieur du maillage identifié par le nom de service mysql.default.svc.cluster.local sur le port 3306. (vous avez défini ce transfert dans la passerelle d'entrée server-ingress-gateway.yaml).

  2. Pour le serveur MySQL, affichez les journaux du proxy Istio :

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

    La sortie ressemble à ceci :

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

    Appuyez sur Ctrl+C pour quitter le journal.

    Dans cette entrée de journal, vous pouvez voir l'appel entrant vers le serveur de base de données MySQL qui écoute sur le port 3306 de l'adresse IP 100.96.4.8. L'appel provient du pod d'entrée associé à l'adresse IP 100.96.1.3.

    Pour plus d'informations sur le format de journalisation Envoy, consultez la section Obtenir les journaux d'accès Envoy.

  3. Testez votre base de données pour voir l'entrée générée :

    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. Vérifiez la base de données créée :

    mysql -pyougottoknowme
    USE test_encrypted_connection;
    SELECT * from message;
    

    Le résultat ressemble à ce qui suit :

    +------+-----------+
    | id   | content   |
    +------+-----------+
    |    1 | Crypto Hi |
    +------+-----------+
    1 row in set (0.00 sec)
    
  5. Quittez la base de données MySQL :

    exit
    exit
    

    Vous devez utiliser la commande exit deux fois : une première fois pour fermer la connexion de base de données et une seconde fois pour quitter le pod.

Tester l'accès en omettant les certificats

Maintenant que vous avez testé et vérifié que l'accès fonctionne correctement avec les certificats injectés, vous pouvez faire le test inverse afin de vérifier ce qui se passe si vous omettez la passerelle de sortie et l'injection de certificats ? Ce type de test est également appelé test négatif.

Vous pouvez effectuer ce test en démarrant un autre pod dans un espace de noms dans lequel l'injection de proxy secondaire est désactivée.

  1. Dans Cloud Shell, créez un espace de noms :

    kubectl --context ${CLIENT_CLUSTER} create ns unencrypted
    
  2. Créez un pod et démarrez une interface système interactive à l'intérieur du conteneur :

    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. Essayez de vous connecter à la base de données une fois que l'interface système interactive a démarré :

    mysql -pyougottoknowme -h $SERVICE_URL
    

    Au bout de 30 secondes, un résultat semblable à celui-ci s'affiche :

    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)
    

    Cet avertissement est attendu car le pod omet la passerelle de sortie et tente d'atteindre la passerelle d'entrée ($SERVICE_URL) directement via Internet.

  4. Essayez de résoudre l'adresse IP du service :

    resolveip $SERVICE_URL
    

    Le résultat renvoyé ressemble à ceci : Votre adresse IP sera différente.

    IP address of 104.154.164.12.nip.io is 104.154.164.12
    

    Cela prouve que le nom de domaine complet peut être résolu et que l'échec de la connexion est effectivement dû à l'absence d'injection de certificats.

  5. Fermez la connexion MySQL et quittez le pod du serveur MySQL :

    exit
    exit
    

Approfondir l'enquête

Un sujet non abordé dans ce tutoriel est le fait que les configurations de sortie appartiennent généralement à un autre rôle ou à une autre organisation au sein de votre entreprise, car elles sont hébergées dans l'espace de noms istio-system. Configurez les autorisations Kubernetes RBAC de sorte à ce que seuls les administrateurs réseau puissent créer et modifier directement les ressources décrites dans le présent tutoriel.

Maintenant que vous savez utiliser un maillage de services pour assurer une communication sécurisée, vous pouvez essayer de créer une application qui doit échanger des données de manière sécurisée et contrôler le chiffrement jusqu'à la couche de certificat. Pour commencer, vous pouvez installer Anthos Service Mesh.

Essayez d'utiliser deux clusters GKE et de les combiner à l'aide de la technique décrite dans le présent tutoriel. Cette technique fonctionne également sur la plate-forme Anthos entre deux clusters Kubernetes étrangers.

Les maillages de services sont un excellent moyen d'améliorer la sécurité au sein de votre cluster mais aussi lors des interactions avec des services externes. Un autre exercice consiste à avoir un point de terminaison mTLS qui n'est pas un deuxième cluster Kubernetes mais plutôt un fournisseur tiers (un fournisseur de paiement, par exemple).

Nettoyer

Pour éviter que les ressources utilisées lors de ce tutoriel ne soient facturées sur votre compte Google Cloud, vous pouvez supprimer le projet.

Supprimer le projet

  1. Dans Cloud Console, accédez à la page Gérer les ressources.

    Accéder à la page Gérer les ressources

  2. Dans la liste des projets, sélectionnez le projet que vous souhaitez supprimer, puis cliquez sur Supprimer.
  3. Dans la boîte de dialogue, saisissez l'ID du projet, puis cliquez sur Arrêter pour supprimer le projet.

Étape suivante