Все статьи
События push и merge request в Git-репозитории, проходящие через подписанный HTTPS-туннель в локальный сервис разработки.
GitLabDevOpswebhookslocalhost

Тестирование Webhook GitLab на локальном хосте безопасно

Чтобы протестировать вебхук 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-конечную точку

  1. Запустите интеграцию локально и протестируйте её маршрут с помощью намеренно неподписанного запроса. Он должен вернуть 401, подтверждая активную аутентификацию.
  2. Запустите npx portpreview 3000 в другом терминале.
  3. Скопируйте исходный код HTTPS и добавьте /webhooks/gitlab.
  4. Держите туннель открытым на протяжении всей конфигурации и тестирования событий.

Проверка SSL в GitLab должна оставаться включенной. Туннель с общедоступным доверенным TLS предотвращает ошибки с самоподписанными сертификатами. Если GitLab работает в частной сети с самостоятельным управлением, он также должен иметь исходящий доступ к общедоступному URL туннеля.

Настройте вебхук проекта

  1. Откройте проект GitLab и выберите Настройки → Вебхуки.
  2. Выберите Добавить новый вебхук и вставьте полный URL доставки туннеля.
  3. Выберите Сгенерировать токен подписи, сразу скопируйте токен и сохраните его в GITLAB_WEBHOOK_SIGNING_TOKEN.
  4. Выберите только необходимые триггеры — например, события Push, события Merge request, события Tag push или события Pipeline.
  5. Оставьте включенной проверку SSL и сохраните вебхук.
  6. Используйте тестовое действие 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 , а не используйте его проверщик повторно.

Часто задаваемые вопросы

Как протестировать вебхук GitLab на локальном хосте?
Откройте локальный маршрут через HTTPS-туннель, добавьте его публичный URL в раздел Webhooks проекта GitLab, настройте токен подписи и триггеры, затем сгенерируйте тестовое или реальное событие.
Следует ли новым вебхукам GitLab использовать X-Gitlab-Token?
GitLab рекомендует использовать токены подписи для новых вебхуков. X-Gitlab-Token содержит секрет в открытом виде, тогда как токены подписи аутентифицируют HMAC-SHA256 дайджест запроса.
Как рассчитывается подпись вебхука GitLab?
Декодируйте токен подписи после удаления префикса whsec_, вычислите HMAC-SHA256 строки webhook-id.webhook-timestamp.raw-body, закодируйте результат в Base64 и добавьте к нему префикс v1,.
Как мне предотвратить повторные действия вебхука GitLab?
Сохраняйте webhook-id с уникальным ограничением и применяйте побочные эффекты транзакционно. GitLab сохраняет этот ID стабильным при повторных попытках, что делает его подходящим для идемпотентности.