Skip to main content

Validação de entregas de webhooks

Você pode usar um segredo de webhook para verificar se uma entrega de webhook é do GitHub.

Sobre a validação de entregas de webhooks

Quando o servidor estiver configurado para receber cargas úteis, ele escutará qualquer entrega enviada ao endpoint que você configurou. Para garantir que o seu servidor processe somente as entregas de webhooks que foram enviadas pela GitHub e para garantir que a entrega não foi adulterada, você deve validar a assinatura do webhook antes de continuar a processar a entrega. Isso o ajudará a evitar gastar tempo do servidor para processar entregas que não são da GitHub e ajudará a evitar ataques man-in-the-middle.

Para fazer isso, você precisa:

  1. Crie um token secreto para um webhook.
  2. Armazene o token de forma segura no seu servidor.
  3. Valide as cargas do webhook de entrada em relação ao token, para verificar se elas são provenientes do GitHub e se não foram adulteradas.

Criando um token secreto

Você pode criar um novo webhook com um token secreto ou pode adicionar um token secreto a um webhook existente. Ao criar um token secreto, você deve escolher uma sequência aleatória de texto com alta entropia.

  • Para criar um novo webhook com um token secreto, consulte "Criar webhooks".
  • Para adicionar um token secreto a um webhook existente, edite as configurações do webhook. Em "Segredo", digite uma cadeia de caracteres para usar como uma chave secret. Para obter mais informações, confira "Editando webhooks".

Armazenamento seguro de tokens secretos

Depois de criar um token secreto, você deve armazená-lo em um local seguro que seu servidor possa acessar. Nunca codifique um token em um aplicativo ou envie um token para qualquer repositório. Para obter mais informações sobre como usar credenciais de autenticação de forma segura em seu código, consulte "Manter suas credenciais de API seguras".

Validação de entregas de webhooks

O GitHub Enterprise Cloud usará seu token secreto para criar uma assinatura de hash que será enviada a você com cada carga. A assinatura do hash aparecerá em cada entrega como o valor do cabeçalho X-Hub-Signature-256. Para obter mais informações, confira "Eventos e cargas de webhook".

Em seu código que lida com entregas de webhooks, você deve calcular um hash usando seu token secreto. Em seguida, compare o hash que o GitHub enviou com o hash esperado que você calculou e certifique-se de que eles correspondem. Para ver exemplos que mostram como validar os hashes em várias linguagens de programação, consulte "Exemplos".

Há alguns aspectos importantes a serem considerados ao validar cargas de webhooks:

  • GitHub Enterprise Cloud usa um código hash hexadecimal HMAC para calcular o hash.
  • A assinatura do hash sempre começa com sha256=.
  • A assinatura de hash é gerada usando o token secreto do seu webhook e o conteúdo da carga útil.
  • Se o seu idioma e a implementação de servidor especificarem uma codificação de caracteres, certifique-se de que você manipula a carga como UTF-8. As cargas do webhook podem conter caracteres unicode.
  • Nunca use um operador simples ==. Em vez disso, considere usar um método como secure_compare ou crypto.timingSafeEqual que executa uma comparação de cadeia de caracteres em "tempo constante" para ajudar a atenuar determinados ataques de tempo contra operadores de igualdade regulares ou loops regulares em linguagens otimizadas por JIT.

Testando a validação da carga útil do webhook

Você pode usar os seguintes valores secret e payload para verificar se sua implementação está correta:

  • secret: "É um segredo para todos"
  • payload: "Hello, World!"

Se sua implementação estiver correta, as assinaturas que você gerar deverão corresponder aos seguintes valores de assinatura:

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

Exemplos

Você pode usar a linguagem de programação de sua preferência para implementar a verificação HMAC em seu código. A seguir, alguns exemplos que mostram como uma implementação pode ser feita em várias linguagens de programação.

Exemplo de Ruby

Por exemplo, você pode definir a seguinte função verify_signature:

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

Em seguida, você pode chamá-lo quando receber um conteúdo 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

Exemplo de Python

Por exemplo, você pode definir a seguinte função verify_signature e chamá-la quando receber um conteúdo 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!")

Exemplo de JavaScript

Por exemplo, você pode definir a seguinte função verifySignature e chamá-la em qualquer ambiente JavaScript quando receber um conteúdo 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;
}

Exemplo de TypeScript

Por exemplo, você pode definir a seguinte função verify_signature e chamá-la quando receber um conteúdo de webhook:

JavaScript
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
};

Solução de problemas

If you are sure that the payload is from GitHub but the signature verification fails:

  • Make sure that you have configured a secret for your webhook. The X-Hub-Signature-256 header will not be present if you have not configured a secret for your webhook. For more information about configuring a secret for your webhook, see "Editando webhooks."
  • Make sure you are using the correct header. GitHub recommends that you use the X-Hub-Signature-256 header, which uses the HMAC-SHA256 algorithm. The X-Hub-Signature header uses the HMAC-SHA1 algorithm and is only included for legacy purposes.
  • Make sure that you are using the correct algorithm. If you are using the X-Hub-Signature-256 header, you should use the HMAC-SHA256 algorithm.
  • Make sure you are using the correct webhook secret. If you don't know the value of your webhook secret, you can update your webhook's secret. For more information, see "Editando webhooks."
  • Make sure that the payload and headers are not modified before verification. For example, if you use a proxy or load balancer, make sure that the proxy or load balancer does not modify the payload or headers.
  • If your language and server implementation specifies a character encoding, ensure that you handle the payload as UTF-8. Webhook payloads can contain unicode characters.

Leitura adicional