API App Identity para pacotes de serviços legados

ID da região

O REGION_ID é um código abreviado que o Google atribui com base na região que você selecionou ao criar o aplicativo. O código não corresponde a um país ou estado, ainda que alguns IDs de região sejam semelhantes aos códigos de país e estado geralmente usados. Para apps criados após fevereiro de 2020, o REGION_ID.r está incluído nos URLs do App Engine. Para apps existentes criados antes dessa data, o ID da região é opcional no URL.

Saiba mais sobre IDs de região.

A API App Identity permite que um aplicativo descubra seu próprio ID, também chamado de ID do projeto. Com esse ID, um aplicativo do App Engine pode confirmar a própria identidade para outros aplicativos do App Engine e APIs do Google, assim como para aplicativos e serviços de terceiros. Ele também pode ser usado para gerar um URL ou endereço de e-mail ou para tomar uma decisão de tempo de execução.

Como conseguir o ID do projeto

O ID do projeto pode ser encontrado usando o método app_identity.get_application_id(). O ambiente WSGI ou CGI expõe alguns detalhes da implementação que são gerenciados pela API.

Como conseguir o nome do host do aplicativo

Por padrão, os aplicativos do App Engine são exibidos a partir de URLs no formato https://PROJECT_ID.REGION_ID.r.appspot.com, em que o ID do projeto faz parte do nome do host. Se um aplicativo é disponibilizado de um domínio personalizado, talvez seja necessário recuperar todo o componente do nome do host. Para isso, use o método app_identity.get_default_version_hostname().

Como confirmar a identidade para outros apps do App Engine

Se você quiser determinar a identidade do aplicativo do App Engine que está fazendo uma solicitação para o aplicativo do App Engine, use o cabeçalho de solicitação X-Appengine-Inbound-Appid. Esse cabeçalho é adicionado à solicitação pelo serviço URLFetch e não pode ser modificado pelo usuário. Por isso, indica com segurança o ID do projeto do aplicativo solicitante, se houver um.

Requisitos:

  • Somente as chamadas feitas para o domínio appspot.com do aplicativo conterão o cabeçalho X-Appengine-Inbound-Appid. Chamadas para domínios personalizados não contêm cabeçalho.
  • Suas solicitações precisam ser definidas para não seguir redirecionamentos. Defina o parâmetro urlfetch.fetch() follow_redirects como False.

No gerenciador do aplicativo, é possível verificar o ID recebido lendo o cabeçalho X-Appengine-Inbound-Appid e comparando-o a uma lista de IDs com permissão para fazer solicitações. Exemplo:

import webapp2

class MainPage(webapp2.RequestHandler):
    allowed_app_ids = [
        'other-app-id',
        'other-app-id-2'
    ]

    def get(self):
        incoming_app_id = self.request.headers.get(
            'X-Appengine-Inbound-Appid', None)

        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 confirmar a identidade das APIs do Google

As APIs do Google usam o protocolo OAuth 2.0 para autenticação e autorização. A API App Identity pode criar tokens OAuth usados para confirmar que a origem de uma solicitação é o próprio aplicativo. O método get_access_token() retorna um token de acesso para um escopo ou uma lista de escopos. Esse token pode ser configurado nos cabeçalhos HTTP de uma chamada para identificar o aplicativo que fez a chamada.

O exemplo a seguir mostra como usar a API App Identity para realizar a autenticação na API Cloud Storage e recuperar uma lista de todos os buckets no projeto.
import json
import logging

from google.appengine.api import app_identity
from google.appengine.api import urlfetch
import webapp2

class MainPage(webapp2.RequestHandler):
    def get(self):
        auth_token, _ = app_identity.get_access_token(
            'https://www.googleapis.com/auth/cloud-platform')
        logging.info(
            'Using token {} to represent identity {}'.format(
                auth_token, app_identity.get_service_account_name()))

        response = urlfetch.fetch(
            'https://www.googleapis.com/storage/v1/b?project={}'.format(
                app_identity.get_application_id()),
            method=urlfetch.GET,
            headers={
                'Authorization': 'Bearer {}'.format(auth_token)
            }
        )

        if response.status_code != 200:
            raise Exception(
                'Call failed. Status code {}. Body {}'.format(
                    response.status_code, response.content))

        result = json.loads(response.content)
        self.response.headers['Content-Type'] = 'application/json'
        self.response.write(json.dumps(result, indent=2))

app = webapp2.WSGIApplication([
    ('/', MainPage)
], debug=True)

A identidade do aplicativo é representada pelo nome da conta de serviço, que costuma ser applicationid@appspot.gserviceaccount.com. Para obter o valor exato, use o método get_service_account_name(). Para conceder acesso ao aplicativo nos serviços que oferecem ACLs, basta conceder acesso à esta conta.

Como afirmar a identidade para serviços de terceiros

O token gerado por get_access_token() só funciona nos serviços do Google. No entanto, é possível usar a tecnologia de assinatura subjacente para confirmar a identidade do seu aplicativo a outros serviços. O método sign_blob() assina bytes usando uma chave privada exclusiva do seu aplicativo, e o método get_public_certificates() retornará certificados que podem ser usados para validar a assinatura.

Veja um exemplo que mostra como assinar um blob e validar sua assinatura:

import base64

from Crypto.Hash import SHA256
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Util.asn1 import DerSequence
from google.appengine.api import app_identity
import webapp2

def verify_signature(data, signature, x509_certificate):
    """Verifies a signature using the given x.509 public key certificate."""

    # PyCrypto 2.6 doesn't support x.509 certificates directly, so we'll need
    # to extract the public key from it manually.
    # This code is based on https://github.com/google/oauth2client/blob/master
    # /oauth2client/_pycrypto_crypt.py
    pem_lines = x509_certificate.replace(b' ', b'').split()
    cert_der = base64.urlsafe_b64decode(b''.join(pem_lines[1:-1]))
    cert_seq = DerSequence()
    cert_seq.decode(cert_der)
    tbs_seq = DerSequence()
    tbs_seq.decode(cert_seq[0])
    public_key = RSA.importKey(tbs_seq[6])

    signer = PKCS1_v1_5.new(public_key)
    digest = SHA256.new(data)

    return signer.verify(digest, signature)

def verify_signed_by_app(data, signature):
    """Checks the signature and data against all currently valid certificates
    for the application."""
    public_certificates = app_identity.get_public_certificates()

    for cert in public_certificates:
        if verify_signature(data, signature, cert.x509_certificate_pem):
            return True

    return False

class MainPage(webapp2.RequestHandler):
    def get(self):
        message = 'Hello, world!'
        signing_key_name, signature = app_identity.sign_blob(message)
        verified = verify_signed_by_app(message, signature)

        self.response.content_type = 'text/plain'
        self.response.write('Message: {}\n'.format(message))
        self.response.write(
            'Signature: {}\n'.format(base64.b64encode(signature)))
        self.response.write('Verified: {}\n'.format(verified))

app = webapp2.WSGIApplication([
    ('/', MainPage)
], debug=True)

Como encontrar o nome padrão do bucket do Cloud Storage

Cada aplicativo pode ter um bucket padrão do Cloud Storage, que inclui 5 GB de armazenamento gratuito e uma cota gratuita para operações de E/S.

Para encontrar o nome do bucket padrão, use a API App Identity. Chame google.appengine.api.app_identity.app_identity.get_default_gcs_bucket_name.