API App Identity para servicios agrupados antiguos

ID de región

El REGION_ID es un código abreviado que Google asigna en función de la región que selecciones al crear tu aplicación. El código no corresponde a un país o provincia, aunque algunos IDs de región pueden parecerse a los códigos de país y provincia que se usan habitualmente. En las aplicaciones creadas después de febrero del 2020, REGION_ID.r se incluye en las URLs de App Engine. En las aplicaciones creadas antes de esa fecha, el ID de región es opcional en la URL.

Más información sobre los IDs de región

La API App Identity permite que una aplicación descubra su ID de aplicación (también llamado ID de proyecto). Con el ID, una aplicación de App Engine puede afirmar su identidad a otras aplicaciones de App Engine, APIs de Google y aplicaciones y servicios de terceros. El ID de aplicación también se puede usar para generar una URL o una dirección de correo electrónico, o para tomar una decisión en tiempo de ejecución.

Obtener el ID de proyecto

El ID del proyecto se puede encontrar con el método ApiProxy.getCurrentEnvironment().getAppId().

Obtener el nombre de host de la aplicación

De forma predeterminada, las aplicaciones de App Engine se sirven desde URLs con el formato https://PROJECT_ID.REGION_ID.r.appspot.com, donde el ID del proyecto forma parte del nombre de host. Si una aplicación se sirve desde un dominio personalizado, puede que sea necesario recuperar todo el componente del nombre de host. Para ello, puede usar el atributo com.google.appengine.runtime.default_version_hostname de CurrentEnvironment.

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
  resp.setContentType("text/plain");
  ApiProxy.Environment env = ApiProxy.getCurrentEnvironment();
  resp.getWriter().print("default_version_hostname: ");
  resp.getWriter()
      .println(env.getAttributes().get("com.google.appengine.runtime.default_version_hostname"));
}

Afirmar la identidad en otras aplicaciones de App Engine

Si quieres determinar la identidad de la aplicación de App Engine que está haciendo una solicitud a tu aplicación de App Engine, puedes usar el encabezado de solicitud X-Appengine-Inbound-Appid. El servicio URLFetch añade este encabezado a la solicitud y no se puede modificar, por lo que indica de forma segura el ID del proyecto de la aplicación que realiza la solicitud, si está presente.

Requisitos:

  • Solo las llamadas realizadas al dominio appspot.com de tu aplicación contendrán el encabezado X-Appengine-Inbound-Appid. Las llamadas a dominios personalizados no contienen la cabecera.
  • Tus solicitudes deben configurarse para que no sigan las redirecciones. Si usas la clase URLFetchService, tu aplicación debe especificar doNotFollowRedirect. Las aplicaciones que se ejecutan en el entorno de ejecución de Java 8 no usan el servicio URLFetch de forma predeterminada. Para habilitar URLFetch, sigue estas instrucciones.
  • Si tu aplicación usa java.net, actualiza el código para que no siga las redirecciones:
    connection.setInstanceFollowRedirects(false);

En el controlador de tu aplicación, puedes comprobar el ID entrante leyendo el encabezado X-Appengine-Inbound-Appid y comparándolo con una lista de IDs que tienen permiso para enviar solicitudes.

Afirmar la identidad en las APIs de Google

Las APIs de Google utilizan el protocolo OAuth 2.0 para la autenticación y la autorización. La API App Identity puede crear tokens de OAuth que se pueden usar para afirmar que la fuente de una solicitud es la propia aplicación. El método getAccessToken() devuelve un token de acceso para un permiso o una lista de permisos. Este token se puede definir en los encabezados HTTP de una llamada para identificar la aplicación que llama.

En el siguiente ejemplo se muestra cómo usar la API App Identity para hacer una llamada REST a la API Google URL Shortener.

/**
 * Returns a shortened URL by calling the Google URL Shortener API.
 *
 * <p>Note: Error handling elided for simplicity.
 */
public String createShortUrl(String longUrl) throws Exception {
  ArrayList<String> scopes = new ArrayList<>();
  scopes.add("https://www.googleapis.com/auth/urlshortener");
  final AppIdentityService appIdentity = AppIdentityServiceFactory.getAppIdentityService();
  final AppIdentityService.GetAccessTokenResult accessToken = appIdentity.getAccessToken(scopes);
  // The token asserts the identity reported by appIdentity.getServiceAccountName()
  JSONObject request = new JSONObject();
  request.put("longUrl", longUrl);

  URL url = new URL("https://www.googleapis.com/urlshortener/v1/url?pp=1");
  HttpURLConnection connection = (HttpURLConnection) url.openConnection();
  connection.setDoOutput(true);
  connection.setRequestMethod("POST");
  connection.addRequestProperty("Content-Type", "application/json");
  connection.addRequestProperty("Authorization", "Bearer " + accessToken.getAccessToken());

  OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
  request.write(writer);
  writer.close();

  if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
    // Note: Should check the content-encoding.
    //       Any JSON parser can be used; this one is used for illustrative purposes.
    JSONTokener responseTokens = new JSONTokener(connection.getInputStream());
    JSONObject response = new JSONObject(responseTokens);
    return (String) response.get("id");
  } else {
    try (InputStream s = connection.getErrorStream();
        InputStreamReader r = new InputStreamReader(s, StandardCharsets.UTF_8)) {
      throw new RuntimeException(
          String.format(
              "got error (%d) response %s from %s",
              connection.getResponseCode(), CharStreams.toString(r), connection.toString()));
    }
  }
}

Ten en cuenta que la identidad de la aplicación se representa mediante el nombre de la cuenta de servicio, que suele ser applicationid@appspot.gserviceaccount.com. Puedes obtener el valor exacto con el método getServiceAccountName(). En los servicios que ofrecen listas de control de acceso, puedes conceder acceso a la aplicación otorgando acceso a esta cuenta.

Afirmar la identidad en servicios de terceros

El token generado por getAccessToken() solo funciona con los servicios de Google. Sin embargo, puedes usar la tecnología de firma subyacente para afirmar la identidad de tu aplicación a otros servicios. El método signForApp() firmará los bytes con una clave privada única para tu aplicación, y el método getPublicCertificatesForApp() devolverá certificados que se pueden usar para validar la firma.

A continuación, se muestra un ejemplo de cómo firmar un blob y validar su firma:
// Note that the algorithm used by AppIdentity.signForApp() and
// getPublicCertificatesForApp() is "SHA256withRSA"

private byte[] signBlob(byte[] blob) {
  AppIdentityService.SigningResult result = appIdentity.signForApp(blob);
  return result.getSignature();
}

private byte[] getPublicCertificate() throws UnsupportedEncodingException {
  Collection<PublicCertificate> certs = appIdentity.getPublicCertificatesForApp();
  PublicCertificate publicCert = certs.iterator().next();
  return publicCert.getX509CertificateInPemFormat().getBytes("UTF-8");
}

private Certificate parsePublicCertificate(byte[] publicCert)
    throws CertificateException, NoSuchAlgorithmException {
  InputStream stream = new ByteArrayInputStream(publicCert);
  CertificateFactory cf = CertificateFactory.getInstance("X.509");
  return cf.generateCertificate(stream);
}

private boolean verifySignature(byte[] blob, byte[] blobSignature, PublicKey pk)
    throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
  Signature signature = Signature.getInstance("SHA256withRSA");
  signature.initVerify(pk);
  signature.update(blob);
  return signature.verify(blobSignature);
}

private String simulateIdentityAssertion()
    throws CertificateException, UnsupportedEncodingException, NoSuchAlgorithmException,
    InvalidKeyException, SignatureException {
  // Simulate the sending app.
  String message = "abcdefg " + Calendar.getInstance().getTime().toString();
  byte[] blob = message.getBytes();
  byte[] blobSignature = signBlob(blob);
  byte[] publicCert = getPublicCertificate();

  // Simulate the receiving app, which gets the certificate, blob, and signature.
  Certificate cert = parsePublicCertificate(publicCert);
  PublicKey pk = cert.getPublicKey();
  boolean isValid = verifySignature(blob, blobSignature, pk);

  return String.format(
      "isValid=%b for message: %s\n\tsignature: %s\n\tpublic cert: %s",
      isValid, message, Arrays.toString(blobSignature), Arrays.toString(publicCert));
}

Obtener el nombre del segmento de Cloud Storage predeterminado

Cada aplicación puede tener un segmento predeterminado de Cloud Storage, que incluye 5 GB de almacenamiento gratuito y una cuota gratuita para las operaciones de E/S.

Para obtener el nombre del bucket predeterminado, puedes usar la API App Identity. Llama a AppIdentityService.getDefaultGcsBucketName.