Webhooks

Validación de webhooks

Acerca de la validación de entregas de webhook

Una vez que su servidor esté configurado para recibir payloads, quedará a la espera de cualquier entrega que se envíe al endpoint que configuró. Para garantizar que su servidor procese únicamente las entregas de webhook enviadas por Fluid y para asegurarse de que la entrega no haya sido manipulada, debe validar la firma del webhook antes de continuar procesando la entrega. Esto le ayudará a evitar que su servidor dedique tiempo a procesar entregas que no provienen de Fluid y ayudará a evitar ataques man-in-the-middle.


Para hacer esto, necesita:

  1. Crear un token secreto para un webhook.

  2. Almacenar el token de forma segura en su servidor.

  3. Validar los payloads entrantes del webhook contra el token, para verificar que provienen de Fluid y que no fueron manipulados.

Crear un token secreto

Puede crear un nuevo webhook con un token secreto, o puede agregar un token secreto a un webhook existente. Al crear un token secreto, debe elegir una cadena de texto aleatoria con alta entropía.

Para crear o editar la integración de webhook con un token secreto, consulte Cómo configurar la integración de Webhooks con Fluid Boards

Almacenar el token secreto de forma segura

Después de crear un token secreto, debe almacenarlo en una ubicación segura a la que su servidor pueda acceder. Nunca incluya un token codificado en una aplicación ni lo envíe a ningún repositorio. 

Validar las entregas de webhook

Fluid usará su token secreto para crear una firma hash que se le envía con cada payload. La firma hash aparecerá en cada entrega como el valor del encabezado X-Hub-Signature-256. Para más información, consulte "Eventos y payloads de webhook."

En su código que gestiona las entregas de webhook, debe calcular un hash usando su token secreto. Luego, compare el hash que envió Fluid con el hash esperado que calculó, y asegúrese de que coincidan. Para ver ejemplos de cómo validar los hashes en varios lenguajes de programación, consulte "Ejemplos" a continuación

Hay algunas cosas importantes a tener en cuenta al validar payloads de webhook:

  • Fluid usa un digest hexadecimal HMAC para calcular el hash.

  • La firma hash se genera usando el token secreto de su webhook y el contenido del payload.

  • Si su lenguaje e implementación de servidor especifican una codificación de caracteres, asegúrese de manejar el payload como UTF-8. Los payloads de webhook pueden contener caracteres unicode.

  • Nunca use un operador == simple. En su lugar, considere usar un método como secure_compare o crypto.timingSafeEqual, que realiza una comparación de cadenas en "tiempo constante" para ayudar a mitigar ciertos ataques de temporización contra los operadores de igualdad regulares, o los bucles regulares en lenguajes optimizados por JIT.

Probar la validación del payload del webhook

Puede usar los siguientes valores de secreto y payload para verificar que su implementación sea correcta:

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

  • payload: "Hello, World!"

Si su implementación es correcta, las firmas que genere deben coincidir con los siguientes valores de firma:

signature: 757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17

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


Ejemplos

Puede usar el lenguaje de programación de su preferencia para implementar la verificación HMAC en su código. A continuación se muestran algunos ejemplos de cómo podría verse una implementación en varios lenguajes de programación.

Ejemplo en Node.js

Por ejemplo, puede definir la siguiente función verifySignature y llamarla en cualquier entorno JavaScript cuando reciba un 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');
    }
}

Ejemplo en Javascript

Por ejemplo, puede definir la siguiente función verify_signature y llamarla cuando reciba un 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;
}


Ejemplo en TypeScript

Por ejemplo, puede definir la siguiente función verify_signature y llamarla cuando reciba un 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
};


Ejemplo en Ruby

Por ejemplo, puede definir la siguiente función 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


Luego puede llamarla cuando reciba un 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


Ejemplo en Python

Por ejemplo, puede definir la siguiente función verify_signature y llamarla cuando reciba un 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!")


Solución de problemas

Si está seguro de que el payload proviene de Fluid pero la verificación de la firma falla:

  • Asegúrese de haber configurado un secreto para su webhook. El encabezado X-Hub-Signature-256 no estará presente si no ha configurado un secreto para su webhook. Los métodos de autenticación alternativos, como Basic y PAT, usan el encabezado de autorización de solicitud http específico.

  • Asegúrese de estar usando el algoritmo correcto. Si está usando el encabezado X-Hub-Signature-256, debe usar el algoritmo HMAC-SHA256.

  • Asegúrese de estar usando el secreto de webhook correcto. Si no conoce el valor de su secreto de webhook, puede actualizar el secreto de su webhook. Para más información, consulte Cómo configurar la integración de Webhooks con Fluid Boards

  • Asegúrese de que el payload y los encabezados no se modifiquen antes de la verificación. Por ejemplo, si usa un proxy o un balanceador de carga, asegúrese de que el proxy o el balanceador de carga no modifique el payload ni los encabezados.

  •  Si está usando una biblioteca en particular y accede a los parámetros de la solicitud a través de funciones de la biblioteca, asegúrese de leer el payload como texto; algunas bibliotecas analizarán el payload y reformatearán los datos, lo cual también puede variar entre diferentes lenguajes, por ejemplo, 1.0 puede cambiarse a 1. La validación de la firma fallará, ya que los payloads no son iguales.

  • Si su lenguaje e implementación de servidor especifican una codificación de caracteres, asegúrese de manejar el payload como UTF-8. Los payloads de webhook pueden contener caracteres unicode.



Lecturas adicionales

Was this article helpful?