Комментарии 2
Свой gRPC-wire вместо стандартного стека — это круто, но есть один архитектурный нюанс по безопасности.
В секции про Tsak вы пишете: «берётся самый правый хоп X-Forwarded-For, тот, который дописал доверенный прокси и который клиент подделать не может».
Если перед вашим приложением стоит цепочка балансировщиков (например, внешний Anti-DDoS -> ваш краевой Nginx), то самым правым хопом в заголовке окажется IP-адрес внешнего шлюза, а не реального атакующего. Выдав бан по этому IP при переборе, вы положите доступ вообще всем легитимным пользователям, идущим через этот узел.
Безопаснее парсить заголовок X-Forwarded-For справа налево, последовательно отбрасывая статические IP-адреса ваших собственных доверенных прокси. Первый же неизвестный адрес в цепочке с конца — это и есть настоящий IP клиента.
Вы правы, и попали ровно в место, где это так. В Tsak троттл API-ключей берёт parts[^1] из XFF без списка доверенных прокси, модель однохоповая. В changelog 3.7.0 это допущение записано словами «assumes exactly one trusted proxy», а в статью оговорка не доехала. При цепочке Anti-DDoS → nginx правый хоп это адрес шлюза, и все за ним делят одну корзину. Причём это хуже, чем блокировка всех: правило «успешный вход обнуляет счётчик» в общей корзине сбрасывает счётчик и атакующему.
Ваш рецепт правильный, и он у нас уже есть, только в соседнем продукте: TrustedProxyResolverProcessor в redb.Identity делает KnownProxies/KnownNetworks, проверяет сокетный пир и идёт справа налево до первого недоверенного адреса. Tsak на эту модель переведём, единственная поправка к рецепту: нераспознаваемый хоп должен обрывать обход, а не пропускаться, иначе обход уедет в клиентскую часть заголовка. Статью поправлю, чтобы правый хоп не звучал как безусловное свойство. Спасибо, замечание нашло расхождение между двумя реализациями, а не опечатку.
Согласен, мне надо конечно внимательней надо быть.

redb 3.7: свой gRPC-протокол, отсечение props до агрегата и релиз, отозванный через день