Migrar App Identity a los tokens de ID de OIDC

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

Si tus apps de Python 3 necesitan confirmar su identidad cuando envían solicitudes a otras apps de App Engine, puedes usar tokens de ID de OpenID Connect (OIDC) que sean emitidos y decodificados por las API de OAuth 2.0 de Google.

A continuación, se muestra una descripción general del uso de tokens de ID de OIDC para confirmar y verificar la identidad:

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

Proceso de OAuth 2.0

En esta guía, se describe cómo actualizar las apps de App Engine a fin de usar tokens de ID de OpenID Connect (OIDC) para confirmar la identidad y cómo actualizar las otras apps de App Engine con el fin de usar tokens de ID destinados a verificar la identidad antes de procesar una solicitud.

Diferencias fundamentales entre las API de App Identity y OIDC

  • Las apps en el entorno de ejecución de Python 2 no necesitan confirmar su identidad de forma explícita. Cuando una app usa las bibliotecas de Python httplib, urllib o urllib2, o el servicio de recuperación de URL de App Engine a fin de enviar solicitudes salientes, el entorno de ejecución usa ese servicio para realizar la solicitud. Si la solicitud se envía al dominio appspot.com, la recuperación de URL confirma de forma automática la identidad de la app solicitante mediante la adición del encabezado X-Appengine-Inbound-Appid a la solicitud. Ese encabezado contiene el ID de aplicación de la app (también llamado ID del proyecto).

    Las apps en el entorno de ejecución de Python 3 deben confirmar su identidad de forma explícita mediante la recuperación de un token de ID de OIDC del entorno de ejecución de Google Cloud y su adición al encabezado de la solicitud. Deberás actualizar todo el código que envía solicitudes a otras apps de App Engine para que las solicitudes contengan un token de ID de OIDC.

  • El encabezado X-Appengine-Inbound-Appid en una solicitud contiene el ID del proyecto de la app que envió la solicitud.

    La carga útil del token de ID de OIDC de Google no identifica directamente el ID del proyecto de la app. En su lugar, el token identifica la cuenta de servicio en la que se ejecuta la app, ya que proporciona la dirección de correo electrónico de esa cuenta de servicio. Deberás agregar 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 app para el proyecto, el ID del proyecto se puede encontrar en la dirección de correo electrónico 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 app receptora puede buscar esto en la lista de ID de proyectos desde los que permitirá solicitudes.

    Sin embargo, si la app que realiza la solicitud usa una cuenta de servicio administrada por el usuario en lugar de la cuenta de servicio predeterminada de App Engine, la app que recibe la solicitud solo puede verificar la identidad de esa cuenta de servicio, lo que no necesariamente definirá el ID de proyecto de la app que realiza la solicitud. En ese caso, la app que recibe la solicitud deberá mantener una lista de correos electrónicos de cuentas de servicio permitidos en lugar de una lista de ID de proyecto permitidos.

  • Las cuotas para las llamadas a la API de recuperación de URL son diferentes de las cuotas de las API de OAuth 2.0 de Google para otorgar tokens. Puedes ver la cantidad máxima de tokens que puedes otorgar por día en la pantalla de consentimiento de OAuth en la consola de Google Cloud. La recuperación de URL, la API de App Identity y las API de OAuth 2.0 de Google no generan cargos de facturación.

Descripción general del proceso de migración

Si deseas migrar las apps de Python para usar las API de OIDC a fin de confirmar y verificar la identidad, haz lo siguiente:

  1. En el caso de las apps que necesitan confirmar su identidad cuando envían solicitudes a otras apps de App Engine, sigue estos pasos:

    1. Espera hasta que la app se ejecute en un entorno de Python 3 para migrar a los tokens de ID.

      Si bien es posible usar tokens de ID en el entorno de ejecución de Python 2, los pasos en Python 2 son complejos y solo se necesitan de forma temporal hasta que actualices tu app para que se ejecute en el entorno de ejecución de Python 3.

    2. Una vez que tu app se ejecute en Python 3, actualízala para solicitar un token de ID y agregar el token a un encabezado de solicitud.

  2. En el caso de las apps que deben verificar su identidad antes de procesar una solicitud, haz lo siguiente:

    1. Comienza por actualizar tus apps de Python 2 para admitir los tokens de ID y las identidades de la API de App Identity. Esto permitirá que tus apps verifiquen y procesen solicitudes desde apps de Python 2 que usan la API de App Identity o apps de Python 3 que usan tokens de ID.

    2. Una vez que las apps actualizadas de Python 2 sean estables, mígralas al entorno de ejecución de Python 3. Sigue admitiendo identidades de la API de App Identity y tokens de ID hasta que estés seguro de que las apps ya no necesitan admitir solicitudes de apps heredadas.

    3. Cuando ya no necesites procesar solicitudes de apps heredadas de App Engine, quita el código que verifica las identidades de la API de App Identity.

  3. Después de probar tus apps, implementa la app que procesa las solicitudes primero. Luego, implementa la app de Python 3 actualizada que usa tokens de ID para confirmar su identidad.

Confirma la identidad

Espera a que tu app se ejecute en un entorno de Python 3 y sigue estos pasos para actualizar la app a fin de que confirme su identidad con tokens de ID:

  1. Instala la biblioteca cliente google-auth.

  2. Agrega código para solicitar un token de ID de las API de OAuth 2.0 de Google y agrégalo a un encabezado de solicitud antes de enviar una solicitud.

  3. Prueba las actualizaciones.

Instala la biblioteca cliente google-auth para apps de Python 3

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

     google-auth

Cuando implementes la app, App Engine descargará todas las dependencias que se definen en el archivo requirements.txt.

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

Agrega código para confirmar la identidad

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

  1. Agrega 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 recuperar un token de ID. Incluye los siguientes parámetros en la llamada de método:

    • request: Pasa el objeto de solicitud que estás listo para enviar.
    • audience: Pasa la URL de la app a la que envías la solicitud. Esto vincula el token a la solicitud y evita que otra app lo use.

      Por motivos de claridad y especificidad, te recomendamos pasar la URL appspot.com que App Engine creó para el servicio específico que recibe la solicitud, incluso si usas un dominio personalizado en la app.

  3. En tu objeto de solicitud, establece 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"}

Prueba actualizaciones para confirmar la identidad

Para ejecutar tu app de forma local y probar si puede enviar tokens de ID de forma correcta, haz lo siguiente:

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

    1. Ingresa 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

      Reemplaza project-ID por el ID del proyecto de Google Cloud.

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

    2. Ingresa el siguiente comando:

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

    Reemplaza service-account-key por el nombre de ruta absoluto del archivo que contiene la clave de la cuenta de servicio que descargaste.

  2. En la misma shell en la que exportaste la variable de entorno GOOGLE_APPLICATION_CREDENTIALS, inicia la app de Python.

  3. Envía una solicitud desde la app y confirma que se completó con de forma correcta. Si aún no tienes una app que pueda recibir solicitudes y usar tokens de ID para verificar las identidades, haz lo siguiente:

    1. Descarga la app de muestra “de entrada”.
    2. En el archivo main.py de la muestra, agrega el ID del proyecto de Google Cloud 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.

Verifica y procesa solicitudes

Si deseas actualizar las apps de Python 2 para que usen tokens de ID o identidades de la API de App Identity antes de procesar las solicitudes, haz lo siguiente:

  1. Instala la biblioteca cliente google-auth.

  2. Actualiza tu código para hacer lo siguiente:

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

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

  3. Prueba las actualizaciones.

Instala la biblioteca cliente google-auth para las apps de Python 2

A fin de que la biblioteca cliente google-auth esté disponible para tu app de Python 2, sigue estos pasos:

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

     google-auth==1.19.2
    

    Te recomendamos usar la versión 1.19.2 de la biblioteca cliente de Cloud Logging, ya que admite apps de Python 2.7.

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

    libraries:
    - name: ssl
      version: latest
    
  3. Crea un directorio para almacenar tus bibliotecas de terceros, como lib/. Luego, 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. Agrega lo siguiente al 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 supone que la carpeta lib se encuentra en el directorio de trabajo actual. Si no puedes garantizar que lib esté siempre 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, recomendamos que instales dependencias en un entorno virtual como virtualenv para Python 2.

Actualiza el código para verificar solicitudes

Busca en tu código y encuentra todas las instancias de obtención del valor del encabezado X-Appengine-Inbound-Appid. Actualiza esas instancias para realizar las siguientes acciones:

  1. Agrega 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 recuperar la carga útil del token decodificado. Incluye los siguientes parámetros en la llamada de método:

    • token: Pasa el token que extrajiste de la solicitud entrante.
    • request: Pasa un objeto google.auth.transport.Request nuevo.

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

  4. El método verify_oauth2_token muestra la carga útil del token decodificado, que contiene varios pares de nombre/valor, incluida la dirección de correo electrónico de la cuenta de servicio predeterminada de la app que generó el token.

  5. Extrae el nombre de usuario de la dirección de correo electrónico en la carga útil del token.

    El nombre de usuario es el mismo que el ID del proyecto de esa app que envió la solicitud. Este es el mismo valor que se mostró antes en el encabezado X-Appengine-Inbound-Appid.

  6. Si el nombre de usuario o ID del proyecto está en la lista de los ID del 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.
"""

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)

Prueba actualizaciones para verificar la identidad

Si deseas probar que tu app pueda usar un token de ID o el encabezado X-Appengine-Inbound-Appid para verificar las solicitudes, ejecuta la app en el servidor de desarrollo local de Python 2 y envía solicitudes desde apps de Python 2 (que usarán la API de App Identity) y desde apps de Python 3 que envían tokens de ID.

Si no actualizaste tus apps para enviar tokens de ID, haz lo siguiente:

  1. Descarga la app de muestra “de solicitud”.

  2. Agrega credenciales de cuenta de servicio a tu entorno local como se describe en Prueba actualizaciones para confirmar apps.

  3. Usa comandos estándar de Python 3 para iniciar la app de muestra de Python 3.

  4. Envía una solicitud desde la app de muestra y confirma que se ejecute de forma correcta.

Implementa tus apps

Cuando estés listo para implementar las apps, debes hacer lo siguiente:

  1. Prueba las apps en App Engine.

  2. Si las apps se ejecutan sin errores, usa la división del tráfico para aumentar con lentitud el tráfico de las apps actualizadas. Supervisa las apps con atención para detectar cualquier problema antes de enrutar más tráfico a las apps actualizadas.

Usa una cuenta de servicio diferente para confirmar la identidad

Cuando solicitas un token de ID, en la solicitud se usa la identidad de la cuenta de servicio predeterminada de App Engine según la configuración predeterminada. Cuando verifiques el token, la carga útil del token contendrá la dirección de correo electrónico de la cuenta de servicio predeterminada, que se mapea al ID del proyecto de tu app.

La cuenta de servicio predeterminada de App Engine tiene un nivel de permiso muy alto de forma predeterminada. Puede ver y editar todo el proyecto de Google Cloud, por lo que, en la mayoría de los casos, esta cuenta no será la adecuada cuando tu app necesite autenticarse con los servicios de Cloud.

Sin embargo, la cuenta de servicio predeterminada es segura cuando se confirma la identidad de una app porque solo usas el token de ID para verificar la identidad de la app que envió una solicitud. Los permisos reales que se otorgaron a la cuenta de servicio no se consideran ni se necesitan durante este proceso.

Si prefieres usar una cuenta de servicio diferente para las solicitudes de token de ID, haz lo siguiente:

  1. Configura una variable de entorno llamada GOOGLE_APPLICATION_CREDENTIALS para la ruta de acceso a un archivo JSON que contiene 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 recuperar un token de ID.

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