Skip to main content

Validation des livraisons de webhook

Vous pouvez utiliser un secret de webhook pour vérifier qu’une livraison de webhook provient de GitHub.

À propos de la validation des livraisons de webhook

Une fois que votre serveur est configuré pour recevoir des charges utiles, il écoutera toute livraison envoyée au point de terminaison que vous avez configuré. Pour vous assurer que votre serveur traite uniquement les livraisons de webhook envoyées par GitHub et pour vous assurer que la livraison n’a pas été falsifiée, vous devez valider la signature du webhook avant de traiter la livraison. Cela vous aidera à éviter de consacrer du temps de serveur à traiter les livraisons qui ne proviennent pas de GitHub et vous aidera à éviter les attaques de l'homme du milieu.

Pour cela, vous devez procéder comme suit :

  1. Créer un jeton secret pour un webhook.
  2. Stockez le jeton en toute sécurité sur votre serveur.
  3. Contrôlez la validité des charges utiles des webhooks entrants en les comparant au jeton, afin de confirmer qu’elles proviennent bien de GitHub et qu’elles n’ont subi aucune altération.

Création d’un jeton secret

Vous pouvez créer un nouveau webhook avec un jeton secret ou ajouter un jeton secret à un webhook existant. Lors de la création d'un jeton secret, vous devez choisir une chaîne de texte aléatoire à forte entropie.

  • Pour créer un nouveau webhook avec un jeton secret, consultez « Création de webhooks ».
  • Pour ajouter un jeton secret à un webhook existant, modifiez les paramètres du webhook. Sous « Secret », saisissez une chaîne de caractères à utiliser comme clé secret. Pour plus d’informations, consultez « Édition de webhooks ».

Stockage sécurisé du jeton secret

Après avoir créé un jeton secret, vous devez le stocker dans un emplacement sécurisé auquel votre serveur peut accéder. Ne codez jamais en dur un jeton dans une application et ne l'envoyez jamais dans un référentiel. Pour plus d'informations sur la manière d'utiliser les informations d'authentification en toute sécurité dans votre code, consultez « Sécuriser les informations d’identification de l’API ».

Validation des livraisons de webhook

GitHub utilisera votre jeton secret pour créer une signature de hachage qui vous sera envoyée avec chaque charge utile. La signature de hachage apparaîtra dans chaque envoi en tant que valeur de l'en-tête X-Hub-Signature-256. Pour plus d’informations, consultez « Événements et charges utiles du webhook ».

Dans le code chargé de traiter les livraisons de webhooks, calculez un hachage à l’aide de votre jeton secret. Ensuite, comparez le code de hachage envoyé par GitHub avec le code de hachage attendu que vous avez calculé, et assurez-vous qu'ils correspondent. Pour des exemples montrant comment valider les codes de hachage dans différents langages de programmation, consultez « Exemples ».

Il y a quelques points importants à garder à l'esprit lors de la validation des charges utiles des webhooks :

  • GitHub utilise un condensé hexadécimal HMAC pour le calcul du hachage.
  • La signature de code de hachage commence toujours par sha256=.
  • La signature de code de hachage est générée à l’aide du jeton secret de votre webhook et du contenu de la charge utile.
  • Si votre implémentation de langage et de serveur spécifie un codage de caractères, vérifiez que vous traitez la charge utile en UTF-8. Les charges utiles des webhooks peuvent contenir des caractères Unicode.
  • N’utilisez jamais d’opérateur == ordinaire. Pensez plutôt à utiliser une méthode comme secure_compare ou crypto.timingSafeEqual, qui effectue une comparaison de chaînes en « temps constant » afin d'atténuer certaines attaques ponctuelles contre les opérateurs d'égalité réguliers ou les boucles régulières dans les langages optimisés par le JIT.

Test de la validation de la charge utile du webhook

Vous pouvez utiliser les valeurs secret et payload suivantes pour vérifier que votre implémentation est correcte :

  • secret: It's a Secret to Everybody
  • payload: Hello, World!

Si votre implémentation est correcte, les signatures que vous générez devraient correspondre aux valeurs de signature suivantes :

  • signature : 757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
  • X-Hub-Signature-256 : sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17

Exemples

Vous pouvez utiliser le langage de programmation de votre choix pour implémenter la vérification HMAC dans votre code. Les exemples suivants montrent comment une implémentation peut se présenter dans différents langages de programmation.

Exemple Ruby

Par exemple, vous pouvez définir la fonction verify_signature suivante :

def verify_signature(payload_body)
  signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)
  return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['HTTP_X_HUB_SIGNATURE_256'])
end

Vous pouvez ensuite l’appeler quand vous recevez une charge utile de webhook :

post '/payload' do
  request.body.rewind
  payload_body = request.body.read
  verify_signature(payload_body)
  push = JSON.parse(payload_body)
  "I got some JSON: #{push.inspect}"
end

exemple de Python

Par exemple, vous pouvez définir la fonction verify_signature suivante et l’appeler quand vous recevez une charge utile de webhook :

import hashlib
import hmac
def verify_signature(payload_body, secret_token, signature_header):
    """Verify that the payload was sent from GitHub by validating SHA256.

    Raise and return 403 if not authorized.

    Args:
        payload_body: original request body to verify (request.body())
        secret_token: GitHub app webhook token (WEBHOOK_SECRET)
        signature_header: header received from GitHub (x-hub-signature-256)
    """
    if not signature_header:
        raise HTTPException(status_code=403, detail="x-hub-signature-256 header is missing!")
    hash_object = hmac.new(secret_token.encode('utf-8'), msg=payload_body, digestmod=hashlib.sha256)
    expected_signature = "sha256=" + hash_object.hexdigest()
    if not hmac.compare_digest(expected_signature, signature_header):
        raise HTTPException(status_code=403, detail="Request signatures didn't match!")

Exemple JavaScript

Par exemple, vous pouvez définir la fonction verifySignature suivante et l’appeler dans un environnement JavaScript quand vous recevez une charge utile de webhook :

let encoder = new TextEncoder();

async function verifySignature(secret, header, payload) {
    let parts = header.split("=");
    let sigHex = parts[1];

    let algorithm = { name: "HMAC", hash: { name: 'SHA-256' } };

    let keyBytes = encoder.encode(secret);
    let extractable = false;
    let key = await crypto.subtle.importKey(
        "raw",
        keyBytes,
        algorithm,
        extractable,
        [ "sign", "verify" ],
    );

    let sigBytes = hexToBytes(sigHex);
    let dataBytes = encoder.encode(payload);
    let equal = await crypto.subtle.verify(
        algorithm.name,
        key,
        sigBytes,
        dataBytes,
    );

    return equal;
}

function hexToBytes(hex) {
    let len = hex.length / 2;
    let bytes = new Uint8Array(len);

    let index = 0;
    for (let i = 0; i < hex.length; i += 2) {
        let c = hex.slice(i, i + 2);
        let b = parseInt(c, 16);
        bytes[index] = b;
        index += 1;
    }

    return bytes;
}

Exemple Typescript

Par exemple, vous pouvez définir la fonction verify_signature suivante et l’appeler quand vous recevez une charge utile de webhook :

JavaScript
import { Webhooks } from "@octokit/webhooks";

const webhooks = new Webhooks({
  secret: process.env.WEBHOOK_SECRET,
});

const handleWebhook = async (req, res) => {
  const signature = req.headers["x-hub-signature-256"];
  const body = await req.text();

  if (!(await webhooks.verify(body, signature))) {
    res.status(401).send("Unauthorized");
    return;
  }

  // The rest of your logic here
};

Dépannage

Si vous êtes sûr que la charge utile provient de GitHub mais que la vérification de la signature échoue :

  • Vérifiez que vous avez configuré un secret pour votre webhook. L’en-tête X-Hub-Signature-256 n’est pas présent si vous n’avez pas configuré de secret pour votre webhook. Pour plus d’informations sur la configuration d’un secret pour votre webhook, consultez Édition de webhooks.
  • Vérifiez que vous utilisez le bon en-tête. GitHub recommande d’utiliser l’en-tête X-Hub-Signature-256, qui utilise l’algorithme HMAC-SHA256. L’en-tête X-Hub-Signature utilise l’algorithme HMAC-SHA1 et n’est inclus que pour des raisons d’héritage.
  • Vérifiez que vous utilisez le bon algorithme. Si vous utilisez l’en-tête X-Hub-Signature-256, vous devez utiliser l’algorithme HMAC-SHA256.
  • Vérifiez que vous utilisez le bon secret de webhook. Si vous ne connaissez pas la valeur du secret de votre webhook, vous pouvez mettre à jour le secret de votre webhook. Pour plus d’informations, consultez « Édition de webhooks ».
  • Assurez-vous que la charge utile et les en-têtes ne sont pas modifiés avant la vérification. Par exemple, si vous utilisez un proxy ou un équilibreur de charge, assurez-vous que le proxy ou l’équilibreur de charge ne modifie pas la charge utile ou les en-têtes.
  • Si votre implémentation de langage et de serveur spécifie un codage de caractères, vérifiez que vous traitez la charge utile en UTF-8. Les charges utiles des webhooks peuvent contenir des caractères Unicode.

Pour aller plus loin