Une fois que votre serveur est configuré pour recevoir des charges utiles, il écoute toutes les charges utiles envoyées au point de terminaison que vous avez configuré. Pour des raisons de sécurité, vous pouvez limiter les demandes à celles envoyées par GitHub. Il existe plusieurs moyens de le faire (par exemple, vous pouvez choisir d’autoriser les demandes envoyées par l’adresse IP de GitHub), mais la méthode la plus facile est de configurer un jeton secret et de valider les informations.
Vous pouvez utiliser l’API REST pour gérer les webhooks de dépôt, d’organisation et d’application. Vous pouvez lister les livraisons d’un webhook, ou obtenir et refaire une livraison individuelle d’un webhook, qui peut être intégré dans une application ou un service externe. Vous pouvez également utiliser l’API REST pour changer la configuration du webhook. Par exemple, vous pouvez modifier l’URL de charge utile, le type de contenu, la vérification SSL et le secret. Pour plus d'informations, consultez les pages suivantes :
Définition de votre jeton secret
Vous devez configurer votre jeton secret à deux endroits : GitHub et votre serveur.
Pour définir votre jeton sur GitHub :
- Accédez au dépôt dans lequel vous avez configuré votre webhook.
- Sous le nom de votre dépôt, cliquez sur Paramètres. Si vous ne voyez pas l’onglet « Paramètres », sélectionnez le menu déroulant , puis cliquez sur Paramètres.
- Dans la barre latérale gauche, cliquez sur Webhooks.
- À côté du webhook, cliquez sur Modifier.
- Dans le champ « Secret », tapez une chaîne aléatoire avec une entropie élevée. Vous pouvez générer une chaîne avec
ruby -rsecurerandom -e 'puts SecureRandom.hex(20)'
dans le Terminal, par exemple. - Cliquez sur Mettre à jour le webhook.
Ensuite, configurez une variable d’environnement sur votre serveur pour stocker ce jeton. En règle générale, ça revient à simplement exécuter :
export SECRET_TOKEN=YOUR-TOKEN
Ne codez jamais en dur le jeton dans votre application !
Validation des charges utiles provenant de GitHub
Quand votre jeton secret est défini, GitHub Enterprise Server l’utilise pour créer une signature de hachage avec chaque charge utile. Cette signature de hachage est ajoutée aux en-têtes de chaque demande sous la forme x-hub-signature-256
.
Remarque : Pour la compatibilité descendante, nous ajoutons également l’en-tête x-hub-signature
qui est généré avec la fonction de hachage SHA-1. Si possible, nous vous recommandons d’utiliser l’en-tête x-hub-signature-256
pour améliorer la sécurité. Les exemples ci-dessous montrent l’utilisation de l’en-tête x-hub-signature-256
.
Vous devez calculer un hachage en utilisant votre SECRET_TOKEN
et vérifier que le résultat correspond au hachage de GitHub Enterprise Server. GitHub Enterprise Server utilise un code de hachage hexadécimal HMAC pour calculer le hachage.
Remarque : Les charges utiles de webhook peuvent contenir des caractères Unicode. 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.
Vos implémentations de langage et de serveur peuvent différer des exemples suivants. Toutefois, il y a un certain nombre de choses très importantes à souligner :
-
Quelle que soit l’implémentation que vous utilisez, la signature de hachage commence par
sha256=
, et utilise la clé de votre jeton secret et le corps de votre charge utile. -
L’utilisation d’un opérateur
==
brut n’est pas recommandée. Une méthode commesecure_compare
oucrypto.timingSafeEqual
effectue une comparaison de chaînes de « temps constant », qui permet de prévenir certaines attaques de minutage contre les opérateurs d’égalité classiques, ou des boucles classiques dans les langages optimisés JIT.
Valeurs de test
Quel que soit le langage de programmation que vous utilisez pour implémenter la vérification HMAC dans votre code, vous pouvez utiliser les valeurs secret
et payload
suivantes pour vérifier que votre implémentation est correcte.
- secret : « C’est un secret pour tout le monde »
- charge utile : « Hello, World ! »
Si votre implémentation est correcte et utilise l’algorithme SHA-256, les signatures que vous générez doivent correspondre aux valeurs de signature suivantes :
- signature : 757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
- x-hub-signature : sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
Si votre implémentation est correcte et utilise l’algorithme SHA-1, les signatures que vous générez doivent correspondre aux valeurs de signature suivantes :
- signature : 01dc10d0c83e72ed246219cdd91669667fe2ca59
- x-hub-signature : sha1=01dc10d0c83e72ed246219cdd91669667fe2ca59
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 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 :
import * as crypto from "crypto"; const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET; const verify_signature = (req: Request) => { const signature = crypto .createHmac("sha256", WEBHOOK_SECRET) .update(JSON.stringify(req.body)) .digest("hex"); let trusted = Buffer.from(`sha256=${signature}`, 'ascii'); let untrusted = Buffer.from(req.headers.get("x-hub-signature-256"), 'ascii'); return crypto.timingSafeEqual(trusted, untrusted); }; const handleWebhook = (req: Request, res: Response) => { if (!verify_signature(req)) { res.status(401).send("Unauthorized"); return; } // The rest of your logic here };
import * as crypto from "crypto";
const WEBHOOK_SECRET: string = process.env.WEBHOOK_SECRET;
const verify_signature = (req: Request) => {
const signature = crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(JSON.stringify(req.body))
.digest("hex");
let trusted = Buffer.from(`sha256=${signature}`, 'ascii');
let untrusted = Buffer.from(req.headers.get("x-hub-signature-256"), 'ascii');
return crypto.timingSafeEqual(trusted, untrusted);
};
const handleWebhook = (req: Request, res: Response) => {
if (!verify_signature(req)) {
res.status(401).send("Unauthorized");
return;
}
// The rest of your logic here
};