Utiliser des abonnements en mode push

Cloud Pub/Sub est compatible avec la distribution de messages en mode push et pull. Pour obtenir une présentation et une comparaison des abonnements en mode pull et push, consultez la page Présentation des abonnements. Ce document décrit la distribution push. Pour en savoir plus sur la distribution pull, consultez le Guide pour les abonnés – Mode pull.

Un abonnement Cloud Pub/Sub peut être configuré pour envoyer tous les messages sous forme de requêtes HTTP POST à un webhook, à un point de terminaison push ou à une URL. En général, le point de terminaison push doit être un serveur HTTPS accessible au public, présentant un certificat SSL valide signé par une autorité de certification et pouvant être routé par DNS. Vous devez également confirmer que vous en êtes le propriétaire ou justifier d'un niveau d'accès équivalent au domaine du point de terminaison push ou à un chemin d'URL.

De plus, les abonnements push peuvent être configurés pour fournir un en-tête d'authentification permettant aux points de terminaison d'autoriser les requêtes. Des mécanismes d'authentification et d'autorisation différents et éventuellement plus simples sont disponibles pour les points de terminaison de l'environnement standard App Engine et de Cloud Functions hébergés dans le même projet que l'abonnement.

Recevoir des messages en mode push

Une requête push Cloud Pub/Sub ressemble à l'exemple ci-dessous. Notez que le champ de données du message est encodé en base64.

    POST https://www.example.com/my-push-endpoint 

   {
     "message": {
       "attributes": {
         "key": "value"
       },
       "data": "SGVsbG8gQ2xvdWQgUHViL1N1YiEgSGVyZSBpcyBteSBtZXNzYWdlIQ==",
       "messageId": "136969346945"
     },
     "subscription": "projects/myproject/subscriptions/mysubscription"
   }
Votre point de terminaison push doit gérer les messages entrants et afficher un code d'état HTTP pour indiquer le succès ou l'échec. Un code d'état success équivaut à la confirmation de réception de message. Les codes d'état interprétés comme des accusés de réception de messages par le système Cloud Pub/Sub sont les suivants : 200, 201, 202, 204 ou 102. Une réponse indiquant un succès pourrait ressembler à ceci :

204 No Content

Cloud Pub/Sub n'envoie pas d'accusé de réception négatif (parfois appelé nack ) pour les abonnements push. Si le webhook n'affiche pas de code de type succès, Cloud Pub/Sub tente à nouveau la distribution jusqu'à expiration du message après la période de rétention de message de l'abonnement. Vous pouvez configurer un délai d'accusé de réception par défaut pour les abonnements push. Toutefois, contrairement aux abonnements pull, ce délai ne peut pas être prolongé pour des messages individuels. Il correspond en réalité à la durée limite pendant laquelle le point de terminaison doit répondre à la requête push.

Authentification et autorisation

Utiliser des jetons Web JSON (JWT)

Les abonnements push peuvent être configurés pour associer une identité de compte de service aux requêtes push, ce qui permet au point de terminaison de les authentifier. Lorsque l'authentification est activée sur un abonnement push, les requêtes push de cet abonnement incluent un JWT OpenIDConnect signé dans l'en-tête d'autorisation. Le point de terminaison push peut utiliser le jeton pour valider le fait que la requête est émise pour le compte de service associé à l'abonnement et pour décider de l'autorisation.

Le jeton JWT OpenIDConnect est un ensemble de trois chaînes codées en base64 délimitées par des points : en-tête, ensemble de revendications et signature. Exemple d'en-tête d'autorisation :

"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" 

L'en-tête et l'ensemble de revendications sont des chaînes JSON. Une fois décodés, ils prennent la forme suivante :

{"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"
  }

Les jetons ont une durée de vie d'une heure.

Authentifier des URL de l'environnement standard App Engine et de Cloud Functions

Un mécanisme alternatif plus simple de protection des URL push est disponible pour les webhook qui sont des applications de l'environnement standard App Engine ou de Cloud Functions dans le même projet que l'abonnement.

Pour les applications App Engine, vous pouvez exiger que la connexion soit faite par l'administrateur pour les requêtes push vers des URL utilisant ce modèle : /_ah/push-handlers/.*

Pour ce faire, vous devez ajouter l'option login: admin dans votre fichier app.yaml comme indiqué dans cet exemple Python 2, ou ajouter <security-constraint> comme indiqué dans cet exemple Java. Notez que l'option login: admin ne fonctionne pas pour les applications Python 3. Vous devrez implémenter la logique d'authentification dans votre code d'application. Pour plus d'informations, consultez la page Comprendre le contrôle d'accès.

Le mécanisme est similaire pour Cloud Functions, mais ne nécessite aucun chemin particulier.

Configurer Cloud Pub/Sub pour l’authentification push

La configuration de l'authentification pour un abonnement comprend deux paramètres :

  • Compte de service : Le compte de service GCP associé à l'abonnement push.
  • Audience de jeton (facultatif) : Une chaîne unique, ne respectant pas la casse, qui peut être utilisée par le webhook pour valider l'audience visée par un jeton spécifique.

Outre la configuration de ces champs, vous devez également accorder à Cloud Pub/Sub les autorisations nécessaires pour créer des jetons pour votre compte de service. Cloud Pub/Sub crée et gère un compte de service spécial pour votre projet : service-PROJECT_NUMBER@gcp-sa-pubsub.iam.gserviceaccount.com. Ce compte de service a besoin du rôle Créateur de jeton de compte de service. Si vous utilisez Cloud Console pour configurer l'abonnement pour l'authentification push, le rôle est attribué automatiquement. Sinon, vous devez explicitement attribuer le rôle au compte.

Notez que la fonctionnalité sera progressivement étendue aux projets plus anciens. Par conséquent, le compte de service Cloud Pub/Sub peut ne pas exister pour votre projet juste après le lancement de la fonctionnalité. Il sera cependant immédiatement disponible pour tous les projets nouvellement créés. Contactez cloud-pubsub@google.com si vous avez un besoin urgent d'activer la fonctionnalité sur un projet spécifique.

Ligne de commande

# grant Cloud Pub/Sub the permission to create tokens
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'

# configure the subscription push identity
gcloud beta 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}

Console

  1. Accédez à la page Sujets de Cloud Pub/Sub

    Accéder à la page Sujets

  2. Cliquez sur un nom de sujet.

  3. Créez ou mettez à jour un abonnement.

  4. Entrez une identité et (éventuellement) une audience.

Authentification et autorisation par le point de terminaison push

Revendications

Un jeton JWT peut être utilisé pour valider que les revendications - y compris les revendications par email et aud - sont signées par Google. Pour plus d'informations sur la manière dont les API OAuth 2.0 de Google peuvent être utilisées à la fois pour l'authentification et l'autorisation, consultez la documentation sur OpenID Connect.

Deux mécanismes rendent ces revendications significatives. Tout d'abord, Cloud Pub/Sub requiert que l'utilisateur ou le compte de service utilisé pour associer une identité de compte de service à un abonnement push possède le rôle d'utilisateur du compte de service pour le projet ou le compte de service.

Deuxièmement, l'accès aux certificats utilisés pour signer les jetons est étroitement contrôlé. Pour créer le jeton, Cloud Pub/Sub doit appeler un service Google interne en utilisant une identité de compte de service de signature distincte. Le compte de service de signature doit être autorisé à créer des jetons pour le compte de service revendiqué ou le projet contenant le compte. Cette opération est effectuée à l'aide de l'autorisation iam.serviceAccounts.getOpenIdToken ou d'un rôle de créateur de jetons de compte de service.

Ce rôle ou cette autorisation peut être accordé à n’importe quel compte. Cependant, vous pouvez utiliser le service Cloud IAM pour vous assurer que le compte de signature Cloud Pub/Sub est celui qui dispose de cette autorisation. Plus précisément, Cloud Pub/Sub utilise un compte de service comme celui-ci :

service-{project_number}@gcp-sa-pubsub.iam.gserviceaccount.com
  • {project_number} : Le projet GCP contenant l'abonnement.
  • gcp-sa-pubsub : Le projet appartenant à Google qui contient le compte de service de signature.

Valider des jetons

L'exemple suivant montre comment authentifier une requête push auprès d'une application App Engine.

Protocole

Requête :

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

Réponse :

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"
}

Java

@WebServlet(value = "/pubsub/authenticated-push")
public class PubSubAuthenticatedPush extends HttpServlet {
  private final String pubsubVerificationToken = System.getenv("PUBSUB_VERIFICATION_TOKEN");
  private final MessageRepository messageRepository;
  private final GoogleIdTokenVerifier verifier =
      new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
          /**
           * Please change example.com to match with value you are providing while creating
           * subscription as provided in @see <a
           * href="https://github.com/GoogleCloudPlatform/java-docs-samples/tree/master/appengine-java8/pubsub">README</a>.
           */
          .setAudience(Collections.singletonList("example.com"))
          .build();
  private final Gson gson = new Gson();
  private final JsonParser jsonParser = new JsonParser();

  @Override
  public void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws IOException, ServletException {

    // Verify that the request originates from the application.
    if (req.getParameter("token").compareTo(pubsubVerificationToken) != 0) {
      resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
      return;
    }
    // Get the Cloud Pub/Sub-generated JWT in the "Authorization" header.
    String authorizationHeader = req.getHeader("Authorization");
    if (authorizationHeader == null
        || authorizationHeader.isEmpty()
        || authorizationHeader.split(" ").length != 2) {
      resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
      return;
    }
    String authorization = authorizationHeader.split(" ")[1];

    try {
      // Verify and decode the JWT.
      GoogleIdToken idToken = verifier.verify(authorization);
      messageRepository.saveToken(authorization);
      messageRepository.saveClaim(idToken.getPayload().toPrettyString());
      // parse message object from "message" field in the request body json
      // decode message data from base64
      Message message = getMessage(req);
      messageRepository.save(message);
      // 200, 201, 204, 102 status codes are interpreted as success by the Pub/Sub system
      resp.setStatus(102);
      super.doPost(req, resp);
    } catch (Exception e) {
      resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
    }
  }

  private Message getMessage(HttpServletRequest request) throws IOException {
    String requestBody = request.getReader().lines().collect(Collectors.joining("\n"));
    JsonElement jsonRoot = jsonParser.parse(requestBody);
    String messageStr = jsonRoot.getAsJsonObject().get("message").toString();
    Message message = gson.fromJson(messageStr, Message.class);
    // decode from base64
    String decoded = decode(message.getData());
    message.setData(decoded);
    return message;
  }

  private String decode(String data) {
    return new String(Base64.getDecoder().decode(data));
  }

  PubSubAuthenticatedPush(MessageRepository messageRepository) {
    this.messageRepository = messageRepository;
  }

  public PubSubAuthenticatedPush() {
    this(MessageRepositoryImpl.getInstance());
  }
}

Node.js

app.post('/pubsub/authenticated-push', jsonBodyParser, async (req, res) => {
  // Verify that the request originates from the application.
  if (req.query.token !== PUBSUB_VERIFICATION_TOKEN) {
    res.status(400).send('Invalid request');
    return;
  }

  // Verify that the push request originates from Cloud Pub/Sub.
  try {
    // Get the Cloud Pub/Sub-generated JWT in the "Authorization" header.
    const bearer = req.header('Authorization');
    const token = bearer.match(/Bearer (.*)/)[1];
    tokens.push(token);

    // Verify and decode the JWT.
    const ticket = await authClient.verifyIdToken({
      idToken: token,
      audience: 'example.com',
    });

    const claim = ticket.getPayload();
    claims.push(claim);
  } catch (e) {
    res.status(400).send('Invalid token');
    return;
  }

  // The message is a unicode string encoded in base64.
  const message = Buffer.from(req.body.message.data, 'base64').toString(
    'utf-8'
  );

  messages.push(message);

  res.status(200).send();
});

Python

@app.route('/_ah/push-handlers/receive_messages', methods=['POST'])
def receive_messages_handler():
    # Verify that the request originates from the application.
    if (request.args.get('token', '') !=
            current_app.config['PUBSUB_VERIFICATION_TOKEN']):
        return 'Invalid request', 400

    # Verify that the push request originates from Cloud Pub/Sub.
    try:
        # Get the Cloud Pub/Sub-generated JWT in the "Authorization" header.
        bearer_token = request.headers.get('Authorization')
        token = bearer_token.split(' ')[1]
        TOKENS.append(token)

        # Verify and decode the JWT. `verify_oauth2_token` verifies
        # the JWT signature, the `aud` claim, and the `exp` claim.
        claim = id_token.verify_oauth2_token(token, requests.Request(),
                                             audience='example.com')
        # Must also verify the `iss` claim.
        if claim['iss'] not in [
            'accounts.google.com',
            'https://accounts.google.com'
        ]:
            raise ValueError('Wrong issuer.')
        CLAIMS.append(claim)
    except Exception as e:
        return 'Invalid token: {}\n'.format(e), 400

    envelope = json.loads(request.data.decode('utf-8'))
    payload = base64.b64decode(envelope['message']['data'])
    MESSAGES.append(payload)
    # Returning any 2xx status indicates successful receipt of the message.
    return 'OK', 200

Vous trouverez des exemples supplémentaires sur la validation du support JWT dans le guide de connexion à Google pour les sites Web. Un aperçu plus général des jetons OpenID est disponible dans le guide OpenID Connect.

Cloud Run

Le service Cloud Run authentifie automatiquement les appels HTTP. La seule configuration requise de l'utilisateur est qu'il dispose des rôles IAM nécessaires attribués au compte de l'appelant. Par exemple, vous pouvez autoriser ou révoquer l'autorisation d'appeler un point de terminaison Cloud Run spécifique pour un compte.

Valider la propriété du domaine

Pour éviter le trafic indésirable, Cloud Pub/Sub requiert la vérification de la propriété du point de terminaison push. Les étapes suivantes sont requises pour tous les points de terminaison, avec pour exception :

  • Cloud Functions
  • Cloud Run
  • Les applications de l'environnement standard App Engine étant dans le même projet que l'abonnement

A. Vérifiez que vous avez un accès administrateur au domaine :

Terminez le processus de validation du site à l'aide de la Search Console. Veillez à enregistrer la version https:// de l'URL de votre site. Pour plus de détails, consultez la documentation relative à la propriété du site.

B. Accordez l'accès au projet contenant l'abonnement :

Pour permettre au projet GCP de générer du trafic vers le domaine ou l'URL du point de terminaison, vous devez également accorder l'accès au domaine au projet GCP contenant l'abonnement :

  1. Dans la console, accédez aux Identifiants dans la section API et services.
    Accéder aux identifiants sur la page API et services de la console
  2. Sélectionnez votre projet, si nécessaire.
  3. Sélectionnez l'onglet Validation de domaine.
  4. Sélectionnez Ajouter un domaine.
  5. Entrez le domaine et sélectionnez Ajouter un domaine.

La console GCP compare votre domaine à ceux que vous avez vérifiés dans la Search Console. En supposant que vous avez correctement validé le domaine, la page s'actualise pour afficher votre nouvelle liste de domaines autorisés.

Vous pouvez à présent utiliser l'un de ces domaines pour recevoir des messages en mode push. Pour ce faire, vous devez les configurer en tant que points de terminaison lorsque vous créez un abonnement Cloud Pub/Sub. Pour en savoir plus, consultez la section Configurer les abonnements.

Arrêter et reprendre la distribution

Pour empêcher temporairement l'envoi de requêtes par Cloud Pub/Sub au point de terminaison push, passez l'abonnement en mode pull. Notez que plusieurs minutes peuvent être nécessaires pour que cette modification prenne effet.

Pour reprendre la distribution push, définissez à nouveau l'URL sur un point de terminaison valide. Pour arrêter définitivement la distribution, supprimez l'abonnement.

Quotas, limites et taux de distribution

Notez que les abonnements push sont soumis à un ensemble de quotas et de limites de ressources. De plus, le taux de distribution push est automatiquement ajusté pour maximiser le taux de distribution sans surcharger le point de terminaison push. Ceci est accompli en utilisant un algorithme de démarrage progressif (Slow Start) :

  • Le système commence par envoyer un seul message à la fois.
  • À chaque distribution réussie, le nombre de messages envoyés simultanément est doublé.
  • La vitesse à laquelle le système distribue des messages simultanés continue à doubler jusqu'à ce que la distribution échoue, ou que le système atteigne un quota ou une limite de ressources.
  • Pour chaque échec de distribution, le nombre de requêtes simultanées adressées au point de terminaison est divisé par deux, jusqu'à atteindre au moins une requête à la fois.

Cet algorithme suppose qu'il y a suffisamment de messages publiés dans la file d'attente pour maintenir ce débit. En fin de compte, le taux de distribution push est limité par le taux de publication des messages.

Cette page vous a-t-elle été utile ? Évaluez-la :

Envoyer des commentaires concernant…

Documentation sur Cloud Pub/Sub