Все записи
5 мин чтенияКоманда Mitelis

Новые операторы в правилах: wildcard, starts_with и ends_with

Восемь строковых операторов в движке правил: что покрывает символ *, чем строгий wildcard отличается от обычного, как ведут себя not_-проверки без поля и какой оператор выбирать.

Шаблон /api/* и строки, которые под него подходят

Раньше сравнение строк в правилах сводилось к точному совпадению и поиску подстроки. Условия вида «все пути под /api/», «любой поддомен example.com» или «Content-Type, оканчивающийся на /json» приходилось собирать из нескольких правил или приближать через contains, захватывая лишнее. Теперь каждое из них записывается одним оператором.

Что добавили

Восемь операторов, четыре пары «прямой / отрицательный»:

Оператор Что проверяет Регистр
wildcard / not_wildcard вся строка по шаблону с * не учитывается
strict_wildcard / not_strict_wildcard вся строка по шаблону с * учитывается
starts_with / not_starts_with строка начинается со значения учитывается
ends_with / not_ends_with строка заканчивается значением учитывается

Операторы применяются к полям hostname, uri_path, uri, ua, ja4, method, scheme, а также к любому заголовку и cookie через header.<name> и cookie.<name>. Условие описывается тремя ключами — поле, оператор, значение:

{
  "field": "uri_path",
  "op": "wildcard",
  "value": "/api/*"
}

Как работает wildcard

Правил у шаблона три, и все они важны на практике.

Сравнивается вся строка. Шаблон не ищется внутри значения, а прикладывается к нему целиком. /api/* совпадёт с /api/users, но не с /v2/api/users: строка должна начинаться так, как начинается шаблон. Если нужно «где-то внутри», ставьте * с обеих сторон — */api/*.

* — ноль или больше любых символов, включая /. Один * покрывает и пустой хвост, и вложенные пути любой глубины: /api/* подходит к /api/, /api/users и /api/v2/users/42/orders. Звёздочек в шаблоне может быть несколько.

wildcard не различает регистр, strict_wildcard различает. Для /api/* обычный wildcard пропустит и /API/USERS. Если бэкенд чувствителен к регистру и /Admin/ для него не то же самое, что /admin/, — берите строгий вариант.

Несколько типичных шаблонов:

Шаблон Совпадает Не совпадает
/api/* /api/, /api/v1/users /v2/api/, /apix
*/admin/* /admin/, /ru/admin/users /administrator
*.php /index.php, /a/b/c.PHP /index.php5
*.example.com для hostname cdn.example.com, a.b.example.com example.com, example.com.evil.net
* любая строка —

Шаблон с несколькими * удобен для User-Agent, где между постоянными частями стоят версии:

{
  "field": "ua",
  "op": "wildcard",
  "value": "Mozilla/5.0 (Linux; Android *) AppleWebKit/*"
}

Специальный символ в шаблоне один — *. Знак ? никакой роли не играет и сравнивается как обычный символ. Если звёздочка нужна буквально, экранируйте её обратным слешем: \*; сам обратный слеш записывается как \\. В JSON обратный слеш тоже экранируется, поэтому в файле правила это выглядит так:

{
  "field": "uri_path",
  "op": "strict_wildcard",
  "value": "/files/\\*/*"
}

Такой шаблон совпадёт с /files/*/report.pdf, но не с /files/2024/report.pdf.

Три варианта шаблона движок отклонит как невалидные: пустое значение, незакрытый escape (обратный слеш в конце строки) и две звёздочки подряд без экранирования — **. Одна * уже покрывает любую последовательность, поэтому ** избыточна и считается ошибкой.

Сравнение операторов wildcard, strict_wildcard и starts_with на четырёх путях
Как один и тот же набор путей проходит через разные операторы

starts_with и ends_with

Это частные случаи wildcard, которые читаются однозначно и не требуют думать о звёздочках. Оба чувствительны к регистру — как strict_wildcard, а не как wildcard.

starts_with закрывает разделы сайта и группы маршрутов:

{
  "field": "uri_path",
  "op": "starts_with",
  "value": "/admin/"
}

ends_with удобен для расширений файлов, суффиксов доменов и типов содержимого. Условие ниже покрывает application/json, application/problem+json и любой другой тип с этим окончанием без перечисления:

{
  "field": "header.Content-Type",
  "op": "ends_with",
  "value": "/json"
}

Оба оператора работают и с отпечатком ja4: общий префикс отпечатка позволяет отобрать группу похожих клиентов одним условием, не перечисляя полные значения.

Отрицательные проверки и отсутствующие поля

У каждого оператора есть вариант с префиксом not_. Главная тонкость — поведение, когда поля в запросе нет: отсутствующий заголовок или cookie делает отрицательную проверку истинной. Условие ниже совпадёт и с запросом, где session начинается не с prod_, и с запросом вообще без этой cookie:

{
  "field": "cookie.session",
  "op": "not_starts_with",
  "value": "prod_"
}

Обычно это то, что нужно: «пропустить всех, у кого нет продовой сессии». Если же поле должно присутствовать обязательно, объедините exists и not_* в одной группе all — запросы без cookie перестанут проходить:

{
  "all": [
    { "field": "cookie.session", "op": "exists" },
    { "field": "cookie.session", "op": "not_starts_with", "value": "prod_" }
  ]
}

Для полей, которые есть в каждом запросе, — uri_path, hostname, method, scheme — этой оговорки нет, отрицание там работает буквально.

Какой оператор выбрать

  • Нужен раздел сайта или префикс маршрута — starts_with. Если регистр не важен — wildcard с * в конце.
  • Нужны расширение, суффикс домена или типа содержимого — ends_with.
  • Нужен фрагмент в середине или несколько переменных частей — wildcard с несколькими *.
  • Регистр имеет значение — strict_wildcard, starts_with или ends_with; обычный wildcard регистр игнорирует.
  • Заголовок или cookie не должны иметь определённого вида, но при этом обязаны присутствовать — not_* вместе с exists в группе all.

Если раньше вы использовали contains для условий вида «путь начинается с…», стоит перевести их на starts_with: contains для /admin/ срабатывает и на /blog/admin/guide, а starts_with — нет.

Что дальше

Новые операторы уже доступны, существующие правила менять не нужно. Правила настраиваются в рабочем пространстве, вопросы — в нашем Telegram-канале.