Harvester тянет данные из внешних сетей, и защищать нужно сам канал приёма — два вектора: проверка входящих webhook'ов и сдерживание частоты обращений к чужим API. Узко про канал: шифрование учётных данных держит sources, а перенос прав доступа из источника — acl-identity.
if на каждого провайдера: ядро держит
единый механизм проверки, а
коннектор
приносит свой подключаемый верификатор — он объявляет
схему подписи, способ извлечь ключ дедупликации и есть ли в
доставке метка времени. Ядро прогоняет три шага в строгом
порядке свежесть → подпись → дедуп (дёшево →
дорого): сначала отсеять по метке времени и заголовкам, и лишь
то, что прошло подпись, пускать в дедуп — иначе неаутентичный
поток сам станет DoS на хранилище дедупа. Новый источник =
новый верификатор; ядро не трогаем.
SET key NX EX — первый раз ставит, повтор видит
ключ и отбрасывается.
TTL зависит от метки: 15 минут там, где есть
timestamp (свежесть уже отрезала старое), и 24 часа там, где
метки нет — чтобы ловить и ручные redelivery. Сам канал —
только по TLS (терминирует reverse-proxy).
v0:ts:body, метка есть (окно 5 мин), дедуп
по event_id.
X-Hub-Signature-256, метки нет, дедуп по
X-GitHub-Delivery.
Idempotency-Key / Event-UUID.
X-Hub-Signature (admin) или JWT (Connect),
метки нет, дедуп по
X-Atlassian-Webhook-Identifier.
tier) потолками. Поэтому ключ лимитера
строится по rate_limit_scope из
манифеста коннектора
(tenant / account_token / workspace_method / site), а не по
абстракции «источник» — общего числа на всех нет. Манифест же
объявляет стартовый и максимальный темп: безопасная отправная
точка под типовой тариф, реальную же границу рантайм нащупывает
по ответам самого источника.
429 — резкий откат ×0.5
(additive-increase / multiplicative-decrease). Пол и старт —
темп из манифеста, потолок — его же максимум. Текущий темп
не сбрасываем между прогонами — это выученная
ёмкость API; в Redis он живёт с TTL в часы. Исполнитель —
распределённый token-bucket в Redis, списание атомарно (Lua),
ключ per-scope, переживает рестарт воркера.
Retry-After — жёсткая пауза на весь scope, её
уважают все воркеры, она перекрывает AIMD. А если
X-RateLimit-Remaining мал — тормозим заранее:
safe_rps = Remaining / (Reset − now), берём
min с темпом AIMD.