API App Identity para serviços agrupados antigos

ID da região

O REGION_ID é um código abreviado que a Google atribui com base na região que seleciona quando cria a sua app. O código não corresponde a um país ou uma província, embora alguns IDs de regiões possam parecer semelhantes aos códigos de países e províncias usados frequentemente. Para apps criadas após fevereiro de 2020, REGION_ID.r está incluído nos URLs do App Engine. Para apps existentes criadas antes desta data, o ID da região é opcional no URL.

Saiba mais acerca dos IDs de regiões.

A API App Identity permite que uma aplicação descubra o respetivo ID da aplicação (também denominado ID do projeto). Usando o ID, uma aplicação do App Engine pode afirmar a sua identidade a outras apps do App Engine, APIs Google e aplicações e serviços de terceiros. O ID da aplicação também pode ser usado para gerar um URL ou um endereço de email, ou para tomar uma decisão em tempo de execução.

Obter o ID do projeto

Pode encontrar o ID do projeto através do método app_identity.get_application_id(). O ambiente WSGI ou CGI expõe alguns detalhes de implementação, que são processados pela API.

Obter o nome do anfitrião da aplicação

Por predefinição, as apps do App Engine são publicadas a partir de URLs no formato https://PROJECT_ID.REGION_ID.r.appspot.com, em que o ID do projeto faz parte do nome de anfitrião. Se uma app for publicada a partir de um domínio personalizado, pode ser necessário obter o componente de nome de anfitrião completo. Pode fazê-lo através do método app_identity.get_default_version_hostname().

Afirmar a identidade a outras apps do App Engine

Se quiser determinar a identidade da app do App Engine que está a fazer um pedido à sua app do App Engine, pode usar o cabeçalho do pedido X-Appengine-Inbound-Appid. Este cabeçalho é adicionado ao pedido pelo serviço URLFetch e não é modificável pelo utilizador, pelo que indica com segurança o ID do projeto da aplicação que está a fazer o pedido, se estiver presente.

Requisitos:

  • Apenas as chamadas feitas para o domínio appspot.com da sua app vão conter o cabeçalho X-Appengine-Inbound-Appid. As chamadas para domínios personalizados não contêm o cabeçalho.
  • Os seus pedidos têm de estar definidos para não seguirem redirecionamentos. Defina o parâmetro urlfetch.fetch() follow_redirects como False.

No controlador da aplicação, pode verificar o ID recebido lendo o cabeçalho X-Appengine-Inbound-Appid e comparando-o com uma lista de IDs autorizados a fazer pedidos. Por 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)

Afirmar a identidade às APIs Google

As APIs Google usam o protocolo OAuth 2.0 para autenticação e autorização. A API App Identity pode criar tokens OAuth que podem ser usados para afirmar que a origem de um pedido é a própria aplicação. O método get_access_token() devolve um token de acesso para um âmbito ou uma lista de âmbitos. Este token pode ser definido nos cabeçalhos HTTP de uma chamada para identificar a aplicação de chamada.

O exemplo seguinte mostra como usar a API App Identity para autenticar na API Cloud Storage e obter uma lista de todos os contentores 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)

Tenha em atenção que a identidade da aplicação é representada pelo nome da conta de serviço, que é normalmente applicationid@appspot.gserviceaccount.com. Pode obter o valor exato através do método get_service_account_name(). Para serviços que oferecem ACLs, pode conceder acesso à aplicação concedendo acesso a esta conta.

Afirmar a identidade a serviços de terceiros

O token gerado por get_access_token() só funciona com os serviços Google. No entanto, pode usar a tecnologia de assinatura subjacente para afirmar a identidade da sua aplicação a outros serviços. O método sign_blob() assina bytes através de uma chave privada exclusiva da sua aplicação, e o método get_public_certificates() devolve certificados que podem ser usados para validar a assinatura.

Segue-se um exemplo que mostra como assinar um blob e validar a respetiva 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)

Obter o nome do contentor do Cloud Storage predefinido

Cada aplicação pode ter um contentor do Cloud Storage predefinido, que inclui 5 GB de armazenamento gratuito e uma quota gratuita para operações de I/O.

Para obter o nome do contentor predefinido, pode usar a API App Identity. Chame google.appengine.api.app_identity.app_identity.get_default_gcs_bucket_name.