Webhooks

Validando Webhooks

Sobre a Validação de Entregas de Webhook

Depois que seu servidor estiver configurado para receber payloads, ele ficará aguardando qualquer entrega enviada ao endpoint que você configurou. Para garantir que seu servidor processe apenas entregas de webhook enviadas pelo Fluid e para assegurar que a entrega não foi adulterada, você deve validar a assinatura do webhook antes de processar a entrega. Isso ajudará a evitar o gasto de tempo do servidor para processar entregas que não são do Fluid e ajudará a evitar ataques man-in-the-middle.


Para fazer isso, você precisa:

  1. Criar um token secreto para um webhook.

  2. Armazenar o token com segurança em seu servidor.

  3. Validar os payloads de webhook recebidos em relação ao token, para verificar se eles estão vindo do Fluid e não foram adulterados.

Criando um token secreto

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

Para criar/editar a integração de webhook com um token secreto, consulte Como configurar a integração de Webhooks com o Fluid Boards

Armazenando o token secreto com segurança

Após criar um token secreto, você deve armazená-lo em um local seguro que seu servidor possa acessar. Nunca codifique um token diretamente em uma aplicação ou envie um token para qualquer repositório. 

Validando entregas de webhook

O Fluid usará seu token secreto para criar uma assinatura hash que é enviada a você com cada payload. A assinatura hash aparecerá em cada entrega como o valor do cabeçalho X-Hub-Signature-256. Para mais informações, consulte "Eventos e payloads de webhook."

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

Há algumas coisas importantes a ter em mente ao validar payloads de webhook:

  • O Fluid usa um digest hex HMAC para calcular o hash.

  • A assinatura hash é gerada usando o token secreto do seu webhook e o conteúdo do payload.

  • Se sua linguagem e implementação de servidor especificarem uma codificação de caracteres, certifique-se de tratar o payload como UTF-8. Payloads de 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 realiza uma comparação de strings em "tempo constante" para ajudar a mitigar certos ataques de temporização contra operadores de igualdade regulares, ou loops regulares em linguagens otimizadas por JIT.

Testando a validação do payload de webhook

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

  • secret: "It's a Secret to Everybody"

  • payload: "Hello, World!"

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

signature: 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 estão alguns exemplos mostrando como uma implementação pode parecer em várias linguagens de programação.

Exemplo em Node.js

Por exemplo, você pode definir a seguinte função verifySignature e chamá-la em qualquer ambiente JavaScript quando receber um payload de webhook:

const crypto = require('crypto');

function validateSignature(secret, req, res, next) {
	
    const sharedSecret = secret; 
        const signature = req.headers['x-hub-signature-256'];
	const payload = req.body;
	
    if (!signature) {
		console.log(`X-Hub-Signature header missing`); 
        return res.status(400).send('X-Hub-Signature-256 header missing');
    }

	const hmac = crypto.createHmac('sha256', sharedSecret);
	const calculatedSignature = hmac.update(payload).digest('hex');

     if (crypto.timingSafeEqual(Buffer.from(signature), Buffer.from('sha256=' + calculatedSignature))) {
	    console.log(`X-Hub-Signature-256 is VALID`);
        next();
    } else {
		console.log(`X-Hub-Signature-256 is INVALID`);
        return res.status(403).send('Invalid X-Hub-Signature-256');
    }
}

Exemplo em Javascript

Por exemplo, você pode definir a seguinte função verify_signature e chamá-la quando receber um payload 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 em TypeScript

Por exemplo, você pode definir a seguinte função verify_signature e chamá-la quando receber um payload 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
};


Exemplo em 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á-la quando receber um payload 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 em Python

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

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

    Raise and return 403 if not authorized.

    Args:
        payload_body: original request body to verify (request.body())
        secret_token: Fluid app webhook token (WEBHOOK_SECRET)
        signature_header: header received from Fluid (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!")


Solução de problemas

Se você tiver certeza de que o payload é do Fluid, mas a verificação de assinatura falhar:

  • Certifique-se de que você configurou um segredo para o seu webhook. O cabeçalho X-Hub-Signature-256 não estará presente se você não tiver configurado um segredo para o seu webhook. Métodos de autenticação alternativos, como Basic e PAT, usam o cabeçalho de autorização de solicitação http específico.

  • Certifique-se de que está usando o algoritmo correto. Se você estiver usando o cabeçalho X-Hub-Signature-256, deverá usar o algoritmo HMAC-SHA256.

  • Certifique-se de que está usando o segredo de webhook correto. Se você não souber o valor do seu segredo de webhook, poderá atualizar o segredo do seu webhook. Para mais informações, consulte Como configurar a integração de Webhooks com o Fluid Boards

  • Certifique-se de que o payload e os cabeçalhos não sejam modificados antes da verificação. Por exemplo, se você usar um proxy ou balanceador de carga, certifique-se de que o proxy ou balanceador de carga não modifique o payload ou os cabeçalhos.

  •  Se você estiver usando uma biblioteca específica e acessar os parâmetros da solicitação por meio de funções da biblioteca, certifique-se de ler o payload como texto. Algumas bibliotecas analisarão o payload e reformatarão os dados, o que também pode variar entre diferentes linguagens, por exemplo, 1.0 pode ser alterado para 1. A validação da assinatura falhará, pois os payloads não são os mesmos.

  • Se sua linguagem e implementação de servidor especificarem uma codificação de caracteres, certifique-se de tratar o payload como UTF-8. Os payloads de webhook podem conter caracteres unicode.



Leitura Adicional

Was this article helpful?