S'authentifier entre différents services

Vous pouvez mettre en œuvre l'authentification entre différents services à l'aide d'un compte de service dans un service gRPC. Cette page présente l'authentification de service à service à travers un exemple complet, comprenant la procédure de configuration du proxy Extensible Service Proxy (ESP) dans un service gRPC pour assurer la compatibilité avec des requêtes authentifiées, ainsi que la procédure d'appel du service depuis un client gRPC.

Pour qu'un service puisse effectuer des appels authentifiés dans une API Cloud Endpoints, le service appelant doit disposer d'un compte de service et envoyer un jeton d'authentification dans la requête. L'appelant doit utiliser un jeton d'ID Google ou un jeton Web JSON (JWT) personnalisé, signé exclusivement par le compte de service de l'appelant. Le proxy ESP valide la correspondance de la revendication iss dans le JWT avec le paramètre issuer dans la configuration de service. Il ne vérifie pas les autorisations Identity and Access Management accordées sur le compte de service.

Dans notre exemple, vous devrez configurer et utiliser la forme la plus simple d'authentification de service à service, qui consiste pour le client à utiliser son compte de service Google Cloud pour générer les JWT d'authentification. L'approche pour les autres méthodes d'authentification est similaire, même si le processus côté client permettant d'obtenir des jetons d'authentification valides dépend de la méthode d'authentification utilisée.

Avant de commencer

Ce guide utilise l'exemple Bookstore utilisé dans nos tutoriels.

  1. Clonez le dépôt GitHub où est hébergé le code d'exemple gRPC :

    git clone https://github.com/GoogleCloudPlatform/python-docs-samples.git
    
  2. Modifiez votre répertoire de travail :

    cd python-docs-samples/endpoints/bookstore-grpc/
    
  3. Suivez les instructions du tutoriel pour configurer un projet si vous n'en avez pas encore.

Dans cet exemple, vous utilisez le déploiement sur Google Kubernetes Engine, bien que la configuration de l'authentification soit la même pour Compute Engine.

Dans l'exemple, deux projets Google Cloud Platform sont référencés :

  • Le projet producteur du service, qui possède le service Cloud Endpoints pour gRPC
  • Le projet client du service, qui possède le client gRPC

Créer le compte de service et la clé du client

Pour créer le compte de service et la clé pour le projet client, procédez comme suit :

  1. Dans Google Cloud Console, accédez à la page APIs & services (API et services).

    API et services

    Assurez-vous que vous êtes dans votre projet client.
  2. Sur la page Identifiants, dans la liste déroulante Créer des identifiants, sélectionnez Clé de compte de service.
  3. Sur la page Créer une clé de compte de service, si vous souhaitez utiliser un compte de service existant, sélectionnez-le. Dans le cas contraire, dans la liste déroulante Compte de service, sélectionnez Nouveau compte de service et saisissez un nom de compte.

    Un ID de compte de service correspondant est créé pour vous. Notez cet ID, car il est nécessaire dans les sections suivantes. Exemple :

    service-account-name@YOUR_PROJECT_ID.iam.gserviceaccount.com
    
  4. Cliquez sur la liste déroulante Rôle et sélectionnez les rôles suivants :

    • Comptes de service > Utilisateur du compte de service
    • Comptes de service > Créateur de jetons du compte de service
  5. Assurez-vous que le type de clé JSON est sélectionné.

  6. Cliquez sur Créer. Le fichier de clé JSON de votre compte de service est téléchargé sur votre ordinateur local. Notez l'emplacement et assurez-vous qu'il est stocké de manière sécurisée, car il sera utilisé ultérieurement pour générer des jetons.

Configurer l'authentification pour le service

Utilisez le projet producteur pour toutes les étapes de cette section.

Configurer l'authentification dans la configuration de l'API gRPC

L'authentification pour le proxy ESP est configurée dans la section authentication du fichier de configuration YAML de votre API gRPC. La configuration avec authentification pour notre exemple de service se trouve dans le fichier api_config_auth.yaml.

authentication:
  providers:
  - id: google_service_account
    # Replace SERVICE-ACCOUNT-ID with your service account's email address.
    issuer: SERVICE-ACCOUNT-ID
    jwks_uri: https://www.googleapis.com/robot/v1/metadata/x509/SERVICE-ACCOUNT-ID
  rules:
  # This auth rule will apply to all methods.
  - selector: "*"
    requirements:
      - provider_id: google_service_account

La section providers spécifie le ou les fournisseurs d'authentification que vous souhaitez utiliser. Dans le cas présent, il s'agira d'un compte de service Google. La section rules indique que vous avez besoin de jetons de ce fournisseur pour accéder à toutes les méthodes de votre service.

Dans votre copie du fichier se trouvant dans le clone du dépôt :

  • Remplacez MY_PROJECT_ID par l'ID de votre projet producteur.
  • Remplacez SERVICE-ACCOUNT-ID dans la section authentication (à la fois pour les valeurs issuer et jwks_uri) par l'ID du compte de service client complet, noté à la section précédente. Cela indique au proxy ESP que vous souhaitez accorder l'accès à votre service aux utilisateurs qui fournissent des jetons valides à partir de ce compte de service particulier.

Enregistrez le fichier pour l'étape suivante.

Déployer la configuration et le service

Ces étapes sont les mêmes que celles de la page Premiers pas avec gRPC sur GKE :

  1. Déployez la configuration de votre service sur Endpoints : cette étape est nécessaire même si vous l'avez suivie pour le tutoriel, car la configuration utilisée est différente. Notez le nom du service affiché :

    gcloud endpoints services deploy api_descriptor.pb api_config_auth.yaml --project PRODUCER_PROJECT
    
  2. Créez un cluster de conteneurs et authentifiez kubectl auprès du cluster, si ce n'est pas déjà fait.

  3. Déployez les exemples d'API et d'ESP sur le cluster. Si vous utilisez des projets producteur et client distincts, vérifiez d'abord que vous avez défini le bon projet dans l'outil de ligne de commande gcloud :

    gcloud config set project PRODUCER_PROJECT
    

Appeler des méthodes authentifiées à partir d'un client gRPC

Enfin, côté client, vous pouvez utiliser la clé du compte de service pour générer un jeton JWT, puis utiliser ce jeton pour appeler une méthode Bookstore authentifiée. Commencez par installer les éléments Python indispensables à la génération du jeton et à l'exécution de l'exemple de client. Assurez-vous de vous trouver dans le dossier python-docs-samples/endpoints/bookstore-grpc du client cloné, puis :

virtualenv bookstore-env
source bookstore-env/bin/activate
pip install -r requirements.txt

Générer un jeton JWT

Dans cet exemple, Bookstore utilise l'authentification de service à service, dans laquelle le service appelant n'est authentifié que par son compte de service. Il est donc simple de créer un jeton approprié à envoyer avec nos requêtes. Notez que vous pouvez également exiger une authentification plus stricte entre différents services, dans laquelle le jeton généré doit être davantage authentifié par Google (à l'aide d'un jeton d'ID Google).

Pour cet exemple, le script Python fournit peut générer un jeton à partir du fichier de clé JSON téléchargé précédemment, à l'aide d'un ID d'utilisateur et d'une adresse e-mail fictifs.

#!/usr/bin/env python

# Copyright 2016 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Example of generating a JWT signed from a service account file."""

import argparse
import json
import time

import google.auth.crypt
import google.auth.jwt

"""Max lifetime of the token (one hour, in seconds)."""
MAX_TOKEN_LIFETIME_SECS = 3600

def generate_jwt(service_account_file, issuer, audiences):
    """Generates a signed JSON Web Token using a Google API Service Account."""
    with open(service_account_file, 'r') as fh:
        service_account_info = json.load(fh)

    signer = google.auth.crypt.RSASigner.from_string(
        service_account_info['private_key'],
        service_account_info['private_key_id'])

    now = int(time.time())

    payload = {
        'iat': now,
        'exp': now + MAX_TOKEN_LIFETIME_SECS,
        # aud must match 'audience' in the security configuration in your
        # swagger spec. It can be any string.
        'aud': audiences,
        # iss must match 'issuer' in the security configuration in your
        # swagger spec. It can be any string.
        'iss': issuer,
        # sub and email are mapped to the user id and email respectively.
        'sub': issuer,
        'email': 'user@example.com'
    }

    signed_jwt = google.auth.jwt.encode(signer, payload)
    return signed_jwt

if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        description=__doc__,
        formatter_class=argparse.RawDescriptionHelpFormatter)
    parser.add_argument('--file',
                        help='The path to your service account json file.')
    parser.add_argument('--issuer', default='', help='issuer')
    parser.add_argument('--audiences', default='', help='audiences')

    args = parser.parse_args()

    signed_jwt = generate_jwt(args.file, args.issuer, args.audiences)
    print(signed_jwt.decode('utf-8'))

Pour générer un jeton à l'aide du script :

  • Générez un jeton JWT et affectez-le à la variable $JWT_TOKEN :

    JWT_TOKEN=$(python jwt_token_gen.py \
        --file=[SERVICE_ACCOUNT_FILE] \
        --audiences=[SERVICE_NAME] \
        --issuer=[SERVICE-ACCOUNT-ID])
    

    où :

    • [SERVICE_ACCOUNT_FILE] est le fichier de clé JSON du compte de service client précédemment téléchargé.
    • [SERVICE_NAME] est le nom du service Bookstore qui a été affiché lorsque nous avons déployé sa configuration de service mise à jour sur Endpoints.
    • [SERVICE-ACCOUNT-ID] est l'ID de compte de service client complet obtenu lors de la génération du compte de service.

Effectuer un appel gRPC authentifié

Cette dernière étape utilise bookstore_client.py, qui est le même client que celui utilisé dans les tutoriels. Pour effectuer un appel authentifié, le client transmet le JWT en tant que métadonnées lors de l'appel de sa méthode.

def run(host, port, api_key, auth_token, timeout, use_tls):
    """Makes a basic ListShelves call against a gRPC Bookstore server."""

    if use_tls:
        with open('../roots.pem', 'rb') as f:
            creds = grpc.ssl_channel_credentials(f.read())
        channel = grpc.secure_channel('{}:{}'.format(host, port), creds)
    else:
        channel = grpc.insecure_channel('{}:{}'.format(host, port))

    stub = bookstore_pb2_grpc.BookstoreStub(channel)
    metadata = []
    if api_key:
        metadata.append(('x-api-key', api_key))
    if auth_token:
        metadata.append(('authorization', 'Bearer ' + auth_token))
    shelves = stub.ListShelves(empty_pb2.Empty(), timeout, metadata=metadata)
    print('ListShelves: {}'.format(shelves))

Pour exécuter l'exemple :

  1. Utilisez kubectl get services pour obtenir l'adresse IP externe du service Bookstore déployé :

    #kubectl get services
    NAME                 CLUSTER-IP      EXTERNAL-IP      PORT(S)           AGE
    echo                 10.11.246.240   104.196.186.92   80/TCP            10d
    endpoints            10.11.243.168   104.196.210.50   80/TCP,8090/TCP   10d
    esp-grpc-bookstore   10.11.254.34    104.196.60.37    80/TCP            1d
    kubernetes           10.11.240.1     <none>           443/TCP           10d
    

    Dans ce cas, il s'agit du service esp-grpc-bookstore et son adresse IP externe est 104.196.60.37.

  2. Attribuez l'adresse IP à la variable EXTERNAL_IP :

    EXTERNAL_IP=104.196.60.37
    
  3. Répertoriez toutes les étagères du service Bookstore :

    python bookstore_client.py --port=80 --host=$EXTERNAL_IP --auth_token=$JWT_TOKEN
    

    Le service affiche toutes les étagères du service Bookstore actuel. Vous pouvez le vérifier en ne fournissant pas de jeton ou en spécifiant un ID de compte de service incorrect lors de la génération du fichier JWT. La commande doit échouer.

Étapes suivantes