Autoriser l'accès aux services Cloud Run pour Anthos à l'aide d'Istio

Ce tutoriel explique comment autoriser l'accès aux services que vous déployez sur Cloud Run pour Anthos à l'aide d'Istio.

Cloud Run permet aux développeurs de déployer et de diffuser des applications et des fonctions. Cloud Run exécute les services dans un environnement entièrement géré ou dans un cluster Google Kubernetes Engine (GKE), appelé Cloud Run pour Anthos.

L'expérience des développeurs est la même dans les deux environnements, mais les capacités des plates-formes sous-jacentes diffèrent.

Cloud Run pour Anthos n'utilise pas IAM (Identity and Access Management) pour accorder des autorisations permettant d'appeler des services. À la place, vous vous servez d'Istio pour mettre en œuvre l'authentification et l'autorisation. Dans ce tutoriel, vous allez utiliser les règles d'authentification et d'autorisation Istio pour sécuriser un exemple de service Cloud Run pour Anthos.

Les règles d'authentification et d'autorisation spécifient les identités (comptes de service et utilisateurs IAM) qui peuvent appeler l'exemple de service.

Dans ce tutoriel, vous allez mettre en œuvre l'authentification et l'autorisation pour les clients exécutés en dehors du cluster GKE.

Objectifs

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

  1. Connectez-vous à votre compte Google Cloud. Si vous débutez sur Google Cloud, créez un compte pour évaluer les performances de nos produits en conditions réelles. Les nouveaux clients bénéficient également de 300 $ de crédits gratuits pour exécuter, tester et déployer des charges de travail.
  2. Dans Google Cloud Console, sur la page de sélection du projet, sélectionnez ou créez un projet Google Cloud.

    Accéder au sélecteur de projet

  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. Activez l'API Google Kubernetes Engine, l'API Cloud Run et les API Cloud.
    Activer les API
  5. Dans Cloud Console, accédez à Cloud Shell.
    Accéder à Cloud Shell
    Au bas 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), et dans lequel des valeurs sont déjà définies pour votre projet actuel. L'initialisation de la session peut prendre quelques secondes. Utilisez Cloud Shell pour exécuter toutes les commandes de ce tutoriel.

Configurer l'environnement

  1. Définissez les variables d'environnement et l'outil gcloud par défaut pour la zone Compute Engine que vous souhaitez utiliser pour ce tutoriel :

    ZONE=us-central1-f
    gcloud config set compute/zone $ZONE
    

    Vous pouvez modifier la zone.

  2. Créez un cluster GKE avec le module complémentaire Cloud Run :

    CLUSTER=cloud-run-gke-invoker-tutorial
    
    gcloud beta container clusters create $CLUSTER \
        --addons HorizontalPodAutoscaling,HttpLoadBalancing,CloudRun \
        --enable-ip-alias \
        --enable-stackdriver-kubernetes \
        --machine-type e2-standard-2 \
        --release-channel regular
    

    Ce tutoriel requiert la version de GKE 1.15.11-gke.9 et ultérieures, 1.16.8-gke.7 et ultérieures ou 1.17.4-gke.5 et ultérieures. Les nouveaux clusters GKE qui utilisent la version disponible regular respectent les contraintes de version.

Déployer un exemple de service

  1. Dans Cloud Shell, créez un espace de noms appelé tutorial dans le cluster GKE :

    kubectl create namespace tutorial
    

    Vous pouvez modifier le nom de l'espace de noms :

  2. Déployez un service appelé sample sur Cloud Run pour Anthos dans l'espace de noms tutorial :

    gcloud run deploy sample \
        --cluster $CLUSTER \
        --cluster-location $ZONE \
        --namespace tutorial \
        --image gcr.io/knative-samples/simple-api \
        --platform gke
    

    Cette commande crée un objet de service Knative Serving.

  3. Cloud Run pour Anthos expose des services sur l'adresse IP externe de la passerelle d'entrée Istio. Récupérez l'adresse IP externe et enregistrez-la dans une variable d'environnement appelée EXTERNAL_IP et dans un fichier appelé external-ip.txt :

    export EXTERNAL_IP=$(./get-external-ip.sh | tee external-ip.txt)
    
    echo $EXTERNAL_IP
    
    get_external_ip () {
        external_ip=$(kubectl -n gke-system get svc istio-ingress \
            -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    }
    get_external_ip
    while [ -z "$external_ip" ]; do
        sleep 2
        get_external_ip
    done
    echo "$external_ip"
  4. Assurez-vous que l'exemple de service renvoie un message HTTP/1.1 200 OK :

    curl -siH "Host: sample.tutorial.example.com" $EXTERNAL_IP | head -n1
    

Créer un compte de service

  1. Créez un compte de service IAM auquel vous accorderez l'accès à l'exemple de service plus tard dans ce tutoriel. Stockez l'adresse e-mail du compte de service dans une variable d'environnement :

    export SERVICE_ACCOUNT_EMAIL=$(gcloud iam service-accounts create \
        cloudrun-gke-invoker-tutorial \
        --display-name "Cloud Run for Anthos authorization tutorial service account" \
        --format "value(email)")
    

    Ce tutoriel utilise le nom de compte de service cloudrun-gke-invoker-tutorial. Ce nom est modifiable.

Configurer l'authentification et l'autorisation Istio

  1. Créez une règle d'authentification Istio :

    kubectl apply -f authenticationpolicy.yaml
    
    apiVersion: authentication.istio.io/v1alpha1
    kind: Policy
    metadata:
      name: istio-ingress-jwt
      namespace: gke-system
    spec:
      targets:
      - name: istio-ingress
        ports:
        - name: http2
        - name: https
      origins:
      - jwt:
          issuer: https://accounts.google.com
          jwksUri: https://www.googleapis.com/oauth2/v3/certs
      originIsOptional: true # use authorization policy to allow or deny request
      principalBinding: USE_ORIGIN

    Cette règle d'authentification valide les jetons Web JSON (JWT, JSON Web Tokens) dans l'en-tête Authorization des requêtes entrantes envoyées aux ports http2 et https de la passerelle d'entrée Istio. Pour respecter la règle, les jetons JWT doivent être des jetons d'identification OpenID Connect (OIDC) émis et signés par Google. Pour en savoir plus sur la validation des jetons d'identification, consultez la documentation sur Google Identity Platform.

    L'attribut originIsOptional: true signifie que la règle d'authentification accepte les requêtes, même si elles ne contiennent pas de jeton JWT répondant aux contraintes spécifiées. Cette règle vise à rendre les attributs des jetons JWT disponibles pour la règle d'autorisation que vous allez créer à l'étape suivante. La règle d'autorisation décide si vous souhaitez accepter ou refuser les requêtes.

  2. Créez une règle d'autorisation Istio appelée invoke-tutorial :

    envsubst < authorizationpolicy-invoker.tmpl.yaml | kubectl apply -f -
    

    Cette règle d'autorisation permet d'accéder à toutes les charges de travail dont les noms d'hôte correspondent à *.tutorial.example.com à l'aide de n'importe quelle méthode HTTP et de n'importe quel chemin d'URL, à toutes les requêtes qui incluent un jeton JWT émis et signé par Google (https://accounts.google.com) et au compte de service identifié par $SERVICE_ACCOUNT_EMAIL avec une revendication de cible (aud) de http://example.com.

    Si vous modifiez le domaine par défaut de Cloud Run pour Anthos, ajustez la valeur du champ hosts dans votre environnement de sorte qu'elle corresponde à votre domaine.

    apiVersion: security.istio.io/v1beta1
    kind: AuthorizationPolicy
    metadata:
      name: invoke-tutorial
      namespace: gke-system
    spec:
      action: ALLOW
      rules:
      - to:
        - operation:
            hosts:
            - '*.tutorial.example.com'
        when:
        - key: request.auth.claims[aud]
          values:
          - http://example.com
        - key: request.auth.claims[email]
          values:
          - $SERVICE_ACCOUNT_EMAIL
        - key: request.auth.claims[iss]
          values:
          - https://accounts.google.com
      selector:
       matchLabels:
         istio: ingress-gke-system
  3. L'application des règles d'authentification et d'autorisation peut prendre un certain temps. Exécutez la commande suivante et attendez que le message HTTP/1.1 403 Forbidden s'affiche dans la sortie :

    while sleep 2; do
        curl -siH "Host: sample.tutorial.example.com" $EXTERNAL_IP | head -n1
    done
    

    Au début, le résultat peut alterner entre HTTP/1.1 200 OK et HTTP/1.1 403 Forbidden, car les modifications des règles dans Envoy et Istio sont cohérentes à terme.

    Lorsque le message HTTP/1.1 403 Forbidden s'affiche à plusieurs reprises, appuyez sur Ctrl+C pour interrompre le délai d'attente.

Accéder au service

Les règles d'authentification et d'autorisation que vous avez créées nécessitent des jetons d'identification émis et signés par Google. Vous pouvez obtenir des jetons de différentes façons. La liste suivante répertorie quatre options par ordre de recommandation :

  1. Si vous accédez au service protégé depuis une instance Compute Engine, un cluster GKE ou un autre environnement dans lequel vous avez accès au serveur de métadonnées, utilisez le serveur de métadonnées Compute Engine.
  2. Utilisez une bibliothèque cliente d'authentification Google si celle-ci est disponible dans votre langage de programmation.
  3. Si vous ne pouvez pas utiliser la bibliothèque cliente d'authentification Google, utilisez directement l'API Service Account Credentials IAM.
  4. Si vous souhaitez tester votre service de façon interactive, y compris pendant la mise en œuvre, utilisez l'outil de ligne de commande gcloud.

Utiliser le serveur de métadonnées Compute Engine

  1. Dans Cloud Shell, créez une instance Compute Engine (VM) et associez-y le compte de service que vous avez créé précédemment :

    VM=cloudrun-gke-invoker-tutorial-vm
    
    gcloud compute instances create $VM \
        --scopes cloud-platform \
        --service-account $SERVICE_ACCOUNT_EMAIL
    

    Ce tutoriel utilise le nom d'instance cloudrun-gke-invoker-tutorial-vm. Ce nom est modifiable.

  2. Copiez le fichier contenant l'adresse IP publique de la passerelle d'entrée Istio dans la VM :

    gcloud compute scp external-ip.txt $VM:~
    
  3. Connectez-vous à la VM à l'aide de SSH :

    gcloud compute ssh $VM
    
  4. Une fois dans la session SSH, obtenez un jeton d'identification depuis le serveur de métadonnées Compute Engine :

    ID_TOKEN=$(curl -s -H Metadata-Flavor:Google \
        --data-urlencode format=full \
        --data-urlencode audience=http://example.com \
        http://metadata/computeMetadata/v1/instance/service-accounts/default/identity)
    
  5. Envoyez une requête à l'aide du jeton d'identification à l'exemple de service Cloud Run pour Anthos :

    curl -s -w"\n" -H "Host: sample.tutorial.example.com" \
        -H "Authorization: Bearer $ID_TOKEN" $(cat external-ip.txt)
    

    Le résultat est le suivant :

    OK
    
  6. Quittez la session SSH :

    exit
    

Utiliser une bibliothèque cliente

Les bibliothèques clientes d'authentification Google pour Python, Java, Go et Node.js vous permettent d'obtenir des jetons d'identification à l'aide d'API pratiques. Pour utiliser la bibliothèque Python et la classe IDTokenCredentials, procédez comme suit :

  1. Dans Cloud Shell, créez et téléchargez les identifiants du compte de service :

    gcloud iam service-accounts keys create service-account.json \
        --iam-account $SERVICE_ACCOUNT_EMAIL
    
  2. Créez un environnement virtuel Python :

    virtualenv -p python3 .venv
    
  3. Activez l'environnement virtuel Python pour cette session de terminal :

    source .venv/bin/activate
    
  4. Installez les dépendances :

    pip install -r requirements.txt
    
  5. Obtenez un jeton d'identification et envoyez une requête à l'exemple de service Cloud Run pour Anthos :

    python id_token_request.py http://$EXTERNAL_IP http://example.com \
        Host:sample.tutorial.example.com
    

    Le résultat est :

    OK
    
    import os
    import sys
    from google.auth.transport.requests import AuthorizedSession
    from google.oauth2 import service_account
    
    def request(method, url, target_audience=None, service_account_file=None,
                data=None, headers=None, **kwargs):
        """Obtains a Google-issued ID token and uses it to make a HTTP request.
    
        Args:
          method (str): The HTTP request method to use
                ('GET', 'OPTIONS', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE')
          url: The URL where the HTTP request will be sent.
          target_audience (str): Optional, the value to use in the audience
                ('aud') claim of the ID token. Defaults to the value of 'url'
                if not provided.
          service_account_file (str): Optional, the full path to the service
                account credentials JSON file. Defaults to
                '<working directory>/service-account.json'.
          data: Optional dictionary, list of tuples, bytes, or file-like object
                to send in the body of the request.
          headers (dict): Optional dictionary of HTTP headers to send with the
                request.
          **kwargs: Any of the parameters defined for the request function:
                https://github.com/requests/requests/blob/master/requests/api.py
                If no timeout is provided, it is set to 90 seconds.
    
        Returns:
          The page body, or raises an exception if the HTTP request failed.
        """
        # Set target_audience, if missing
        if not target_audience:
            target_audience = url
    
        # Set service_account_file, if missing
        if not service_account_file:
            service_account_file = os.path.join(os.getcwd(),
                                                'service-account.json')
    
        # Set the default timeout, if missing
        if 'timeout' not in kwargs:
            kwargs['timeout'] = 90  # seconds
    
        # Obtain ID token credentials for the specified audience
        creds = service_account.IDTokenCredentials.from_service_account_file(
            service_account_file, target_audience=target_audience)
    
        # Create a session for sending requests with the ID token credentials
        session = AuthorizedSession(creds)
    
        # Send a HTTP request to the provided URL using the Google-issued ID token
        resp = session.request(method, url, data=data, headers=headers, **kwargs)
        if resp.status_code == 403:
            raise Exception('Service account {} does not have permission to '
                            'access the application.'.format(
                                creds.service_account_email))
        elif resp.status_code != 200:
            raise Exception(
                'Bad response from application: {!r} / {!r} / {!r}'.format(
                    resp.status_code, resp.headers, resp.text))
        else:
            return resp.text

La documentation d'Identity-Aware Proxy présente un exemple de code qui illustre comment obtenir des jetons d'identification délivrés par Google dans d'autres langages de programmation.

Utiliser l'API Service Account Credentials IAM

Si aucune bibliothèque cliente d'authentification Google permettant de créer des jetons d'identification n'est disponible pour votre langage de programmation, vous pouvez utiliser directement l'API Service Account Credentials IAM :

  1. Créez un rôle IAM personnalisé ayant l'autorisation de créer des jetons d'identification pour les comptes de service :

    gcloud iam roles create serviceAccountIdTokenCreator \
        --project $GOOGLE_CLOUD_PROJECT \
        --description "Impersonate service accounts to create OpenID Connect ID tokens" \
        --permissions "iam.serviceAccounts.getOpenIdToken" \
        --stage GA \
        --title "Service Account ID Token Creator"
    

    Vous pouvez utiliser le rôle Créateur de jetons du compte de service prédéfini (iam.serviceAccountTokenCreator) plutôt que de créer un rôle personnalisé. Toutefois, notez que ce rôle fournit des autorisations supplémentaires qui sont inutiles à la création des jetons d'identification avec l'API Service Account Credentials.

  2. Créez une liaison de règle pour vous attribuer le rôle personnalisé sur le compte de service que vous avez créé précédemment :

    gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_EMAIL \
        --member user:$(gcloud config get-value account) \
        --role projects/$GOOGLE_CLOUD_PROJECT/roles/serviceAccountIdTokenCreator
    

    L'application de la liaison de règle peut prendre un certain temps. La commande suivante vérifie toutes les cinq secondes que la liaison de règle est appliquée. Elle arrête la vérification et vous renvoie à l'invite de terminal lorsque la liaison de règle est effective :

    status_code=""
    while [ "$status_code" != "200" ] ; do
        sleep 5
        echo "Checking permissions"
        status_code=$(curl -s -w "%{http_code}" -o /dev/null -X POST \
            -H "Authorization: Bearer $(gcloud auth print-access-token)" \
            --data-urlencode audience=http://example.com \
            --data-urlencode includeEmail=true \
            "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:generateIdToken")
    done
    
  3. Utilisez la méthode generateIdToken de l'API Service Account Credentials IAM afin de générer un jeton d'identification pour le compte de service :

    ID_TOKEN=$(curl -s -X POST \
        -H "Authorization: Bearer $(gcloud auth print-access-token)" \
        --data-urlencode audience=http://example.com \
        --data-urlencode includeEmail=true \
        "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:generateIdToken" | jq -r '.token')
    
  4. Envoyez une requête à l'aide du jeton d'identification à l'exemple de service Cloud Run pour Anthos :

    curl -s -w"\n" -H "Host: sample.tutorial.example.com" \
        -H "Authorization: Bearer $ID_TOKEN" $EXTERNAL_IP
    

    Le résultat est :

    OK
    

Utiliser l'outil de ligne de commande gcloud

Pour obtenir des jetons d'identification délivrés par Google, vous pouvez utiliser l'outil de ligne de commande gcloud. Ces jetons permettent de vous identifier ou d'identifier un compte de service, ce qui peut être utile pour tester les services de façon interactive.

  1. Accordez-vous le rôle Créateur de jetons du compte de service prédéfini sur le compte de service que vous avez créé précédemment :

    gcloud iam service-accounts add-iam-policy-binding $SERVICE_ACCOUNT_EMAIL \
        --member user:$(gcloud config get-value account) \
        --role roles/iam.serviceAccountTokenCreator
    

    L'application de la liaison de règle peut prendre un certain temps. La commande suivante vérifie toutes les cinq secondes que la liaison de règle est appliquée. Elle arrête la vérification et vous renvoie à l'invite de terminal lorsque la liaison de règle est effective :

    status_code=""
    while [ "$status_code" != "200" ] ; do
        sleep 3
        echo "Checking permissions"
        status_code=$(curl -s -w "%{http_code}" -o /dev/null -X POST \
            -H "Authorization: Bearer $(gcloud auth print-access-token)" \
            -H "Content-Type: application/json" \
            -d '{"payload": "{}"}' \
            "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/${SERVICE_ACCOUNT_EMAIL}:signJwt")
    done
    
  2. Prenez le contrôle du compte de service et obtenez un jeton d'identification :

    ID_TOKEN=$(gcloud auth print-identity-token \
        --audiences http://example.com \
        --impersonate-service-account $SERVICE_ACCOUNT_EMAIL \
        --include-email)
    
  3. Envoyez une requête à l'aide du jeton d'identification à l'exemple de service Cloud Run pour Anthos :

    curl -s -w"\n" -H "Host: sample.tutorial.example.com" \
        -H "Authorization: Bearer $ID_TOKEN" $EXTERNAL_IP
    

    Le résultat est :

    OK
    

Nettoyer

Pour éviter que les ressources utilisées lors de ce tutoriel soient facturées sur votre compte Google Cloud, supprimez le projet contenant les ressources, ou conservez le projet et supprimez les ressources individuelles.

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.

Supprimer les ressources

Si vous souhaitez conserver le projet Google Cloud que vous avez utilisé dans ce tutoriel, supprimez les différentes ressources :

  1. Supprimez le cluster GKE :

    gcloud container clusters delete $CLUSTER --async --quiet
    
  2. Supprimez le compte de service :

    gcloud iam service-accounts delete $SERVICE_ACCOUNT_EMAIL --quiet
    
  3. Supprimez le fichier d'identifiants de votre compte de service :

    rm -f service-account.json
    
  4. Supprimez l'instance Compute Engine :

    gcloud compute instances delete $VM --quiet
    

Étape suivante