Чтобы протестировать вебхук GitLab на локальном хосте, откройте ваш локальный обработчик через HTTPS-туннель, добавьте этот URL в Настройки → Вебхуки, сгенерируйте токен подписи и проверяйте стандартную подпись вебхуков GitLab перед разбором полезной нагрузки. Вызовите push или merge request, проверьте доставку и итеративно работайте локально без деплоя интеграции после каждого изменения.
Используйте токены подписи GitLab, а не новый простой текстовый секретный токен
GitLab поддерживает два механизма, которые легко перепутать. Старый секретный токен копируется в заголовок X-Gitlab-Token запроса. Он подтверждает знание общего значения, но не защищает целостность тела. GitLab теперь рекомендует токен подписи для новых вебхуков. Он создает HMAC-SHA256 подпись и соответствует формату сообщений стандартных вебхуков.
официальная документация GitLab по вебхукам говорит, что подписанный запрос содержит webhook-id, webhook-timestamp и webhook-signature. Подпись охватывает идентификатор сообщения, временную метку и точное исходное тело JSON. Это защищает как источник, так и целостность полезной нагрузки.
Реализуйте проверку стандартных вебхуков в Node.js
Токены подписи GitLab отображаются один раз и используют префикс whsec_ . Уберите этот префикс и декодируйте оставшуюся часть в Base64, чтобы получить ключ HMAC. Каждая полученная подпись имеет форму v1,<base64 signature>; заголовок может содержать несколько подписьей, разделённых пробелами.
import crypto from 'node:crypto';
function safeEqual(a, b) {
const left = Buffer.from(a);
const right = Buffer.from(b);
return left.length === right.length &&
crypto.timingSafeEqual(left, right);
}
function verifyGitLabWebhook({ token, id, timestamp, body, signatures }) {
if (!token?.startsWith('whsec_') || !id || !timestamp || !signatures) {
return false;
}
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false;
const key = Buffer.from(token.slice(6), 'base64');
const message = `${id}.${timestamp}.${body}`;
const digest = crypto.createHmac('sha256', key).update(message).digest('base64');
const expected = `v1,${digest}`;
return signatures.split(' ').some((value) => safeEqual(value, expected));
}
Пятиминутное окно временной метки, показанное здесь, является политикой приложения, а не значением для слепого копирования. Выберите допуск, который учитывает разброс часов, но блокирует полезное повторное воспроизведение. Синхронизируйте часы принимающей машины. Храните каждую принятую webhook-id при уникальном ограничении, потому что только проверка новой метки времени не может предотвратить две мгновенные доставки одного и того же сообщения.
Постройте маршрут Express webhook
Захватите исходное тело на этом маршруте. Глобальный вызов express.json() до проверки уничтожает представление байт за байтом, подписанное GitLab.
import express from 'express';
const app = express();
app.post(
'/webhooks/gitlab',
express.raw({ type: 'application/json', limit: '2mb' }),
async (req, res) => {
const body = req.body.toString('utf8');
const valid = verifyGitLabWebhook({
token: process.env.GITLAB_WEBHOOK_SIGNING_TOKEN,
id: req.get('webhook-id'),
timestamp: req.get('webhook-timestamp'),
signatures: req.get('webhook-signature'),
body,
});
if (!valid) return res.sendStatus(401);
await inbox.insertOnce(req.get('webhook-id'), JSON.parse(body));
return res.sendStatus(202);
},
);
app.use(express.json());
app.listen(3000);
Монтировать обычный JSON-парсер после маршрута webhook или использовать его callback verify для сохранения сырого буфера. Никогда не отключайте проверки подписи только потому, что конечная точка пересылается на localhost; URL туннеля всё ещё доступен из публичного интернета.
Создайте публичную HTTPS-конечную точку
- Запустите интеграцию локально и протестируйте её маршрут с помощью намеренно неподписанного запроса. Он должен вернуть 401, подтверждая активную аутентификацию.
- Запустите
npx portpreview 3000в другом терминале. - Скопируйте исходный код HTTPS и добавьте
/webhooks/gitlab. - Держите туннель открытым на протяжении всей конфигурации и тестирования событий.
Проверка SSL в GitLab должна оставаться включенной. Туннель с общедоступным доверенным TLS предотвращает ошибки с самоподписанными сертификатами. Если GitLab работает в частной сети с самостоятельным управлением, он также должен иметь исходящий доступ к общедоступному URL туннеля.
Настройте вебхук проекта
- Откройте проект GitLab и выберите Настройки → Вебхуки.
- Выберите Добавить новый вебхук и вставьте полный URL доставки туннеля.
- Выберите Сгенерировать токен подписи, сразу скопируйте токен и сохраните его в
GITLAB_WEBHOOK_SIGNING_TOKEN. - Выберите только необходимые триггеры — например, события Push, события Merge request, события Tag push или события Pipeline.
- Оставьте включенной проверку SSL и сохраните вебхук.
- Используйте тестовое действие GitLab или создайте реальное событие, затем проверьте локальный запрос и историю доставки GitLab.
Перезапустите локальный процесс после установки переменной окружения. Если вы переносите существующую интеграцию, GitLab позволяет использовать токен подписи и устаревший секретный токен одновременно. Проверьте webhook-signature при наличии, временно возвращайтесь к X-Gitlab-Token, затем удалите более слабый секрет после того, как все получатели будут поддерживать подписи.
Отправляйте события GitLab по заголовку и полезной нагрузке
X-Gitlab-Event , что дает читаемое имя события, такое как Push Hook или Merge Request Hook. Используйте его для маршрутизации, но также проверяйте object_kind полезной нагрузки. Это делает видимыми неожиданные комбинации.
switch (req.get('x-gitlab-event')) {
case 'Push Hook':
await handlePush(payload);
break;
case 'Merge Request Hook':
await handleMergeRequest(payload);
break;
case 'Pipeline Hook':
await handlePipeline(payload);
break;
default:
await recordUnsupportedGitLabEvent(payload.object_kind);
}
События Push
Создание тестовой ветки, обычные коммиты, принудительные пуши и удаление ветки. Нулевой SHA может представлять отсутствующую сторону перехода ссылки. Крупные пуши могут отличаться от фикстуры с одним коммитом, поэтому не следует предполагать, что каждый измененный коммит появляется в неограниченном массиве. Используйте идентификаторы проекта и ссылки, а не разбирайте строку отображения.
События запросов на слияние
Действия, такие как открытие, обновление, одобрение, слияние и закрытие, могут иметь один и тот же широкий тип события. Маршрутизируйте по документированным атрибутам объекта и делайте повторяющиеся обновления идемпотентными. Никогда не сливайте код и не одобряйте развертывание только потому, что изменяемый заголовок или имя пользователя совпадают.
События конвейера и заданий
Они могут быть частыми. Фильтруйте на GitLab и снова в вашем обработчике по проекту, ветке, статусу и среде. Очередь медленной работы с артефактами или развертыванием и сначала подтверждайте вебхук.
Проектирование для повторов и рекурсивных триггеров
GitLab включает webhook-id, который остается неизменным при повторах и равен старому Idempotency-Key. Используйте его в качестве ключа идемпотентности доставки. X-Gitlab-Webhook-UUID идентифицирует выполнение вебхука, в то время как X-Gitlab-Event-UUID может помочь отслеживать события; рекурсивные вебхуки могут использовать общий UUID события.
Если обработчик изменяет GitLab через API, он может создать другой вебхук. Добавьте явную защиту от циклов: тегируйте действия своей идентичностью интеграции, игнорируйте изменения, которые не изменяют желаемое состояние, и ограничьте переходы рабочего процесса. Руководство по повторам и идемпотентности рассматривает шаблоны транзакционных входящих сообщений.
Устранение неисправностей при сбое тестов вебхуков GitLab
GitLab не удается подключиться к URL
Подтвердите, что процесс туннеля активен, полный путь правильный, и ваш локальный сервер слушает на перенаправленном порту. Для самостоятельно управляемого GitLab проверьте политику исходящей сети и DNS. Не отключайте проверку SSL для скрытия несвязанного сбоя маршрутизации.
Подпись никогда не совпадает
Используйте токен подписи, а не старый секретный токен. Уберите whsec_, декодируйте оставшийся токен из Base64 и подпишите {webhook-id}.{webhook-timestamp}.{raw body}. Закодируйте двоичный HMAC-дigest в Base64 и добавьте префикс v1,. Сравните с каждой подписью, разделенной пробелами.
Отметка времени отклонена
Проверьте системное время и обработку часового пояса; заголовок — это Unix-отметка времени в секундах. Не сравнивайте его с миллисекундами JavaScript без деления на 1000. Если вы отлаживаете захваченный старый запрос, отклонение отметки времени — это корректная защита от повторного воспроизведения.
GitLab отключает вебхук или снижает частоту его вызовов
Проверьте недавний статус доставки и ответ вашего маршрута. Быстро возвращайте 2xx после надежного принятия. Повторяющийся 401 означает неправильную конфигурацию токена; повторяющийся 5xx означает сбои обработчика; тайм-ауты указывают на слишком большое количество синхронной работы.
Поступают только некоторые события
Проверьте выбранные триггеры и фильтры веток. Вебхуки групп и проектов имеют разные области действия. Подтвердите, что событие произошло именно в том проекте, где настроен этот вебхук.
Храните данные локального вебхука GitLab в безопасности
- Храните токены подписи только в проигнорированных файлах окружения и меняйте любой утекший токен.
- Проверяйте подписи, временные метки, идентификаторы проектов и допустимые типы событий перед побочными эффектами.
- Редактируйте сообщения коммитов, URL-адреса приватных репозиториев, электронные адреса пользователей и переменные CI в захваченных данных.
- Предоставляйте токен интеграционного API только с теми правами, которые нужны для его дальнейших действий.
- Удаляйте локальную историю полезной нагрузки по завершении тестирования.
Для независимой от провайдера диагностики используйте руководство по отладке локального вебхука. GitHub использует другой формат подписи, поэтому обращайтесь к отдельному руководству по вебхукам GitHub , а не используйте его проверщик повторно.
