Migrazione dell'identità delle app ai token ID OIDC

Quando un'app in esecuzione nel runtime Python 2 invia una richiesta a un'altra app App Engine, può utilizzare l'API App Identity di App Engine per rivendicarne l'identità. L'app che riceve la richiesta può utilizzare questa identità per determinare se deve elaborare la richiesta.

Se le tue app Python 3 devono dichiarare la loro identità quando inviano richieste ad altre app di App Engine, puoi utilizzare i token ID OpenID Connect (OIDC) emessi e decodificati dalle API OAuth 2.0 di Google.

Ecco una panoramica dell'utilizzo dei token ID OIDC per rivendicare e verificare l'identità:

  1. Un'app App Engine denominata "App A" recupera un token ID dall'ambiente di runtime Google Cloud.
  2. L'app A aggiunge questo token all'intestazione di una richiesta appena prima di inviare la richiesta all'app B, che è un'altra app App Engine.
  3. L'app B utilizza le API OAuth 2.0 di Google per verificare il payload dei token. Il payload decodificato contiene l'identità verificata dell'app A sotto forma di indirizzo email dell'account di servizio predefinito dell'app A.
  4. L'app B confronta l'identità nel payload con un elenco di identità a cui è consentito rispondere. Se la richiesta proviene da un'app consentita, l'app B la elabora e risponde.

Processo OAuth 2.0

Questa guida descrive come aggiornare le app di App Engine per l'utilizzo dei token ID OpenID Connect (OIDC) per rivendicare l'identità e aggiornare le altre app di App Engine per utilizzare i token ID per verificare l'identità prima di elaborare una richiesta.

Differenze chiave tra le API App Identity e OIDC

  • Non è necessario dichiarare esplicitamente l'identità delle app nel runtime di Python 2. Quando un'app utilizza le librerie Python httplib, urllib o urllib2 o il servizio di recupero URL di App Engine per inviare richieste in uscita, il runtime utilizza il servizio di recupero URL di App Engine per effettuare la richiesta. Se la richiesta viene inviata al dominio appspot.com, il recupero URL dichiara automaticamente l'identità dell'app richiedente aggiungendo l'intestazione X-Appengine-Inbound-Appid alla richiesta. L'intestazione contiene l'ID applicazione dell'app (chiamato anche ID progetto).

    Le app nel runtime Python 3 devono rivendicare esplicitamente l'identità recuperando un token ID OIDC dall'ambiente di runtime Google Cloud e aggiungendolo all'intestazione della richiesta. Dovrai aggiornare tutto il codice che invia richieste ad altre app di App Engine in modo che contengano un token ID OIDC.

  • L'intestazione X-Appengine-Inbound-Appid in una richiesta contiene l'ID progetto dell'app che ha inviato la richiesta.

    Il payload del token ID OIDC di Google non identifica direttamente l'ID progetto dell'app stessa. Il token identifica invece l'account di servizio su cui è in esecuzione l'app, fornendo l'indirizzo email dell'account di servizio. Per aggiungere il nome utente dal payload del token, devi aggiungere del codice.

    Se l'account di servizio è l'account di servizio predefinito di App Engine per il progetto, l'ID progetto è disponibile nell'indirizzo email dell'account di servizio. La parte del nome utente dell'indirizzo corrisponde all'ID progetto. In questo caso, il codice dell'app di destinazione può eseguire una ricerca nell'elenco degli ID progetto da cui consentirà le richieste.

    Tuttavia, se l'app richiedente utilizza un account di servizio gestito dall'utente anziché l'account di servizio App Engine predefinito, l'app ricevente può verificare solo l'identità dell'account di servizio, che non definirà necessariamente l'ID progetto dell'app richiedente. In questo caso, l'app ricevente dovrà mantenere un elenco di email degli account di servizio consentite anziché un elenco di ID progetto consentiti.

  • Le quote per le chiamate API di recupero URL sono diverse dalle quote delle API OAuth 2.0 di Google per la concessione di token. Puoi visualizzare il numero massimo di token che puoi concedere al giorno nella schermata di consenso OAuth della console Google Cloud. Né il recupero URL, l'API App Identity, né le API OAuth 2.0 di Google sono soggette alla fatturazione.

Panoramica del processo di migrazione

Per eseguire la migrazione delle app Python per utilizzare le API OIDC per rivendicare e verificare l'identità:

  1. Nelle app che devono dichiarare l'identità quando si inviano richieste ad altre applicazioni di App Engine:

    1. Attendi che l'app sia in esecuzione in un ambiente Python 3 per eseguire la migrazione ai token ID.

      Sebbene sia possibile utilizzare i token ID nel runtime Python 2, i passaggi in Python 2 sono complessi e sono necessari solo temporaneamente finché non aggiorni l'app per eseguirlo nel runtime Python 3.

    2. Una volta eseguita l'app in Python 3, aggiorna l'app per richiedere un token ID e aggiungerlo a un'intestazione della richiesta.

  2. Nelle app che devono verificare l'identità prima di elaborare una richiesta:

    1. Per iniziare, esegui l'upgrade delle tue app Python 2 per supportare sia i token ID che le identità dell'API App Identity. Questo consentirà alle tue app di verificare ed elaborare le richieste da app Python 2 che utilizzano l'API App Identity o app Python 3 che utilizzano token ID.

    2. Una volta che le app Python 2 aggiornate sono stabili, eseguine la migrazione al runtime di Python 3. Continua a supportare sia i token ID sia le identità dell'API App Identity finché non hai la certezza che le app non debbano più supportare le richieste dalle app legacy.

    3. Quando non hai più bisogno di elaborare richieste da app legacy di App Engine, rimuovi il codice che verifica le identità dell'API App Identity.

  3. Dopo aver testato le app, esegui il deployment dell'app che elabora le richieste. Quindi esegui il deployment dell'app Python 3 aggiornata che utilizza i token ID per dichiarare l'identità.

Rivendicazione dell'identità

Attendi finché l'app non è in esecuzione in un ambiente Python 3, quindi segui questi passaggi per eseguire l'upgrade dell'app in modo da rivendicare l'identità con i token ID:

  1. Installa la libreria client google-auth.

  2. Aggiungi codice per richiedere un token ID dalle API OAuth 2.0 di Google e aggiungere il token a un'intestazione della richiesta prima di inviare una richiesta.

  3. Testa gli aggiornamenti.

Installazione della libreria client google-auth per le app Python 3

Per rendere disponibile la libreria client google-auth per l'app Python3, crea un file requirements.txt nella stessa cartella in cui si trova il file app.yaml e aggiungi la seguente riga:

     google-auth

Quando esegui il deployment della tua applicazione, App Engine scaricherà tutte le dipendenze definite nel file requirements.txt.

Per lo sviluppo locale, ti consigliamo di installare le dipendenze in un ambiente virtuale, ad esempio venv.

Aggiunta del codice per rivendicare l'identità

Esegui ricerche nel codice e trova tutte le istanze dell'invio di richieste ad altre app di App Engine. Prima di inviare la richiesta, aggiorna le istanze come indicato di seguito:

  1. Aggiungi le seguenti importazioni:

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. Utilizza google.oauth2.id_token.fetch_id_token(request, audience) per recuperare un token ID. Includi i seguenti parametri nella chiamata al metodo:

    • request: trasmetti l'oggetto della richiesta che stai per inviare.
    • audience: trasmetti l'URL dell'app a cui stai inviando la richiesta. Questo codice associa il token alla richiesta e impedisce che venga utilizzato da un'altra app.

      Per chiarezza e specificità, ti consigliamo di passare l'URL appspot.com creato da App Engine per il servizio specifico che riceve la richiesta, anche se utilizzi un dominio personalizzato per l'app.

  3. Nell'oggetto di richiesta, imposta la seguente intestazione:

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

Ad esempio:

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

Test degli aggiornamenti per dichiarare l'identità

Per eseguire l'app localmente e verificare che l'app possa inviare correttamente i token ID:

  1. Segui queste istruzioni per rendere disponibili le credenziali dell'account di servizio predefinito App Engine nel tuo ambiente locale (le API OAuth di Google richiedono queste credenziali per generare un token ID):

    1. Inserisci il seguente comando gcloud per recuperare la chiave dell'account di servizio per l'account App Engine predefinito del tuo progetto:

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

      Sostituisci project-ID con l'ID del tuo progetto Google Cloud.

      Il file della chiave dell'account di servizio viene scaricato sulla macchina. Puoi spostare e rinominare il file come preferisci. Assicurati di archiviare questo file in modo sicuro perché può essere utilizzato per l'autenticazione come account di servizio. Se perdi il file o se il file è esposto a utenti non autorizzati, elimina la chiave dell'account di servizio e creane una nuova.

    2. Inserisci questo comando:

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

    Sostituisci service-account-key con il nome del percorso assoluto del file contenente la chiave dell'account di servizio scaricata.

  2. Nella stessa shell in cui hai esportato la variabile di ambiente GOOGLE_APPLICATION_CREDENTIALS, avvia l'app Python.

  3. Invia una richiesta dall'app e verifica che l'operazione vada a buon fine. Se non hai ancora un'app in grado di ricevere richieste e utilizzare i token ID per verificare le identità:

    1. Scarica l'app di esempio "in arrivo".
    2. Nel file main.py di esempio, aggiungi l'ID del tuo progetto Google Cloud a allowed_app_ids. Ad esempio:

       allowed_app_ids = [
          '<APP_ID_1>',
          '<APP_ID_2>',
          'my-project-id'
        ]
      
    3. Eseguire l'esempio aggiornato nel server di sviluppo locale Python 2.

Verifica ed elaborazione delle richieste

Per eseguire l'upgrade delle app Python 2 per utilizzare i token ID o le identità dell'API App Identity, prima di elaborare le richieste:

  1. Installa la libreria client di google-auth.

  2. Aggiorna il codice per:

    1. Se la richiesta contiene l'intestazione X-Appengine-Inbound-Appid, utilizzala per verificare l'identità. Le app in esecuzione in un runtime precedente, come Python 2, contengono questa intestazione.

    2. Se la richiesta non contiene l'intestazione X-Appengine-Inbound-Appid, verifica se è presente un token ID OIDC. Se il token esiste, verifica il payload del token e controlla l'identità del mittente.

  3. Testa gli aggiornamenti.

Installazione della libreria client google-auth per le app Python 2

Per rendere disponibile la libreria client di google-auth per la tua app Python 2:

  1. Crea un file requirements.txt nella stessa cartella in cui si trova il file app.yaml e aggiungi la seguente riga:

     google-auth==1.19.2
    

    Ti consigliamo di utilizzare la versione 1.19.2 della libreria client di Cloud Logging, poiché supporta le app Python 2.7.

  2. Nel file app.yaml della tua app, specifica la libreria SSL nella sezione libraries se non è già specificata:

    libraries:
    - name: ssl
      version: latest
    
  3. Crea una directory per archiviare le tue librerie di terze parti, ad esempio lib/. quindi usa pip install per installare le librerie nella directory. Ad esempio:

    pip install -t lib -r requirements.txt
    
  4. Crea un file appengine_config.py nella stessa cartella in cui si trova il file app.yaml. Aggiungi quanto segue al tuo file 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)
    

    Il file appengine_config.py nell'esempio precedente presuppone che la cartella lib si trovi nella directory di lavoro corrente. Se non puoi garantire che lib sarà sempre nella directory di lavoro corrente, specifica il percorso completo della cartella lib. Ad esempio:

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

Per lo sviluppo locale, ti consigliamo di installare le dipendenze in un ambiente virtuale, ad esempio virtualenv per Python 2.

Aggiornamento del codice per la verifica delle richieste

Esegui una ricerca nel codice e trova tutte le istanze per ottenere il valore dell'intestazione X-Appengine-Inbound-Appid. Aggiorna queste istanze per effettuare le seguenti operazioni:

  1. Aggiungi le seguenti importazioni:

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. Se la richiesta in entrata non contiene l'intestazione X-Appengine-Inbound-Appid, cerca l'intestazione Authorization e recupera il relativo valore.

    Il formato dell'intestazione è "ID: token".

  3. Utilizza google.oauth2.id_token.verify_oauth2_token(token, request, audience) per verificare e recuperare il payload dei token decodificati. Includi i seguenti parametri nella chiamata al metodo:

    • token: trasmetti il token estratto dalla richiesta in entrata.
    • request: trasmetti un nuovo oggetto google.auth.transport.Request.

    • audience: trasmetti l'URL dell'app corrente (l'app che invia la richiesta di verifica). Il server di autorizzazione di Google confronterà questo URL con l'URL fornito al momento della generazione originale del token. Se gli URL non corrispondono, il token non verrà verificato e il server di autorizzazione restituirà un errore.

  4. Il metodo verify_oauth2_token restituisce il payload del token decodificato, che contiene diverse coppie nome/valore, incluso l'indirizzo email dell'account di servizio predefinito per l'app che ha generato il token.

  5. Estrai il nome utente dall'indirizzo email nel payload del token.

    Il nome utente è lo stesso dell'ID progetto dell'app che ha inviato la richiesta. Si tratta dello stesso valore restituito in precedenza nell'intestazione X-Appengine-Inbound-Appid.

  6. Se l'ID nome utente/progetto è nell'elenco degli ID progetto consentiti, elabora la richiesta.

Ad esempio:

# 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)

Testare gli aggiornamenti per la verifica dell'identità

Per verificare che la tua app possa utilizzare un token ID o l'intestazione X-Appengine-Inbound-Appid per verificare le richieste, esegui l'app nel server di sviluppo locale Python 2 e invia le richieste dalle app Python 2 (che utilizzeranno l'API App Identity) e dalle app Python 3 che inviano i token ID.

Se non hai aggiornato le tue app per inviare i token ID:

  1. Scarica l'app di esempio "richiesta".

  2. Aggiungi le credenziali dell'account di servizio all'ambiente locale come descritto in Testare gli aggiornamenti per la rivendicazione delle app.

  3. Utilizza i comandi Python 3 standard per avviare l'app di esempio Python 3.

  4. Invia una richiesta dall'app di esempio e conferma che l'operazione abbia esito positivo.

Deployment delle app

Quando è tutto pronto per eseguire il deployment delle tue app:

  1. Testare le app su App Engine.

  2. Se le app vengono eseguite senza errori, utilizza la suddivisione del traffico per aumentare lentamente il traffico verso le app aggiornate. Prima di indirizzare più traffico alle app aggiornate, monitora attentamente le app.

Utilizzare un account di servizio diverso per rivendicare l'identità

Quando richiedi un token ID, la richiesta utilizza l'identità dell'account di servizio predefinito di App Engine. Quando verifichi il token, il payload del token contiene l'indirizzo email dell'account di servizio predefinito, che è mappato all'ID progetto della tua app.

L'account di servizio predefinito di App Engine dispone di un livello di autorizzazione molto elevato per impostazione predefinita. Può visualizzare e modificare l'intero progetto Google Cloud, quindi nella maggior parte dei casi questo account non è appropriato da usare quando l'app deve eseguire l'autenticazione con i servizi Cloud.

Tuttavia, l'account di servizio predefinito è sicuro da utilizzare quando si rivendica l'identità dell'app perché stai utilizzando il token ID solo per verificare l'identità dell'app che ha inviato una richiesta. Durante questo processo, non vengono considerate né necessarie le autorizzazioni concesse all'account di servizio.

Se preferisci comunque utilizzare un account di servizio diverso per le richieste del token ID, procedi nel seguente modo:

  1. Imposta una variabile di ambiente denominata GOOGLE_APPLICATION_CREDENTIALS sul percorso di un file JSON contenente le credenziali dell'account di servizio. Scopri i nostri consigli per archiviare in modo sicuro queste credenziali.

  2. Utilizza google.oauth2.id_token.fetch_id_token(request, audience) per recuperare un token ID.

  3. Quando verifichi questo token, il payload del token conterrà l'indirizzo email del nuovo account di servizio.