Migrer App Identity vers des jetons d'ID OIDC

Lorsqu'une application exécutée dans l'environnement Python 2 envoie une requête à une autre application App Engine, elle peut valider son identité à l'aide de l'API App Engine App Identity. L'application qui reçoit la requête peut utiliser cette identité pour déterminer si elle doit traiter la requête.

Si vos applications Python 3 doivent valider leur identité lors de l'envoi de requêtes à d'autres applications App Engine, vous pouvez utiliser des jetons d'ID OpenID Connect (OIDC) émis et décodés par les API OAuth 2.0 de Google.

Voici un aperçu de l'utilisation des jetons d'ID OIDC pour l'assertion et la validation de l'identité :

  1. Une application App Engine nommée "Application A" récupère un jeton d'ID à partir de l'environnement d'exécution Google Cloud.
  2. L'application A ajoute ce jeton à un en-tête de requête juste avant d'envoyer la requête à l'application B, qui est une autre application App Engine.
  3. L'application B utilise les API OAuth 2.0 de Google pour vérifier la charge utile du jeton. La charge utile décodée contient l'identité validée de l'application A sous la forme de l'adresse e-mail du compte de service par défaut de l'application A.
  4. L'application B compare l'identité de la charge utile à une liste d'identités auxquelles elle est autorisée à répondre. Si la requête provient d'une application autorisée, l'application B la traite et répond.

Processus OAuth 2.0

Ce guide explique comment mettre à jour vos applications App Engine afin qu'elles utilisent les jetons d'ID OpenID Connect (OIDC) pour valider leur identité, et mettre à jour vos autres applications App Engine afin qu'elles utilisent des jetons d'ID pour vérifier l'identité avant de traiter une requête.

Principales différences entre les API App Identity et OIDC

  • Les applications de l'environnement d'exécution Python 2 n'ont pas besoin de valider explicitement une identité. Lorsqu'une application utilise les bibliothèques Python httplib, urllib ou urllib2 ou le service de récupération d'URL App Engine pour envoyer des requêtes sortantes, l'environnement d'exécution utilise le service de récupération d'URL App Engine pour effectuer la requête. Si la requête est envoyée au domaine appspot.com, la récupération d'URL valide automatiquement l'identité de l'application à l'origine de la requête en ajoutant l'en-tête X-Appengine-Inbound-Appid à la requête. Cet en-tête contient l'ID application de l'application (également appelé ID du projet).

    Les applications de l'environnement d'exécution Python 3 doivent valider explicitement leur identité en récupérant un jeton d'ID OIDC depuis l'environnement d'exécution Google Cloud et en l'ajoutant à l'en-tête de requête. Vous devrez mettre à jour l'ensemble du code qui envoie des requêtes à d'autres applications App Engine de sorte que les requêtes contiennent un jeton d'ID OIDC.

  • L'en-tête X-Appengine-Inbound-Appid d'une requête contient l'ID de projet de l'application qui a envoyé la requête.

    La charge utile du jeton d'ID OIDC de Google n'identifie pas directement l'ID du projet de l'application. Au lieu de cela, le jeton identifie le compte de service sur lequel l'application s'exécute, en fournissant l'adresse e-mail de ce compte de service. Vous devez ajouter du code pour extraire le nom d'utilisateur de la charge utile du jeton.

    Si ce compte de service est le compte de service App Engine par défaut du projet, l'ID du projet se trouve dans l'adresse e-mail du compte de service. La partie de l'adresse contenant le nom d'utilisateur est identique à l'ID du projet. Dans ce cas, le code de votre application de réception peut effectuer cette recherche dans la liste des ID de projet à partir desquels il autorise les requêtes.

    Toutefois, si l'application à l'origine de la requête utilise un compte de service géré par l'utilisateur au lieu du compte de service App Engine par défaut, l'application de réception peut uniquement valider l'identité de ce compte de service, ce qui ne définira pas nécessairement l'ID de projet de l'application à l'origine de la requête. Dans ce cas, l'application de réception devra gérer une liste d'adresses e-mail de comptes de service autorisées au lieu d'une liste d'ID de projets autorisés.

  • Les quotas pour les appels à l'API URL Fetch sont différents des quotas des API OAuth 2.0 de Google en ce qui concerne l'attribution de jetons. Vous pouvez voir le nombre maximal de jetons que vous pouvez accorder par jour sur l'écran d'autorisation OAuth de la console Google Cloud. URL Fetch, l'API App Identity et les API OAuth 2.0 de Google n'engendrent aucune facturation.

Présentation du processus de migration

Pour migrer vos applications Python afin qu'elles utilisent les API OIDC pour l'assertion et la validation de l'identité :

  1. Dans les applications qui doivent valider l'identité lors de l'envoi de requêtes à d'autres applications App Engine :

    1. Attendez que votre application s'exécute dans un environnement Python 3 pour migrer vers des jetons d'identification.

      Bien qu'il soit possible d'utiliser des jetons d'ID dans l'environnement d'exécution Python 2, les étapes d'exécution en Python 2 sont complexes et ne sont requises que temporairement jusqu'à ce que vous mettiez à jour votre application pour qu'elle s'exécute dans l'environnement d'exécution Python 3.

    2. Une fois que votre application est exécutée dans Python 3, mettez à jour l'application pour qu'elle demande un jeton d'ID et l'ajoute à un en-tête de requête.

  2. Dans les applications qui doivent valider l'identité avant de traiter une requête :

    1. Commencez par mettre à niveau vos applications Python 2 pour prendre en charge à la fois les jetons d'ID et les identités de l'API App Identity. Vos applications pourront ainsi valider et traiter les requêtes des applications Python 2 qui utilisent l'API App Identity et les requêtes des applications Python 3 qui utilisent des jetons d'ID.

    2. Une fois que vos applications Python 2 mises à niveau sont stables, migrez-les vers l'environnement d'exécution Python 3. Continuez de prendre en charge à la fois les jetons d'ID et les identités de l'API App Identity tant que vous n'êtes pas certain que les applications n'ont plus besoin de prendre en charge les requêtes provenant d'anciennes applications.

    3. Lorsque vous n'avez plus besoin de traiter les requêtes provenant d'anciennes applications App Engine, supprimez le code qui valide les identités de l'API App Identity.

  3. Après avoir testé vos applications, déployez l'application qui traite les requêtes en premier. Déployez ensuite votre application Python 3 mise à jour qui utilise des jetons d'ID pour valider l'identité.

Valider l'identité

Attendez que votre application s'exécute dans un environnement Python 3, puis procédez comme suit pour mettre à niveau l'application afin de valider l'identité avec des jetons d'ID :

  1. Installez la bibliothèque cliente google-auth.

  2. Ajoutez du code pour demander un jeton d'ID à partir des API OAuth 2.0 de Google et ajoutez ce jeton à un en-tête de requête avant d'envoyer une requête.

  3. Testez vos mises à jour.

Installer la bibliothèque cliente google-auth pour les applications Python 3

Pour que la bibliothèque cliente google-auth soit disponible pour votre application Python 3, créez un fichier requirements.txt dans le même dossier que votre fichier app.yaml, puis ajoutez la ligne suivante :

     google-auth

Lorsque vous déployez votre application, App Engine télécharge toutes les dépendances définies dans le fichier requirements.txt.

Pour le développement local, nous vous recommandons d'installer des dépendances dans un environnement virtuel tel que venv.

Ajouter du code pour valider l'identité

Parcourez votre code et trouvez toutes les instances d'envoi de requêtes à d'autres applications App Engine. Mettez à jour ces instances pour qu'elles effectuent les opérations suivantes avant d'envoyer la requête :

  1. Ajoutez les importations suivantes :

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. Utilisez google.oauth2.id_token.fetch_id_token(request, audience) pour récupérer un jeton d'ID. Incluez les paramètres suivants dans l'appel de méthode :

    • request : transmettez l'objet de la requête que vous êtes sur le point d'envoyer.
    • audience : transmettez l'URL de l'application à laquelle vous envoyez la requête. Cela lie le jeton à la requête et empêche l'utilisation du jeton par une autre application.

      Par souci de clarté et de spécificité, nous vous recommandons de transmettre l'URL appspot.com créée par App Engine pour le service spécifique qui reçoit la requête, même si vous utilisez un domaine personnalisé pour l'application.

  3. Dans l'objet de la requête, définissez l'en-tête suivant :

    'Authorization': 'ID {}'.format(token)
    

Exemple :

# Copyright 2020 Google LLC
#
# 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.

from flask import Flask, render_template, request
from google.auth.transport import requests as reqs
from google.oauth2 import id_token
import requests

app = Flask(__name__)

@app.route('/', methods=['GET'])
def index():
    return render_template('index.html')

@app.route('/', methods=['POST'])
def make_request():
    url = request.form['url']
    token = id_token.fetch_id_token(reqs.Request(), url)

    resp = requests.get(
        url,
        headers={'Authorization': f'Bearer {token}'}
    )

    message = f'Response when calling {url}:\n\n'
    message += resp.text

    return message, 200, {'Content-type': 'text/plain'}

Tester les mises à jour pour valider l'identité

Pour exécuter votre application localement et vérifier si l'application peut envoyer des jetons d'ID, procédez comme suit :

  1. Suivez ces étapes pour rendre les identifiants du compte de service App Engine par défaut disponibles dans votre environnement local (les API Google OAuth ont besoin de ces identifiants pour générer un jeton d'ID) :

    1. Saisissez la commande gcloud suivante afin de récupérer la clé de compte de service pour le compte App Engine par défaut de votre projet :

      gcloud iam service-accounts keys create ~/key.json --iam-account project-ID@appspot.gserviceaccount.com

      Remplacez project-ID par l'ID de votre projet Google Cloud.

      Le fichier de clé du compte de service est maintenant téléchargé sur votre ordinateur. Vous pouvez déplacer et renommer ce fichier comme vous le souhaitez. Veillez à stocker ce fichier en toute sécurité, car il peut être utilisé pour s'authentifier en tant que compte de service. Si vous perdez le fichier, ou si le fichier est exposé à des utilisateurs non autorisés, supprimez la clé de compte de service et créez-en une nouvelle.

    2. Saisissez la commande suivante :

      <code>export GOOGLE_APPLICATION_CREDENTIALS=<var>service-account-key</var></code>
      

    Remplacez service-account-key par le nom de chemin absolu du fichier contenant la clé de compte de service que vous avez téléchargée.

  2. Dans l'interface système dans laquelle vous avez exporté la variable d'environnement GOOGLE_APPLICATION_CREDENTIALS, démarrez votre application Python.

  3. Envoyez une requête à partir de l'application et confirmez qu'elle réussit. Si vous ne possédez pas encore d'application pouvant recevoir des requêtes et que vous utilisez des jetons d'ID pour valider les identités :

    1. Téléchargez l'exemple d'application "entrante".
    2. Dans le fichier main.py de l'exemple, ajoutez l'ID de votre projet Google Cloud à allowed_app_ids. Exemple :

       allowed_app_ids = [
          '<APP_ID_1>',
          '<APP_ID_2>',
          'my-project-id'
        ]
      
    3. Exécutez l'exemple mis à jour sur le serveur de développement local de Python 2.

Valider et traiter des requêtes

Pour mettre à niveau vos applications Python 2 afin qu'elles utilisent des jetons d'ID ou des identités de l'API App Identity avant de traiter des requêtes, procédez comme suit :

  1. Installez la bibliothèque cliente google-auth.

  2. Mettez à jour votre code pour effectuer les opérations suivantes :

    1. Si la requête contient l'en-tête X-Appengine-Inbound-Appid, utilisez-le pour vérifier l'identité. Les applications s'exécutant dans un ancien environnement d'exécution, tel que Python 2, contiennent cet en-tête.

    2. Si la requête ne contient pas l'en-tête X-Appengine-Inbound-Appid, recherchez un jeton d'ID OIDC. Si le jeton existe, vérifiez la charge utile du jeton et l'identité de l'expéditeur.

  3. Testez vos mises à jour.

Installer la bibliothèque cliente google-auth pour les applications Python 2

Pour que la bibliothèque cliente google-auth soit disponible pour votre application Python 2, procédez comme suit :

  1. Créez un fichier requirements.txt dans le même dossier que votre fichier app.yaml, puis ajoutez la ligne suivante :

     google-auth==1.19.2
    

    Nous vous recommandons d'utiliser la version 1.19.2 de la bibliothèque cliente Cloud Logging, car elle prend en charge les applications Python 2.7.

  2. Dans le fichier app.yaml de votre application, spécifiez la bibliothèque SSL dans la section libraries si ce n'est pas déjà fait :

    libraries:
    - name: ssl
      version: latest
    
  3. Créez un répertoire pour stocker les bibliothèques tierces, par exemple lib/. Exécutez ensuite pip install pour installer les bibliothèques dans le répertoire. Exemple :

    pip install -t lib -r requirements.txt
  4. Créez un fichier appengine_config.py dans le même dossier que votre fichier app.yaml. Ajoutez le code ci-dessous à votre fichier appengine_config.py :

    # appengine_config.py
    import pkg_resources
    from google.appengine.ext import vendor
    
    # Set path to your libraries folder.
    path = 'lib'
    # Add libraries installed in the path folder.
    vendor.add(path)
    # Add libraries to pkg_resources working set to find the distribution.
    pkg_resources.working_set.add_entry(path)

    Le fichier appengine_config.py de l'exemple précédent suppose que le dossier lib se trouve dans le répertoire de travail actuel. Si vous ne pouvez pas garantir que lib sera toujours dans le répertoire de travail actuel, spécifiez le chemin d'accès complet au dossier lib. Exemple :

    import os
    path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'lib')

Pour le développement local, nous vous recommandons d'installer les dépendances dans un environnement virtuel tel que virtualenv pour Python 2.

Mettre à jour le code pour valider les requêtes

Parcourez votre code et trouvez toutes les instances d'obtention de la valeur de l'en-tête X-Appengine-Inbound-Appid. Mettez à jour ces instances pour effectuer les opérations suivantes :

  1. Ajoutez les importations suivantes :

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. Si la requête entrante ne contient pas l'en-tête X-Appengine-Inbound-Appid, recherchez l'en-tête Authorization et récupérez sa valeur.

    La valeur de l'en-tête est au format "ID: jeton".

  3. Utilisez google.oauth2.id_token.verify_oauth2_token(token, request, audience) pour valider et récupérer la charge utile de jeton décodée. Incluez les paramètres suivants dans l'appel de méthode :

    • token : transmettez le jeton que vous avez extrait de la requête entrante.
    • request : transmettez un nouvel objet google.auth.transport.Request.

    • audience : transmettez l'URL de l'application actuelle (l'application qui envoie la requête de validation). Le serveur d'autorisation de Google va comparer cette URL à celle fournie lors de la génération initiale du jeton. Si les URL ne correspondent pas, le jeton ne sera pas validé et le serveur d'autorisation renverra une erreur.

  4. La méthode verify_oauth2_token renvoie la charge utile de jeton décodée, qui contient plusieurs paires nom/valeur, y compris l'adresse e-mail du compte de service par défaut pour l'application qui a généré le jeton.

  5. Extrayez le nom d'utilisateur de l'adresse e-mail dans la charge utile du jeton.

    Le nom d'utilisateur est identique à l'ID de projet de l'application qui a envoyé la requête. Il s'agit de la même valeur que celle précédemment renvoyée dans l'en-tête X-Appengine-Inbound-Appid.

  6. Si le nom d'utilisateur/l'ID du projet se trouve dans la liste des ID de projet autorisés, traitez la requête.

Exemple :

# Copyright 2020 Google LLC
#
# 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.

"""
Authenticate requests coming from other App Engine instances.
"""

from google.oauth2 import id_token
from google.auth.transport import requests

import logging
import webapp2

def get_app_id(request):
    # Requests from App Engine Standard for Python 2.7 will include a
    # trustworthy X-Appengine-Inbound-Appid. Other requests won't have
    # that header, as the App Engine runtime will strip it out
    incoming_app_id = request.headers.get(
        'X-Appengine-Inbound-Appid', None)
    if incoming_app_id is not None:
        return incoming_app_id

    # Other App Engine apps can get an ID token for the App Engine default
    # service account, which will identify the application ID. They will
    # have to include at token in an Authorization header to be recognized
    # by this method.
    auth_header = request.headers.get('Authorization', None)
    if auth_header is None:
        return None

    # The auth_header must be in the form Authorization: Bearer token.
    bearer, token = auth_header.split()
    if bearer.lower() != 'bearer':
        return None

    try:
        info = id_token.verify_oauth2_token(token, requests.Request())
        service_account_email = info['email']
        incoming_app_id, domain = service_account_email.split('@')
        if domain != 'appspot.gserviceaccount.com':  # Not App Engine svc acct
            return None
        else:
            return incoming_app_id
    except Exception as e:
        # report or log if desired, as here:
        logging.warning('Request has bad OAuth2 id token: {}'.format(e))
        return None

class MainPage(webapp2.RequestHandler):
    allowed_app_ids = [
        'other-app-id',
        'other-app-id-2'
    ]

    def get(self):
        incoming_app_id = get_app_id(self.request)

        if incoming_app_id is None:
            self.abort(403)

        if incoming_app_id not in self.allowed_app_ids:
            self.abort(403)

        self.response.write('This is a protected page.')

app = webapp2.WSGIApplication([
    ('/', MainPage)
], debug=True)

Tester les mises à jour pour valider l'identité

Pour vérifier que votre application peut utiliser un jeton d'ID ou l'en-tête X-Appengine-Inbound-Appid afin de valider les requêtes, exécutez l'application dans le serveur de développement local de Python 2, puis envoyez des requêtes à partir d'applications Python 2 (qui utiliseront l'API App Identity) et à partir d'applications Python 3 qui envoient des jetons d'ID.

Si vous n'avez pas mis à jour vos applications pour envoyer des jetons d'ID :

  1. Téléchargez l'exemple d'application "à l'origine de la requête".

  2. Ajoutez des identifiants de compte de service à votre environnement local, comme décrit dans la section Tester les mises à jour pour valider l'identité des applications.

  3. Utilisez les commandes Python 3 standards pour démarrer l'exemple d'application Python 3.

  4. Envoyez une requête à partir de l'exemple d'application et confirmez qu'elle réussit.

Déployer vos applications

Lorsque vous êtes prêt à déployer vos applications, vous devez effectuer les opérations suivantes :

  1. Testez les applications sur App Engine.

  2. Si les applications s'exécutent sans erreur, répartissez le trafic pour augmenter lentement le trafic de vos applications mises à jour. Surveillez attentivement les éventuels problèmes avant d'acheminer davantage de trafic vers les applications mises à jour.

Utiliser un autre compte de service pour valider l'identité

Lorsque vous demandez un jeton d'ID, la requête utilise l'identité du compte de service App Engine par défaut. Lorsque vous validez le jeton, la charge utile de jeton contient l'adresse e-mail du compte de service par défaut, qui correspond à l'ID du projet de votre application.

Le compte de service App Engine par défaut dispose d'un niveau d'autorisation très élevé par défaut. Il peut afficher et modifier l'ensemble de votre projet Google Cloud. Dans la plupart des cas, l'utilisation de ce compte n'est donc pas appropriée lorsque votre application doit s'authentifier auprès de services cloud.

Toutefois, le compte de service par défaut peut être utilisé en toute sécurité pour valider l'identité de l'application, car vous utilisez uniquement le jeton d'ID pour valider l'identité de l'application qui a envoyé une requête. Les autorisations réelles qui ont été accordées au compte de service ne sont pas prises en compte ni nécessaires au cours de ce processus.

Si vous préférez toujours utiliser un autre compte de service pour vos requêtes de jetons d'ID, procédez comme suit :

  1. Définissez une variable d'environnement nommée GOOGLE_APPLICATION_CREDENTIALS sur le chemin d'accès d'un fichier JSON contenant les identifiants du compte de service. Consultez nos recommandations pour savoir comment stocker ces identifiants en toute sécurité.

  2. Utilisez google.oauth2.id_token.fetch_id_token(request, audience) pour récupérer un jeton d'ID.

  3. Lorsque vous validez ce jeton, la charge utile de jeton contient l'adresse e-mail du nouveau compte de service.