Migrar a identidade da app para tokens de ID OIDC

Quando uma app em execução no tempo de execução do Python 2 envia um pedido a outra app do App Engine, pode usar a API App Identity do App Engine para afirmar a sua identidade. A app que recebe o pedido pode usar esta identidade para determinar se deve processar o pedido.

Se as suas apps Python 3 precisarem de afirmar a respetiva identidade quando enviam pedidos a outras apps do App Engine, pode usar tokens de ID do OpenID Connect (OIDC) que são emitidos e descodificados pelas APIs OAuth 2.0 da Google.

Segue-se uma vista geral da utilização de tokens de ID OIDC para afirmar e validar a identidade:

  1. Uma app do App Engine denominada "App A" obtém um token de ID do Google Cloud ambiente de execução.
  2. A app A adiciona este token a um cabeçalho de pedido imediatamente antes de enviar o pedido à app B, que é outra app do App Engine.
  3. A app B usa as APIs OAuth 2.0 da Google para validar a carga útil do token. A carga útil descodificada contém a identidade validada da app A sob a forma do endereço de email da conta de serviço predefinida da app A.
  4. A app B compara a identidade na carga útil com uma lista de identidades às quais tem autorização para responder. Se o pedido for proveniente de uma app permitida, a app B processa o pedido e responde.

Processo OAuth 2.0

Este guia descreve como atualizar as suas apps do App Engine para usar tokens de ID do OpenID Connect (OIDC) para afirmar a identidade e atualizar as suas outras apps do App Engine para usar tokens de ID para validar a identidade antes de processar um pedido.

Principais diferenças entre as APIs App Identity e OIDC

  • As apps no tempo de execução do Python 2 não precisam de afirmar explicitamente a identidade. Quando uma app usa as bibliotecas Python httplib, urllib ou urllib2, ou o serviço de obtenção de URL do App Engine para enviar pedidos de saída, o tempo de execução usa o serviço de obtenção de URL do App Engine para fazer o pedido. Se o pedido estiver a ser enviado para o domínio appspot.com, o URL Fetch afirma automaticamente a identidade da app que está a fazer o pedido adicionando o cabeçalho X-Appengine-Inbound-Appid ao pedido. Esse cabeçalho contém o ID da aplicação (também denominado ID do projeto).

    As apps no tempo de execução do Python 3 têm de afirmar explicitamente a identidade através da obtenção de um token de ID OIDC do ambiente de tempo de execução e da respetiva adição ao cabeçalho do pedido. Google Cloud Tem de atualizar todo o código que envia pedidos a outras apps do App Engine para que os pedidos contenham um token de ID da OIDC.

  • O cabeçalho X-Appengine-Inbound-Appid num pedido contém o ID do projeto da app que enviou o pedido.

    A carga útil do token de ID OIDC da Google não identifica diretamente o ID do projeto da própria app. Em alternativa, o token identifica a conta de serviço sob a qual a app está a ser executada, fornecendo o endereço de email dessa conta de serviço. Tem de adicionar algum código para extrair o nome de utilizador da carga útil do token.

    Se essa conta de serviço for a conta de serviço do App Engine predefinida ao nível da app para o projeto, o ID do projeto pode ser encontrado no endereço de email da conta de serviço. A parte do nome de utilizador do endereço é igual ao ID do projeto. Neste caso, o código da app de receção pode procurar o ID do projeto na lista de IDs de projetos a partir dos quais vai permitir pedidos.

    No entanto, se a app que envia o pedido estiver a usar uma conta de serviço gerida pelo utilizador em vez da conta de serviço predefinida do App Engine, a app que recebe o pedido só pode validar a identidade dessa conta de serviço, que não define necessariamente o ID do projeto da app que envia o pedido. Nesse caso, a app de receção tem de manter uma lista de emails de contas de serviço permitidos em vez de uma lista de IDs de projetos permitidos.

  • As quotas para chamadas da API URL Fetch são diferentes das quotas das APIs OAuth 2.0 da Google para conceder tokens. Pode ver o número máximo de tokens que pode conceder por dia no Google Cloud ecrã de consentimento OAuth da consola. Nem a obtenção de URLs, a API App Identity nem as APIs OAuth 2.0 da Google incorrem em faturação.

Vista geral do processo de migração

Para migrar as suas apps Python para usar APIs OIDC para afirmar e validar a identidade:

  1. Em apps que precisam de afirmar a identidade quando enviam pedidos a outras apps do App Engine:

    1. Aguarde até que a sua app esteja a ser executada num ambiente Python 3 para migrar para tokens de ID.

      Embora seja possível usar tokens de ID no tempo de execução do Python 2, os passos no Python 2 são complexos e só são necessários temporariamente até atualizar a sua app para ser executada no tempo de execução do Python 3.

    2. Assim que a sua app estiver a ser executada no Python 3, atualize a app para pedir um token de ID e adicione o token a um cabeçalho de pedido.

  2. Em apps que precisam de validar a identidade antes de processar um pedido:

    1. Comece por atualizar as suas apps Python 2 para suportarem tokens de ID e as identidades da API App Identity. Isto permite que as suas apps validem e processem pedidos de apps Python 2 que usam a API App Identity ou apps Python 3 que usam tokens de ID.

    2. Quando as suas apps Python 2 atualizadas estiverem estáveis, migre-as para o tempo de execução do Python 3. Continue a suportar os tokens de ID e as identidades da API App Identity até ter a certeza de que as apps já não precisam de suportar pedidos de apps antigas.

    3. Quando já não precisar de processar pedidos de apps do App Engine antigas, remova o código que valida as identidades da API App Identity.

  3. Depois de testar as suas apps, implemente primeiro a app que processa pedidos. Em seguida, implemente a sua app Python 3 atualizada que usa tokens de ID para afirmar a identidade.

Afirmar identidade

Aguarde até que a sua app esteja a ser executada num ambiente Python 3 e, em seguida, siga estes passos para atualizar a app de modo a validar a identidade com tokens de ID:

  1. Instale a biblioteca cliente google-auth.

  2. Adicione código para pedir um token de ID às APIs OAuth 2.0 da Google e adicione o token a um cabeçalho de pedido antes de enviar um pedido.

  3. Teste as atualizações.

Instalar a biblioteca cliente google-auth para apps Python 3

Para disponibilizar a biblioteca cliente google-auth à sua app Python3, crie um ficheiro requirements.txt na mesma pasta que o ficheiro app.yaml e adicione a seguinte linha:

     google-auth

Quando implementa a sua app, o App Engine transfere todas as dependências definidas no ficheiro requirements.txt.

Para o desenvolvimento local, recomendamos que instale dependências num ambiente virtual, como o venv.

Adicionar código para afirmar a identidade

Pesquise no seu código e encontre todas as instâncias de envio de pedidos para outras apps do App Engine. Atualize essas instâncias para fazer o seguinte antes de enviar o pedido:

  1. Adicione as seguintes importações:

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. Use google.oauth2.id_token.fetch_id_token(request, audience) para obter um token de ID. Inclua os seguintes parâmetros na chamada do método:

    • request: transmita o objeto de pedido que se prepara para enviar.
    • audience: transmita o URL da app para a qual está a enviar o pedido. Isto associa o token ao pedido e impede que o token seja usado por outra app.

      Para maior clareza e especificidade, recomendamos que transmita o appspot.comURL que o App Engine criou para o serviço específico que está a receber o pedido, mesmo que use um domínio personalizado para a app.

  3. No objeto de pedido, defina o seguinte cabeçalho:

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

Por exemplo:

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

Testar atualizações para afirmar a identidade

Para executar a sua app localmente e testar se a app consegue enviar tokens de ID com êxito:

  1. Siga estes passos para disponibilizar as credenciais da conta de serviço do App Engine predefinida no seu ambiente local (as APIs Google OAuth requerem estas credenciais para gerar um token de ID):

    1. Introduza o seguinte comando gcloud para obter a chave da conta de serviço para a conta predefinida do App Engine do seu projeto:

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

      Substitua project-ID pelo ID do seu Google Cloud projeto.

      O ficheiro de chave da conta de serviço é transferido para o seu computador. Pode mover e mudar o nome deste ficheiro como quiser. Certifique-se de que armazena este ficheiro em segurança, uma vez que pode ser usado para autenticar como a sua conta de serviço. Se perder o ficheiro ou se o ficheiro for exposto a utilizadores não autorizados, elimine a chave da conta de serviço e crie uma nova.

    2. Introduza o seguinte comando:

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

    Substitua service-account-key pelo caminho absoluto do ficheiro que contém a chave da conta de serviço que transferiu.

  2. Na mesma shell em que exportou a variável de ambiente, inicie a sua app Python.GOOGLE_APPLICATION_CREDENTIALS

  3. Envie um pedido a partir da app e confirme que é bem-sucedido. Se ainda não tiver uma app que possa receber pedidos e usar tokens de ID para validar identidades:

    1. Transfira a app "incoming" de exemplo.
    2. No ficheiro main.py Google Cloud de exemplo, adicione o ID do seu projeto ao allowed_app_ids. Por exemplo:

       allowed_app_ids = [
          '<APP_ID_1>',
          '<APP_ID_2>',
          'my-project-id'
        ]
      
    3. Execute o exemplo atualizado no servidor de desenvolvimento local do Python 2.

Validação e processamento de pedidos

Para atualizar as suas apps Python 2 para usar identidades de tokens de ID ou da API App Identity antes de processar pedidos:

  1. Instale a biblioteca cliente google-auth.

  2. Atualize o seu código para fazer o seguinte:

    1. Se o pedido contiver o cabeçalho X-Appengine-Inbound-Appid, use esse cabeçalho para validar a identidade. As apps executadas num tempo de execução antigo, como o Python 2, vão conter este cabeçalho.

    2. Se o pedido não contiver o cabeçalho X-Appengine-Inbound-Appid, verifique se existe um token de ID OIDC. Se o token existir, valide a carga útil do token e verifique a identidade do remetente.

  3. Teste as atualizações.

Instalar a biblioteca cliente google-auth para apps Python 2

Para disponibilizar a biblioteca cliente google-auth à sua app Python 2:

  1. Crie um ficheiro requirements.txt na mesma pasta que o ficheiro app.yaml e adicione a seguinte linha:

     google-auth==1.19.2
    

    Recomendamos que use a versão 1.19.2 da biblioteca cliente do Cloud Logging, uma vez que suporta apps Python 2.7.

  2. No ficheiro app.yaml da sua app, especifique a biblioteca SSL na secção libraries se ainda não estiver especificada:

    libraries:
    - name: ssl
      version: latest
    
  3. Crie um diretório para armazenar as suas bibliotecas de terceiros, como lib/. Em seguida, use pip install para instalar as bibliotecas no diretório. Por exemplo:

    pip install -t lib -r requirements.txt
  4. Crie um ficheiro appengine_config.py na mesma pasta que o ficheiro app.yaml. Adicione o seguinte ao ficheiro 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)

    O ficheiro appengine_config.py no exemplo anterior pressupõe que a pasta lib está localizada no diretório de trabalho atual. Se não puder garantir que lib está sempre no diretório de trabalho atual, especifique o caminho completo para a pasta lib. Por exemplo:

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

Para o desenvolvimento local, recomendamos que instale dependências num ambiente virtual, como o virtualenv para o Python 2.

Atualizar código para validar pedidos

Pesquise no seu código e encontre todas as instâncias de obtenção do valor do cabeçalho X-Appengine-Inbound-Appid. Atualize essas instâncias para fazer o seguinte:

  1. Adicione as seguintes importações:

    from google.auth.transport import requests as reqs
    from google.oauth2 import id_token
    
  2. Se o pedido recebido não contiver o cabeçalho X-Appengine-Inbound-Appid, procure o cabeçalho Authorization e obtenha o respetivo valor.

    O valor do cabeçalho está formatado como "ID: token".

  3. Use google.oauth2.id_token.verify_oauth2_token(token, request, audience) para validar e obter a carga útil do token descodificado. Inclua os seguintes parâmetros na chamada do método:

    • token: transmita o token que extraiu do pedido recebido.
    • request: transmita um novo objeto google.auth.transport.Request.

    • audience: transmita o URL da app atual (a app que está a enviar o pedido de validação). O servidor de autorização da Google compara este URL com o URL que foi fornecido quando o token foi gerado originalmente. Se os URLs não corresponderem, o token não é validado e o servidor de autorização devolve um erro.

  4. O método verify_oauth2_token devolve a carga útil do token descodificada, que contém vários pares de nome/valor, incluindo o endereço de email da conta de serviço predefinida para a app que gerou o token.

  5. Extraia o nome de utilizador do endereço de email na carga útil do token.

    O nome de utilizador é igual ao ID do projeto da app que enviou o pedido. Este é o mesmo valor que foi devolvido anteriormente no cabeçalho X-Appengine-Inbound-Appid.

  6. Se o nome de utilizador/ID do projeto estiver na lista de IDs de projetos permitidos, processe o pedido.

Por exemplo:

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

Testes de atualizações para validar a identidade

Para testar se a sua app pode usar um token de ID ou o cabeçalho X-Appengine-Inbound-Appid para validar pedidos, execute a app no servidor de desenvolvimento local do Python 2 e envie pedidos de apps Python 2 (que vão usar a API App Identity) e de apps Python 3 que enviam tokens de ID.

Se não atualizou as suas apps para enviar tokens de ID:

  1. Transfira a app de "pedido" de exemplo.

  2. Adicione credenciais da conta de serviço ao seu ambiente local, conforme descrito no artigo Testar atualizações para afirmar apps.

  3. Use comandos padrão do Python 3 para iniciar a app de exemplo do Python 3.

  4. Envie um pedido a partir da app de exemplo e confirme que é bem-sucedido.

Implementar as suas apps

Quando tiver tudo pronto para implementar as suas apps, deve:

  1. Teste as apps no App Engine.

  2. Se as apps forem executadas sem erros, use a divisão de tráfego para aumentar gradualmente o tráfego das apps atualizadas. Monitorize atentamente as apps para detetar problemas antes de encaminhar mais tráfego para as apps atualizadas.

Usar uma conta de serviço diferente para afirmar a identidade

Quando pede um token de ID, o pedido usa a identidade da conta de serviço predefinida do App Engine por predefinição. Quando valida o token, a carga útil do token contém o endereço de email da conta de serviço predefinida, que é mapeado para o ID do projeto da sua app.

A conta de serviço predefinida do App Engine tem um nível de autorização muito elevado por predefinição. Pode ver e editar todo o seu projeto doGoogle Cloud , pelo que, na maioria dos casos, esta conta não é adequada para utilização quando a sua app precisa de autenticar com os serviços na nuvem.

No entanto, a conta de serviço predefinida é segura para utilização quando afirma a identidade da app, porque só está a usar o token de ID para validar a identidade da app que enviou um pedido. As autorizações reais concedidas à conta de serviço não são consideradas nem necessárias durante este processo.

Se ainda preferir usar uma conta de serviço diferente para os seus pedidos de token de ID, faça o seguinte:

  1. Defina uma variável de ambiente denominada GOOGLE_APPLICATION_CREDENTIALS para o caminho de um ficheiro JSON que contenha as credenciais da conta de serviço. Consulte as nossas recomendações para armazenar estas credenciais em segurança.

  2. Use google.oauth2.id_token.fetch_id_token(request, audience) para obter um token de ID.

  3. Quando valida este token, a carga útil do token contém o endereço de email da nova conta de serviço.