Como migrar a identidade do app para tokens de ID do OIDC

Quando um aplicativo em execução no ambiente do Python 2 envia uma solicitação para outro aplicativo do App Engine, ele pode usar a API App Identity do App Engine para declarar a própria identidade. O aplicativo que recebe a solicitação pode usar a identidade para determinar se precisa processá-la.

Se os aplicativos do Python 3 precisarem declarar a identidade deles ao enviar solicitações para outros aplicativos do App Engine, use os tokens de ID do OpenID Connect (OIDC) que são emitidos e decodificados pelas APIs OAuth 2.0 do Google.

Esta é uma visão geral de como usar tokens de ID do OIDC para declarar e verificar a identidade:

  1. Um aplicativo do App Engine chamado "Aplicativo A" recupera um token de ID do ambiente de execução do Google Cloud.
  2. O Aplicativo A adiciona esse token a um cabeçalho de solicitação antes de enviá-la para o Aplicativo B, que é outro aplicativo do App Engine.
  3. O Aplicativo B usa as APIs OAuth 2.0 do Google para verificar o payload do token. O payload decodificado contém a identidade verificada do Aplicativo A na forma do endereço de e-mail da conta de serviço padrão do Aplicativo A.
  4. O Aplicativo B compara a identidade no payload com uma lista de identidades a que ele tem permissão para responder. Se a solicitação vier de um aplicativo permitido, o Aplicativo B processará a solicitação e lhe responderá.

Processo do OAuth 2.0

Este guia explica como atualizar os aplicativos do App Engine para usar tokens de ID do OpenID Connect (OIDC). O objetivo é declarar identidade e atualizar outros aplicativos do App Engine para usar tokens de ID a fim de verificar a identidade antes de processar uma solicitação.

Principais diferenças entre as APIs App Identity e OIDC

  • Os aplicativos no ambiente de execução do Python 2 não precisam declarar a identidade explicitamente. Quando um aplicativo usa as bibliotecas do Python httplib, urllib ou urllib2 ou o serviço de busca de URL do App Engine para enviar solicitações de saída, o ambiente de execução usa o serviço de busca de URL do App Engine para fazer a solicitação. Se a solicitação estiver sendo enviada para o domínio appspot.com, a busca de URL declarará automaticamente a identidade do aplicativo solicitante adicionando o cabeçalho X-Appengine-Inbound-Appid à solicitação. Esse cabeçalho contém o ID do aplicativo, também chamado de ID do projeto.

    Os aplicativos no ambiente de execução do Python 3 precisam declarar a identidade explicitamente recuperando um token de ID do OIDC do ambiente de execução do Google Cloud e adicionando-o ao cabeçalho da solicitação. Você precisará atualizar todo o código que envia solicitações para outros aplicativos do App Engine, de modo que as solicitações contenham um token de ID do OIDC.

  • O cabeçalho X-Appengine-Inbound-Appid de uma solicitação contém o ID do projeto do aplicativo que enviou a solicitação.

    O payload do token de ID do OIDC do Google não identifica diretamente o ID do projeto do próprio aplicativo. Em vez disso, o token identifica a conta de serviço em que o app está sendo executado, fornecendo o endereço de e-mail dessa conta de serviço. Você precisará adicionar código para extrair o nome de usuário do payload do token.

    Se essa for a conta de serviço padrão no nível do aplicativo do App Engine do projeto, o ID do projeto poderá ser encontrado no endereço de e-mail da conta de serviço. A parte do nome de usuário do endereço é o mesmo que o ID do projeto. Nesse caso, o código do app receptor pode procurá-lo na lista de IDs de projeto que permitirão solicitações.

    No entanto, se o aplicativo solicitante estiver usando uma conta de serviço gerenciada pelo usuário em vez de a conta de serviço padrão do App Engine, o aplicativo de recebimento será verificar apenas a identidade dessa conta de serviço, que não necessariamente definirá o ID do projeto do aplicativo solicitante. Nesse caso, o app receptor precisará manter uma lista de e-mails de conta de serviço permitidos em vez de uma lista de IDs de projeto permitidos.

  • As cotas de chamadas da API URL Fetch são diferentes das cotas das APIs do OAuth 2.0 do Google para a concessão de tokens. É possível ver o número máximo de tokens que podem ser concedidos por dia na tela de consentimento do OAuth do Console do Google Cloud. As APIs URL Fetch, App Identity e OAuth 2.0 do Google não geram faturamento.

Visão geral do processo de migração

Para migrar os aplicativos do Python e usar as APIs do OIDC para declarar e verificar a identidade:

  1. Nos aplicativos que precisam declarar identidade ao enviar solicitações para outros aplicativos do App Engine:

    1. Aguarde até que o aplicativo seja executado em um ambiente do Python 3 para migrar para tokens de ID.

      Embora seja possível usar tokens de ID no ambiente de execução do Python 2, as etapas nesse ambiente são complexas e necessárias somente até que você atualize o aplicativo para ser executado no ambiente de execução do Python 3.

    2. Quando seu aplicativo estiver em execução no Python 3, atualize-o para solicitar um token de ID e adicioná-lo a um cabeçalho de solicitação.

  2. Em aplicativos que precisam verificar a identidade antes de processar uma solicitação:

    1. Comece fazendo upgrade dos aplicativos em Python 2 para oferecer suporte a tokens de ID e às identidades da API App Identity. Isso permitirá que os aplicativos verifiquem e processem solicitações de aplicativos em Python 2 que usam a API App Identity ou aplicativos em Python 3 que usam tokens de ID.

    2. Depois que os aplicativos atualizados em Python 2 estiverem estáveis, migre-os para o ambiente de execução do Python 3. Continue oferecendo suporte para os tokens de ID e as identidades da API App Identity até ter certeza de que os aplicativos não precisam mais aceitar solicitações de aplicativos legados.

    3. Quando não precisar mais processar solicitações de aplicativos legados do App Engine, remova o código que verifica as identidades da API App Identity.

  3. Depois de testar os aplicativos, implante primeiro o aplicativo que processa as solicitações. Em seguida, implante o aplicativo atualizado em Python 3 que usa tokens de ID para confirmar a identidade.

Como declarar identidade

Aguarde até que o aplicativo seja executado em um ambiente Python 3 e siga estas etapas para fazer upgrade do aplicativo e declarar a identidade com os tokens de ID:

  1. Instale a biblioteca de cliente google-auth.

  2. Adicione o código para solicitar um token de ID das APIs OAuth 2.0 do Google e adicione o token a um cabeçalho da solicitação antes de enviar uma solicitação.

  3. Teste as atualizações.

Como instalar a biblioteca de cliente google-auth para aplicativos em Python 3

Se quiser disponibilizar a biblioteca de cliente google-auth para seu aplicativo em Python 3, crie um arquivo requirements.txt na mesma pasta que o arquivo app.yaml e adicione o seguinte linha:

     google-auth

Quando você implanta o app, o App Engine faz o download de todas as dependências definidas no arquivo requirements.txt.

Para desenvolvimento local, recomendamos que você instale as dependências em um ambiente virtual como venv.

Como adicionar código para declarar identidade

Pesquise o código e encontre todas as instâncias de solicitações de envio para outros aplicativos do App Engine. Atualize essas instâncias para fazer o seguinte antes de enviar a solicitação:

  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 recuperar um token de ID. Inclua os seguintes parâmetros na chamada de método:

    • request: transmita o objeto de solicitação que você está preparando para enviar.
    • audience: transmita o URL do aplicativo ao qual você está enviando a solicitação. Isso vincula o token à solicitação e impede que ele seja usado por outro aplicativo.

      Para fins de clareza e especificidade, recomendamos transmitir o URL appspot.com que o App Engine criou para o serviço específico que está recebendo a solicitação, mesmo que você use um domínio personalizado para o aplicativo.

  3. No objeto da solicitação, 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"}

Como testar atualizações para declarar a identidade

Para executar o aplicativo localmente e testar se ele pode enviar tokens de ID:

  1. Siga estas etapas para disponibilizar as credenciais da conta de serviço padrão do App Engine no ambiente local. As APIs OAuth do Google exigem as seguintes credenciais para gerar um token de ID:

    1. Insira o seguinte comando do gcloud a fim de recuperar a chave da conta de serviço da conta padrão 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 projeto do Google Cloud.

      Agora o download do arquivo de chave da conta de serviço é feito em sua máquina. Mova e renomeie esse arquivo como quiser. Armazene esse arquivo com segurança, porque ele pode ser usado para fazer autenticação como sua conta de serviço. Se você perder o arquivo ou se ele for exposto a usuários não autorizados, exclua a chave da conta de serviço e crie uma nova.

    2. Digite o seguinte comando:

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

    Substitua service-account-key pelo nome do caminho absoluto do arquivo que contém a chave da conta de serviço transferida no download.

  2. No mesmo shell em que você exportou a variável de ambiente GOOGLE_APPLICATION_CREDENTIALS, inicie o aplicativo em Python.

  3. Envie uma solicitação do aplicativo e confirme que ela foi realizada. Se você ainda não tem um aplicativo que pode receber solicitações e usar tokens de ID para verificar as identidades:

    1. Faça o download do aplicativo de "entrada" de exemplo.
    2. No arquivo main.py do exemplo, adicione o ID do projeto do Google Cloud aos 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.

Como verificar e processar solicitações

Para fazer upgrade dos aplicativos em Python 2 para usar tokens de ID ou identidades da API App Identity antes de processar solicitações:

  1. Instale a biblioteca de cliente do google-auth.

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

    1. Se a solicitação contiver o cabeçalho X-Appengine-Inbound-Appid, use-o para verificar a identidade. Os aplicativos em um ambiente de execução legado como o Python 2 conterão esse cabeçalho.

    2. Se a solicitação não contiver o cabeçalho X-Appengine-Inbound-Appid, verifique se há um token de ID do OIDC. Se o token existir, verifique o payload do token e a identidade do remetente.

  3. Teste as atualizações.

Como instalar a biblioteca de cliente do google-auth para aplicativos em Python 2

Para disponibilizar a biblioteca de cliente google-auth no seu aplicativo em Python 2:

  1. Crie um arquivo requirements.txt na mesma pasta do arquivo app.yaml e adicione a seguinte linha:

     google-auth==1.19.2
    

    Recomendamos usar a versão 1.19.2 da biblioteca de cliente do Cloud Logging, já que ela é compatível com aplicativos em Python 2.7.

  2. No arquivo app.yaml do aplicativo, especifique a biblioteca SSL na seção libraries se ela ainda não estiver especificada:

    libraries:
    - name: ssl
      version: latest
    
  3. Crie um diretório para armazenar 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 arquivo appengine_config.py na mesma pasta que seu arquivo app.yaml. Adicione a instrução a seguir ao seu arquivo 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 arquivo appengine_config.py no exemplo anterior pressupõe que a pasta lib está localizada no diretório de trabalho atual. Se não for possível garantir que lib estará 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 desenvolvimento local, recomendamos que você instale dependências em um ambiente virtual, como o virtualenv para Python 2.

Como atualizar o código para verificar solicitações

Pesquise no seu código e encontre todas as instâncias que recebem o 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 a solicitação recebida não contiver o cabeçalho X-Appengine-Inbound-Appid, procure o cabeçalho Authorization e recupere o valor dele.

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

  3. Use google.oauth2.id_token.verify_oauth2_token(token, request, audience) para verificar e recuperar o payload do token decodificado. Inclua os seguintes parâmetros na chamada de método:

    • token: transmite o token extraído da solicitação recebida.
    • request: transmite um novo objeto google.auth.transport.Request.

    • audience: transmite o URL do aplicativo atual (que está enviando a solicitação de verificação). O servidor de autorização do Google comparará esse URL ao URL fornecido quando o token foi gerado originalmente. Se os URLs não coincidirem, o token não será verificado, e o servidor de autorização retornará um erro.

  4. O método verify_oauth2_token retorna o payload do token decodificado, que contém vários pares de nome/valor, incluindo o endereço de e-mail da conta de serviço padrão do aplicativo que gerou o token.

  5. Extraia o nome de usuário do endereço de e-mail no payload do token.

    O nome de usuário é igual ao ID do projeto do aplicativo que enviou a solicitação. É o mesmo valor retornado anteriormente no cabeçalho X-Appengine-Inbound-Appid.

  6. Se o nome de usuário/ID do projeto estiver na lista de IDs de projeto permitidos, processe a solicitação.

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

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)

Como testar atualizações para verificar identidade

Para testar se o aplicativo pode usar um token de ID ou o cabeçalho X-Appengine-Inbound-Appid para verificar solicitações, execute o aplicativo no servidor de desenvolvimento local do Python 2 e envie solicitações a partir de aplicativos em Python 2 (que usam a API App Identity) e de aplicativos em Python 3 que enviam tokens de ID.

Se você não tiver atualizado os aplicativos para enviar tokens de ID:

  1. Faça o download do aplicativo "solicitante" de exemplo.

  2. Adicione as credenciais da conta de serviço ao ambiente local conforme descrito em Como testar atualizações para declarar aplicativos.

  3. Use comandos padrão do Python 3 para iniciar o aplicativo de exemplo em Python 3.

  4. Envie uma solicitação a partir do aplicativo de amostra e confirme que ela foi realizada.

Como implantar seus aplicativos

Quando estiver pronto para implantar o aplicativo, você deverá:

  1. Testar os aplicativos no App Engine.

  2. Se os aplicativos forem executados sem erros, use a divisão de tráfego para aumentar gradualmente o tráfego dos aplicativos atualizados. Monitore de perto os aplicativos em busca de problemas antes de encaminhar mais tráfego aos aplicativos atualizados.

Como usar outra conta de serviço para declarar identidade

Ao solicitar um token de ID, a solicitação usa por padrão a identidade da conta de serviço padrão do App Engine. Quando você verifica o token, o payload do token contém o endereço de e-mail da conta de serviço padrão, que é mapeada para o ID do projeto do aplicativo.

A conta de serviço padrão do App Engine tem um nível de permissão muito alto por padrão. Ela pode ver e editar todo o projeto do Google Cloud. Portanto, na maioria dos casos, essa conta não é adequada quando o aplicativo precisa ser autenticado nos serviços do Cloud.

No entanto, é seguro usar a conta de serviço padrão ao declarar a identidade do aplicativo, porque você só está usando o token de ID para verificar a identidade do aplicativo que enviou uma solicitação. As permissões reais que foram concedidas à conta de serviço não são consideradas nem necessárias durante esse processo.

Se você ainda preferir usar uma conta de serviço diferente para suas solicitações de token de ID, faça o seguinte:

  1. Defina uma variável de ambiente chamada GOOGLE_APPLICATION_CREDENTIALS como o caminho de um arquivo JSON que contém as credenciais da conta de serviço. Veja nossas recomendações para armazenar essas credenciais com segurança.

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

  3. Quando você verifica esse token, o payload do token contém o endereço de e-mail da nova conta de serviço.