API App Identity pour les anciens services groupés

ID de la région

Le REGION_ID est un code abrégé que Google attribue en fonction de la région que vous sélectionnez lors de la création de votre application. Le code ne correspond pas à un pays ou une province, même si certains ID de région peuvent ressembler aux codes de pays et de province couramment utilisés. Pour les applications créées après février 2020, REGION_ID.r est inclus dans les URL App Engine. Pour les applications existantes créées avant cette date, l'ID de région est facultatif dans l'URL.

En savoir plus sur les ID de région

L'API App Identity permet à une application de découvrir son ID application (également appelé ID du projet). Cet ID permet à l'application App Engine de valider son identité auprès d'autres applications App Engine, d'API Google et d'applications et services tiers. L'ID application peut également être utilisé pour générer une URL ou une adresse e-mail, ou pour prendre une décision d'exécution.

Obtenir l'ID du projet

Pour obtenir l'ID du projet, vous pouvez utiliser la méthode app_identity.get_application_id(). L'environnement WSGI ou CGI expose certains détails de mise en œuvre gérés par l'API.

Obtenir le nom d'hôte de l'application

Par défaut, les applications App Engine sont diffusées à partir d'URL au format https://PROJECT_ID.REGION_ID.r.appspot.com, où l'ID du projet fait partie du nom d'hôte. Si une application est diffusée à partir d'un domaine personnalisé, il peut être nécessaire de récupérer l'intégralité du nom d'hôte. Vous pouvez le faire à l'aide de la méthode app_identity.get_default_version_hostname().

Valider l'identité auprès d'autres applications App Engine

Si vous souhaitez déterminer l'identité de l'application App Engine qui envoie une requête à votre application App Engine, vous pouvez utiliser l'en-tête de requête X-Appengine-Inbound-Appid. Cet en-tête est ajouté à la requête par le service URLFetch et n'est pas modifiable par l'utilisateur. Sa présence indique donc avec certitude l'ID de projet de l'application à l'origine de la requête, s'il existe.

Exigences :

  • Seuls les appels passés au domaine appspot.com de votre application contiennent l'en-tête X-Appengine-Inbound-Appid. Les appels aux domaines personnalisés ne contiennent pas cet en-tête.
  • Les requêtes doivent être configurées pour ne pas suivre les redirections. Définissez le paramètre urlfetch.fetch() follow_redirects sur False.

Dans votre gestionnaire d'applications, vous pouvez vérifier l'ID entrant en lisant l'en-tête X-Appengine-Inbound-Appid et en le comparant à une liste d'ID autorisés à effectuer des requêtes. Exemple :

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)

Valider l'identité auprès des API Google

Les API Google utilisent le protocole OAuth 2.0 pour l'authentification et l'autorisation. L'API App Identity peut créer des jetons OAuth qui permettent de confirmer que la source d'une requête est l'application elle-même. La méthode get_access_token() renvoie un jeton d'accès pour un champ d'application ou une liste de champs d'application. Ce jeton peut ensuite être défini dans les en-têtes HTTP d'un appel pour identifier l'application appelante.

L'exemple suivant montre comment utiliser l'API App Identity pour s'authentifier auprès de l'API Cloud Storage, et récupérer et répertorier tous les buckets du projet.
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)

Notez que l'identité de l'application est représentée par le nom du compte de service, qui est généralement applicationid@appspot.gserviceaccount.com. Vous pouvez obtenir la valeur exacte en utilisant la méthode get_service_account_name(). Pour les services qui offrent des LCA, vous pouvez accorder l'accès à l'application en accordant l'accès à ce compte.

Valider l'identité auprès de services tiers

Le jeton généré par get_access_token() ne fonctionne qu'avec les services Google. Vous pouvez toutefois utiliser la technologie de signature sous-jacente pour valider l'identité de votre application auprès d'autres services. La méthode sign_blob() signe les octets à l'aide d'une clé privée propre à votre application. La méthode get_public_certificates() renvoie des certificats permettant de valider la signature.

Voici un exemple de procédure à suivre pour signer un blob et valider sa signature :

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)

Obtenir le nom du bucket Cloud Storage par défaut

Chaque application peut être associée à un bucket Cloud Storage par défaut, qui comprend 5 Go de stockage gratuit et un quota gratuit pour les opérations d'E/S.

Vous pouvez vous servir de l'API App Identity pour obtenir le nom du bucket par défaut. Appelez google.appengine.api.app_identity.app_identity.get_default_gcs_bucket_name.