Migrar App Identity a tokens de ID OIDC

Cuando una aplicación que se ejecuta en el tiempo de ejecución de Python 2 envía una solicitud a otra aplicación de App Engine, puede usar la API App Identity de App Engine para confirmar su identidad. La aplicación que recibe la solicitud puede usar esta identidad para determinar si debe procesarla.

Si tus aplicaciones de Python 3 necesitan confirmar su identidad al enviar solicitudes a otras aplicaciones de App Engine, puedes usar tokens de ID de OpenID Connect (OIDC) que emiten y decodifican las APIs OAuth 2.0 de Google.

A continuación, se incluye un resumen de cómo usar los tokens de ID de OIDC para afirmar y verificar la identidad:

  1. Una aplicación de App Engine llamada "App A" obtiene un token de ID del Google Cloud entorno de ejecución.
  2. La aplicación A añade este token a un encabezado de solicitud justo antes de enviar la solicitud a la aplicación B, que es otra aplicación de App Engine.
  3. La aplicación B usa las APIs de OAuth 2.0 de Google para verificar la carga útil del token. La carga útil decodificada contiene la identidad verificada de la aplicación A en forma de dirección de correo electrónico de la cuenta de servicio predeterminada de la aplicación A.
  4. La aplicación B compara la identidad de la carga útil con una lista de identidades a las que puede responder. Si la solicitud procede de una aplicación permitida, la aplicación B la procesa y responde.

Proceso de OAuth 2.0

En esta guía se describe cómo actualizar tus aplicaciones de App Engine para que usen tokens de ID de OpenID Connect (OIDC) para confirmar la identidad, así como cómo actualizar tus otras aplicaciones de App Engine para que usen tokens de ID para verificar la identidad antes de procesar una solicitud.

Diferencias clave entre las APIs App Identity y OIDC

  • Las aplicaciones del tiempo de ejecución de Python 2 no necesitan afirmar su identidad de forma explícita. Cuando una aplicación usa las bibliotecas de Python httplib, urllib o urllib2, o el servicio de obtención de URLs de App Engine para enviar solicitudes salientes, el tiempo de ejecución usa el servicio de obtención de URLs de App Engine para hacer la solicitud. Si la solicitud se envía al dominio appspot.com, URL Fetch afirma automáticamente la identidad de la aplicación que realiza la solicitud añadiendo el encabezado X-Appengine-Inbound-Appid a la solicitud. Este encabezado contiene el ID de aplicación de la aplicación (también llamado ID de proyecto).

    Las aplicaciones en el tiempo de ejecución de Python 3 deben afirmar explícitamente la identidad recuperando un token de ID de OIDC del entorno de tiempo de ejecución y añadiéndolo al encabezado de la solicitud. Google Cloud Deberá actualizar todo el código que envíe solicitudes a otras aplicaciones de App Engine para que las solicitudes contengan un token de ID de OIDC.

  • El encabezado X-Appengine-Inbound-Appid de una solicitud contiene el ID del proyecto de la aplicación que ha enviado la solicitud.

    La carga útil del token de ID de OIDC de Google no identifica directamente el ID de proyecto de la aplicación. En su lugar, el token identifica la cuenta de servicio con la que se ejecuta la aplicación proporcionando la dirección de correo de esa cuenta de servicio. Deberá añadir código para extraer el nombre de usuario de la carga útil del token.

    Si esa cuenta de servicio es la cuenta de servicio predeterminada de App Engine a nivel de aplicación del proyecto, el ID del proyecto se puede encontrar en la dirección de correo de la cuenta de servicio. La parte del nombre de usuario de la dirección es la misma que el ID del proyecto. En este caso, el código de tu aplicación receptora puede buscarlo en la lista de IDs de proyecto desde los que permitirá solicitudes.

    Sin embargo, si la aplicación que hace la solicitud usa una cuenta de servicio gestionada por el usuario en lugar de la cuenta de servicio predeterminada de App Engine, la aplicación receptora solo podrá verificar la identidad de esa cuenta de servicio, que no definirá necesariamente el ID de proyecto de la aplicación que hace la solicitud. En ese caso, la aplicación receptora tendrá que mantener una lista de correos de cuentas de servicio permitidos en lugar de una lista de IDs de proyectos permitidos.

  • Las cuotas de las llamadas a la API de obtención de URLs son diferentes de las cuotas de las APIs de OAuth 2.0 de Google para conceder tokens. Puedes ver el número máximo de tokens que puedes conceder al día en la Google Cloud pantalla de consentimiento de OAuth de la consola. Ni URL Fetch, ni la API App Identity ni las APIs OAuth 2.0 de Google generan costes.

Visión general del proceso de migración

Para migrar tus aplicaciones Python para que usen las APIs de OIDC con el fin de afirmar y verificar la identidad, sigue estos pasos:

  1. En las aplicaciones que necesiten confirmar la identidad al enviar solicitudes a otras aplicaciones de App Engine:

    1. Espera a que tu aplicación se ejecute en un entorno de Python 3 para migrar a los tokens de ID.

      Aunque es posible usar tokens de ID en el entorno de ejecución de Python 2, los pasos de Python 2 son complejos y solo son necesarios temporalmente hasta que actualices tu aplicación para que se ejecute en el entorno de ejecución de Python 3.

    2. Una vez que tu aplicación se ejecute en Python 3, actualízala para solicitar un token de ID y añadirlo a un encabezado de solicitud.

  2. En las aplicaciones que necesiten verificar la identidad antes de procesar una solicitud:

    1. Empieza por actualizar tus aplicaciones de Python 2 para que admitan tanto tokens de ID como identidades de la API App Identity. De esta forma, tus aplicaciones podrán verificar y procesar solicitudes de aplicaciones Python 2 que usen la API App Identity o de aplicaciones Python 3 que usen tokens de ID.

    2. Cuando las aplicaciones de Python 2 actualizadas sean estables, migra a Python 3. Sigue admitiendo tanto los tokens de ID como las identidades de la API App Identity hasta que tengas la certeza de que las aplicaciones ya no necesitan admitir solicitudes de aplicaciones antiguas.

    3. Cuando ya no necesites procesar solicitudes de aplicaciones de App Engine antiguas, elimina el código que verifica las identidades de la API App Identity.

  3. Después de probar tus aplicaciones, despliega primero la aplicación que procesa las solicitudes. A continuación, despliega la aplicación Python 3 actualizada que usa tokens de ID para afirmar la identidad.

Afirmar la identidad

Espera hasta que tu aplicación se ejecute en un entorno de Python 3 y, a continuación, sigue estos pasos para actualizar la aplicación y verificar la identidad con tokens de ID:

  1. Instala la google-auth biblioteca de cliente.

  2. Añade código para solicitar un token de ID a las APIs de OAuth 2.0 de Google y añade el token a un encabezado de solicitud antes de enviar una solicitud.

  3. Prueba las actualizaciones.

Instalar la biblioteca de cliente google-auth para aplicaciones Python 3

Para que la biblioteca de cliente google-auth esté disponible en tu aplicación Python 3, crea un archivo requirements.txt en la misma carpeta que tu archivo app.yaml y añade la siguiente línea:

     google-auth

Cuando despliegues tu aplicación, App Engine descargará todas las dependencias definidas en el archivo requirements.txt.

Para el desarrollo local, te recomendamos que instales las dependencias en un entorno virtual, como venv.

Añadir código para confirmar la identidad

Busca en tu código todas las instancias de envío de solicitudes a otras aplicaciones de App Engine. Actualiza esas instancias para que hagan lo siguiente antes de enviar la solicitud:

  1. Añade las siguientes importaciones:

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. Usa google.oauth2.id_token.fetch_id_token(request, audience) para obtener un token de ID. Incluye los siguientes parámetros en la llamada al método:

    • request: Pasa el objeto de solicitud que estés preparando para enviar.
    • audience: pasa la URL de la aplicación a la que envías la solicitud. De esta forma, el token se vincula a la solicitud y se evita que otra aplicación lo utilice.

      Para que sea más claro y específico, te recomendamos que envíes la appspot.com URL que App Engine ha creado para el servicio específico que recibe la solicitud, aunque uses un dominio personalizado para la aplicación.

  3. En el objeto de solicitud, define el siguiente encabezado:

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

Por ejemplo:

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

Actualizaciones de pruebas para afirmar la identidad

Para ejecutar tu aplicación de forma local y comprobar si puede enviar tokens de ID correctamente, sigue estos pasos:

  1. Sigue estos pasos para que las credenciales de la cuenta de servicio predeterminada de App Engine estén disponibles en tu entorno local (las APIs de Google OAuth requieren estas credenciales para generar un token de ID):

    1. Introduce el siguiente comando de gcloud para recuperar la clave de la cuenta de servicio de la cuenta predeterminada de App Engine de tu proyecto:

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

      Sustituye project-ID por el ID de tu Google Cloud proyecto.

      El archivo de clave de cuenta de servicio se ha descargado en tu máquina. Puedes mover y cambiar el nombre de este archivo como quieras. Asegúrate de almacenar este archivo de forma segura, ya que se puede usar para autenticarte como tu cuenta de servicio. Si pierdes el archivo o si este se expone a usuarios no autorizados, elimina la clave de la cuenta de servicio y crea una nueva.

    2. Introduce el siguiente comando:

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

    Sustituye service-account-key por el nombre de ruta absoluto del archivo que contiene la clave de cuenta de servicio que has descargado.

  2. En el mismo shell en el que has exportado la variable de entorno GOOGLE_APPLICATION_CREDENTIALS, inicia tu aplicación Python.

  3. Envía una solicitud desde la aplicación y confirma que se ha completado correctamente. Si aún no tienes una aplicación que pueda recibir solicitudes y usar tokens de ID para verificar identidades, sigue estos pasos:

    1. Descarga la aplicación de ejemplo "incoming".
    2. En el archivo main.py de la muestra, añade el ID de tu Google Cloud proyecto a allowed_app_ids. Por ejemplo:

       allowed_app_ids = [
          '<APP_ID_1>',
          '<APP_ID_2>',
          'my-project-id'
        ]
      
    3. Ejecuta la muestra actualizada en el servidor de desarrollo local de Python 2.

Verificar y procesar solicitudes

Para actualizar tus aplicaciones de Python 2 de forma que usen identidades de tokens de ID o de la API App Identity antes de procesar las solicitudes, sigue estos pasos:

  1. Instala la biblioteca de cliente google-auth.

  2. Actualice su código para hacer lo siguiente:

    1. Si la solicitud contiene el encabezado X-Appengine-Inbound-Appid, úsalo para verificar la identidad. Las aplicaciones que se ejecutan en un tiempo de ejecución antiguo, como Python 2, incluirán este encabezado.

    2. Si la solicitud no contiene el encabezado X-Appengine-Inbound-Appid, comprueba si hay un token de ID de OIDC. Si el token existe, verifica la carga útil del token y comprueba la identidad del remitente.

  3. Prueba las actualizaciones.

Instalar la biblioteca de cliente google-auth para aplicaciones de Python 2

Para que la biblioteca de cliente google-auth esté disponible en tu aplicación Python 2, haz lo siguiente:

  1. Crea un archivo requirements.txt en la misma carpeta que tu archivo app.yaml y añade la siguiente línea:

     google-auth==1.19.2
    

    Te recomendamos que utilices la versión 1.19.2 de la biblioteca de cliente de Cloud Logging, ya que es compatible con las aplicaciones de Python 2.7.

  2. En el archivo app.yaml de tu aplicación, especifica la biblioteca SSL en la sección libraries si aún no lo has hecho:

    libraries:
    - name: ssl
      version: latest
    
  3. Crea un directorio para almacenar tus bibliotecas de terceros, como lib/. A continuación, usa pip install para instalar las bibliotecas en el directorio. Por ejemplo:

    pip install -t lib -r requirements.txt
  4. Crea un archivo appengine_config.py en la misma carpeta que el archivo app.yaml. Añade lo siguiente a tu archivo 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)

    En el archivo appengine_config.py del ejemplo anterior, se presupone que la carpeta lib se encuentra en el directorio de trabajo actual. Si no puedes garantizar que lib siempre esté en el directorio de trabajo actual, especifica la ruta completa a la carpeta lib. Por ejemplo:

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

Para el desarrollo local, te recomendamos que instales las dependencias en un entorno virtual, como virtualenv para Python 2.

Actualización del código para verificar solicitudes

Busca en tu código y encuentra todas las instancias en las que se obtiene el valor del encabezado X-Appengine-Inbound-Appid. Actualiza esas instancias para que hagan lo siguiente:

  1. Añade las siguientes importaciones:

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. Si la solicitud entrante no contiene el encabezado X-Appengine-Inbound-Appid, busca el encabezado Authorization y recupera su valor.

    El valor del encabezado tiene el formato "ID: token".

  3. Usa google.oauth2.id_token.verify_oauth2_token(token, request, audience) para verificar y obtener la carga útil del token decodificado. Incluye los siguientes parámetros en la llamada al método:

    • token: pasa el token que has extraído de la solicitud entrante.
    • request: pasa un nuevo objeto google.auth.transport.Request.

    • audience: transfiere la URL de la aplicación actual (la aplicación que envía la solicitud de verificación). El servidor de autorización de Google comparará esta URL con la que se proporcionó cuando se generó el token. Si las URLs no coinciden, el token no se verificará y el servidor de autorización devolverá un error.

  4. El método verify_oauth2_token devuelve la carga útil del token decodificado, que contiene varios pares de nombre/valor, incluida la dirección de correo de la cuenta de servicio predeterminada de la aplicación que ha generado el token.

  5. Extrae el nombre de usuario de la dirección de correo del payload del token.

    El nombre de usuario es el mismo que el ID del proyecto de la aplicación que ha enviado la solicitud. Este es el mismo valor que se devolvió anteriormente en el encabezado X-Appengine-Inbound-Appid.

  6. Si el nombre de usuario o el ID de proyecto están en la lista de IDs de proyecto permitidos, procesa la solicitud.

Por ejemplo:

# 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.
"""

import logging

from google.auth.transport import requests
from google.oauth2 import id_token
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)

Novedades de las pruebas para verificar la identidad

Para comprobar que tu aplicación puede usar un token de ID o el encabezado X-Appengine-Inbound-Appid para verificar las solicitudes, ejecuta la aplicación en el servidor de desarrollo local de Python 2 y envía solicitudes desde aplicaciones de Python 2 (que usarán la API App Identity) y desde aplicaciones de Python 3 que envíen tokens de ID.

Si no has actualizado tus aplicaciones para que envíen tokens de ID:

  1. Descarga la aplicación de ejemplo "requesting".

  2. Añade las credenciales de la cuenta de servicio a tu entorno local, tal como se describe en Probar actualizaciones para afirmar aplicaciones.

  3. Usa los comandos estándar de Python 3 para iniciar la aplicación de ejemplo de Python 3.

  4. Envía una solicitud desde la aplicación de ejemplo y confirma que se realiza correctamente.

Desplegar tus aplicaciones

Cuando tengas todo listo para desplegar tus aplicaciones, debes hacer lo siguiente:

  1. Prueba las aplicaciones en App Engine.

  2. Si las aplicaciones se ejecutan sin errores, usa la división del tráfico para aumentar lentamente el tráfico de tus aplicaciones actualizadas. Monitoriza las aplicaciones de cerca para detectar cualquier problema antes de dirigir más tráfico a las aplicaciones actualizadas.

Usar otra cuenta de servicio para afirmar la identidad

Cuando solicitas un token de ID, la solicitud usa de forma predeterminada la identidad de la cuenta de servicio predeterminada de App Engine. Cuando verifiques el token, la carga útil del token contendrá la dirección de correo del servicio predeterminado, que se corresponde con el ID de proyecto de tu aplicación.

La cuenta de servicio predeterminada de App Engine tiene un nivel de permisos muy alto de forma predeterminada. Puede ver y editar todo tu proyecto deGoogle Cloud , por lo que, en la mayoría de los casos, no es adecuado usar esta cuenta cuando tu aplicación necesite autenticarse con servicios de Cloud.

Sin embargo, la cuenta de servicio predeterminada se puede usar de forma segura al afirmar la identidad de la aplicación, ya que solo se usa el token de ID para verificar la identidad de la aplicación que ha enviado una solicitud. Los permisos que se han concedido a la cuenta de servicio no se tienen en cuenta ni son necesarios durante este proceso.

Si prefieres usar otra cuenta de servicio para tus solicitudes de token de ID, haz lo siguiente:

  1. Define una variable de entorno llamada GOOGLE_APPLICATION_CREDENTIALS con la ruta de un archivo JSON que contenga las credenciales de la cuenta de servicio. Consulta nuestras recomendaciones para almacenar estas credenciales de forma segura.

  2. Usa google.oauth2.id_token.fetch_id_token(request, audience) para obtener un token de ID.

  3. Cuando verifiques este token, la carga útil del token contendrá la dirección de correo electrónico de la nueva cuenta de servicio.