Autenticación para suscripciones de envío

Si una suscripción de envío usa autenticación, el servicio de Pub/Sub firma un token web JSON (JWT) y envía el JWT en el encabezado de autorización de la solicitud de envío. El JWT incluye reclamaciones y una firma.

Los suscriptores pueden validar el JWT y verificar lo siguiente:

  • Los reclamos son precisos.
  • El servicio de Pub/Sub firmó las reclamaciones.

Si los suscriptores usan un firewall, no pueden recibir solicitudes de envío. Para recibir solicitudes de envío, debes desactivar el firewall y verificar el JWT. Si un suscriptor tiene un firewall, es posible que recibas un error 403 permission denied.

Antes de comenzar

Formato JWT

JWT es un JWT de OpenIDConnect que consiste en un encabezado, conjunto de reclamos y firma. El servicio Pub/Sub codifica el JWT como una string base64 con delimitadores de punto.

Por ejemplo, el siguiente encabezado de autorización incluye un JWT codificado:

"Authorization" : "Bearer
eyJhbGciOiJSUzI1NiIsImtpZCI6IjdkNjgwZDhjNzBkNDRlOTQ3MTMzY2JkNDk5ZWJjMWE2MWMzZDVh
YmMiLCJ0eXAiOiJKV1QifQ.eyJhdWQiOiJodHRwczovL2V4YW1wbGUuY29tIiwiYXpwIjoiMTEzNzc0M
jY0NDYzMDM4MzIxOTY0IiwiZW1haWwiOiJnYWUtZ2NwQGFwcHNwb3QuZ3NlcnZpY2VhY2NvdW50LmNvb
SIsImVtYWlsX3ZlcmlmaWVkIjp0cnVlLCJleHAiOjE1NTAxODU5MzUsImlhdCI6MTU1MDE4MjMzNSwia
XNzIjoiaHR0cHM6Ly9hY2NvdW50cy5nb29nbGUuY29tIiwic3ViIjoiMTEzNzc0MjY0NDYzMDM4MzIxO
TY0In0.QVjyqpmadTyDZmlX2u3jWd1kJ68YkdwsRZDo-QxSPbxjug4ucLBwAs2QePrcgZ6hhkvdc4UHY
4YF3fz9g7XHULNVIzX5xh02qXEH8dK6PgGndIWcZQzjSYfgO-q-R2oo2hNM5HBBsQN4ARtGK_acG-NGG
WM3CQfahbEjZPAJe_B8M7HfIu_G5jOLZCw2EUcGo8BvEwGcLWB2WqEgRM0-xt5-UPzoa3-FpSPG7DHk7
z9zRUeq6eB__ldb-2o4RciJmjVwHgnYqn3VvlX9oVKEgXpNFhKuYA-mWh5o7BCwhujSMmFoBOh6mbIXF
cyf5UiVqKjpqEbqPGo_AvKvIQ9VTQ" 

El encabezado y el conjunto de reclamos son strings de JSON. Una vez decodificados, toman el siguiente formato:

{"alg":"RS256","kid":"7d680d8c70d44e947133cbd499ebc1a61c3d5abc","typ":"JWT"}

{
   "aud":"https://example.com",
   "azp":"113774264463038321964",
   "email":"gae-gcp@appspot.gserviceaccount.com",
   "sub":"113774264463038321964",
   "email_verified":true,
   "exp":1550185935,
   "iat":1550182335,
   "iss":"https://accounts.google.com"
  }

Los tokens adjuntos a las solicitudes enviadas a los extremos de envío pueden tener hasta una hora de antigüedad.

Configura Pub/Sub para que use la autenticación de envío

En el siguiente ejemplo, se muestra cómo configurar la cuenta de servicio de autenticación de envío en una cuenta de servicio de tu elección y cómo otorgar el rol iam.serviceAccountTokenCreator a la service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com agente de servicio.

Console

  1. Ve a la página Suscripciones de Pub/Sub.

    Ir a la página Suscripciones

  2. Haz clic en Crear suscripción.

  3. En el campo ID de suscripción, ingresa un nombre.

  4. Selecciona un tema.

  5. Selecciona Envío como el Tipo de entrega.

  6. Ingresa una URL de extremo.

  7. Marca Habilitar autenticación.

  8. Selecciona una cuenta de servicio.

  9. Asegúrate de que el agente de servicio service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com tenga el iam.serviceAccountTokenCreator rol en el panel de IAM de tu proyecto. Si no se le otorgó el rol a la cuenta de servicio, haz clic en Otorgar en el panel de IAM para hacerlo.

  10. Opcional: Ingresa un público.

  11. Haz clic en Crear.

gcloud

# Configure the push subscription
gcloud pubsub subscriptions (create|update|modify-push-config) ${SUBSCRIPTION} \
 --topic=${TOPIC} \
 --push-endpoint=${PUSH_ENDPOINT_URI} \
 --push-auth-service-account=${SERVICE_ACCOUNT_EMAIL} \
 --push-auth-token-audience=${OPTIONAL_AUDIENCE_OVERRIDE}

# Your service agent
# `service-{PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com` needs to have the
# `iam.serviceAccountTokenCreator` role.
PUBSUB_SERVICE_ACCOUNT="service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com"
gcloud projects add-iam-policy-binding ${PROJECT_ID} \
 --member="serviceAccount:${PUBSUB_SERVICE_ACCOUNT}"\
 --role='roles/iam.serviceAccountTokenCreator'

Cuando habilitas la autenticación para una suscripción de envío, es posible que veas un error permission denied o not authorized. Para resolver este problema, otorga al principal que inicia la creación o actualización de la suscripción el permiso iam.serviceAccounts.actAs en la cuenta de servicio. Para obtener más información, consulta Autenticación en "Crea suscripciones de envío".

Si usas una suscripción de envío autenticada con una aplicación de App Engine protegida con Identity-Aware Proxy, debes proporcionar el ID de cliente de IAP como público de tu token de autenticación de envío. Para habilitar IAP en tu aplicación de App Engine, consulta Habilita IAP. Para encontrar el ID de cliente de IAP, busca el ID de cliente IAP-App-Engine-app en la página Credenciales.

Reclamaciones

El JWT se puede usar para validar que los reclamos, incluidos los reclamos email y aud, estén firmados por Google. Consulta OpenID Connect si deseas obtener más información sobre cómo se pueden usar las API de OAuth 2.0 de Google para la autenticación y la autorización.

Hay dos mecanismos que hacen que estas afirmaciones tengan sentido. Primero, Pub/Sub requiere que el usuario o la cuenta de servicio que realiza la llamada CreateSubscription, UpdateSubscription o ModifyPushConfig tenga un rol con el permiso iam.serviceAccounts.actAs en la cuenta de servicio de autenticación de envío. Un ejemplo de este rol es el roles/iam.serviceAccountUser rol.

En segundo lugar, el acceso a los certificados usados para firmar los tokens está controlado de forma estricta. Para crear el token, Pub/Sub debe llamar a un servicio interno de Google mediante el uso de una identidad de cuenta de servicio de firma distinta, que es el agente de servicio service-${PROJECT_NUMBER}@gcp-sa-pubsub.iam.gserviceaccount.com. Esta cuenta de servicio de firma debe tener el permiso iam.serviceAccounts.getOpenIdToken o un rol Creador de tokens de cuenta de servicio (roles/iam.serviceAccountTokenCreator) en la cuenta de servicio de autenticación de envío (o en cualquier recurso superior, como el proyecto, de la cuenta de servicio de autenticación de envío).

Valida tokens

Validar tokens enviados por Pub/Sub al extremo de envío involucra lo siguiente:

  • Comprueba la integridad del token mediante la validación de la firma.
  • Asegúrate de que las reclamaciones del correo electrónico y el público en el token coincidan con los valores establecidos en la configuración de suscripción de envío.

En el siguiente ejemplo, se muestra cómo autenticar una solicitud de envío a una aplicación de App Engine que no está protegida con Identity-Aware Proxy. Si tu aplicación de App Engine está protegida con IAP, el encabezado de la solicitud HTTP que contiene el JWT de IAP es x-goog-iap-jwt-assertion y se debe validar en consecuencia.

protocolo

Solicitud:

GET https://oauth2.googleapis.com/tokeninfo?id_token={BEARER_TOKEN}

Respuesta:

200 OK
{
    "alg": "RS256",
    "aud": "example.com",
    "azp": "104176025330667568672",
    "email": "{SERVICE_ACCOUNT_NAME}@{YOUR_PROJECT_NAME}.iam.gserviceaccount.com",
    "email_verified": "true",
    "exp": "1555463097",
    "iat": "1555459497",
    "iss": "https://accounts.google.com",
    "kid": "3782d3f0bc89008d9d2c01730f765cfb19d3b70e",
    "sub": "104176025330667568672",
    "typ": "JWT"
}

C#

Antes de probar esta muestra, sigue las instrucciones de configuración de C# en la guía de inicio rápido sobre el uso de bibliotecas cliente. Si quieres obtener más información, consulta la documentación de referencia de la API de Pub/Sub para C#.

/// <summary>
/// Extended JWT payload to match the pubsub payload format.
/// </summary>
public class PubSubPayload : JsonWebSignature.Payload
{
    [JsonProperty("email")]
    public string Email { get; set; }
    [JsonProperty("email_verified")]
    public string EmailVerified { get; set; }
}
/// <summary>
/// Handle authenticated push request coming from pubsub.
/// See the full sample in https://github.com/GoogleCloudPlatform/dotnet-docs-samples/blob/main/appengine/flexible/Pubsub/Pubsub.Sample/Controllers/HomeController.cs
/// </summary>
[HttpPost]
[Route("/AuthPush")]
public async Task<IActionResult> AuthPushAsync([FromBody] PushBody body, [FromQuery] string token)
{
    // Get the Cloud Pub/Sub-generated "Authorization" header.
    string authorizaionHeader = HttpContext.Request.Headers["Authorization"];
    string verificationToken = token ?? body.message.attributes["token"];
    // JWT token comes in `Bearer <JWT>` format substring 7 specifies the position of first JWT char.
    string authToken = authorizaionHeader.StartsWith("Bearer ") ? authorizaionHeader.Substring(7) : null;
    if (verificationToken != _options.VerificationToken || authToken is null)
    {
        return new BadRequestResult();
    }
    // Verify and decode the JWT.
    // Note: For high volume push requests, it would save some network
    // overhead if you verify the tokens offline by decoding them using
    // Google's Public Cert; caching already seen tokens works best when
    // a large volume of messages have prompted a single push server to
    // handle them, in which case they would all share the same token for
    // a limited time window.
    var payload = await JsonWebSignature.VerifySignedTokenAsync<PubSubPayload>(authToken);

    // IMPORTANT: you should validate payload details not covered
    // by signature and audience verification above, including:
    //   - Ensure that `payload.Email` is equal to the expected service
    //     account set up in the push subscription settings.
    //   - Ensure that `payload.Email_verified` is set to true.

    var messageBytes = Convert.FromBase64String(body.message.data);
    string message = System.Text.Encoding.UTF8.GetString(messageBytes);
    s_authenticatedMessages.Add(message);
    return new OkResult();
}

Go

// receiveMessagesHandler validates authentication token and caches the Pub/Sub
// message received.
func (a *app) receiveMessagesHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
		return
	}

	// Verify that the request originates from the application.
	// a.pubsubVerificationToken = os.Getenv("PUBSUB_VERIFICATION_TOKEN")
	if token, ok := r.URL.Query()["token"]; !ok || len(token) != 1 || token[0] != a.pubsubVerificationToken {
		http.Error(w, "Bad token", http.StatusBadRequest)
		return
	}

	// Get the Cloud Pub/Sub-generated JWT in the "Authorization" header.
	authHeader := r.Header.Get("Authorization")
	if authHeader == ""