← Cloudflare Ruleset Engine / ruleset-engine / rules-language
Значения
Когда HTTP-запрос достигает глобальной сети Cloudflare, Cloudflare формирует таблицу пар «поле-значение», по которым сопоставляются выражения. Эта таблица существует, пока обрабатывается текущий запрос.
Значения, которые заполняют таблицы поиска Rules language, поступают из различных источников:
- Примитивные свойства получаются непосредственно из запроса (
http.request.uri.path, например). - Производные значения являются результатом преобразования, объединения или базовой операции. Например, преобразование
lower(http.request.uri.path)преобразует значениеhttp.request.uri.pathв нижний регистр. - Вычисляемые значения являются результатом поиска, вычисления или иного анализа. Например, Cloudflare использует процесс машинного обучения для динамического расчета показателей атак, представленных
cf.waf.score*поля.
Помимо этих значений, выражения могут содержать литеральные значения: статические, заранее известные значения, которые вы включаете в выражение, чтобы сравнить их со значениями полей запроса или ответа, с преобразованиями или без них.
При работе со значениями в выражениях правил учитывайте сведения из следующих разделов.
Строковые значения и регулярные выражения
Строки представляют собой последовательности байтов, заключённые в определённые разделители.
Правила Cloudflare поддерживают два формата задания литеральных строк, включая регулярные выражения: строковые литералы в кавычках и необработанные строки. Эти форматы отличаются разделителями и механизмами экранирования.
Для указания регулярных выражений в выражении можно использовать любой из двух форматов строк. Однако Cloudflare рекомендует использовать синтаксис необработанных строк, поскольку синтаксис строк в кавычках имеет сложные правила экранирования и при недостаточном тестировании может приводить к неожиданному поведению.
Сопоставление по регулярным выражениям выполняется с помощью движка регулярных выражений Rust.
Синтаксис строк в кавычках
При использовании синтаксиса строк в кавычках строковый литерал ограничивается " (двойная кавычка). Этот формат требует экранирования специальных символов " и \ с помощью \" и \\, соответственно.
Синтаксис строк в кавычках предъявляет следующие дополнительные требования к экранированию:
- При использовании для задания регулярного выражения в правой части оператор регулярных выражений (
matchesили~), строка разбирается по правилам экранирования регулярных выражений. - При использовании в правой части выражений с другими операторами либо в параметры функции, строка анализируется с использованием базовых правил экранирования.
# Test if URI path contains 'a"b'
http.request.uri.path matches "a\"b"
# Test if URI path contains 'a"#b'
http.request.uri.path matches "a\"#b"
# Replace 'a' with '\' (backslash)
regex_replace(http.host, "a", "\\")Синтаксис необработанных строк
Чтобы задать строку (или регулярное выражение) с помощью синтаксиса необработанных строк, используются специальные разделители:
- Начальный разделитель состоит из
rсимвол, за которым может (необязательно) следовать один или несколько#символов (до 255), за которыми следует"(двойная кавычка). - Закрывающий разделитель представляет собой
"(двойная кавычка), за которой следует такое же количество символов#символов, как и в исходном разделителе (от 0 до 255).
В raw-строке нет специальных символов, поэтому все символы до завершающего разделителя воспринимаются буквально (escape-последовательности отсутствуют).
В отличие от синтаксиса строк в кавычках синтаксис необработанных строк всегда одинаков независимо от контекста использования (например, в качестве регулярного выражения с оператор регулярных выражений либо в качестве параметра вызов функции).
# Test if URI path contains 'a"b'
http.request.uri.path matches r#"a"b"#
# Test if URI path contains 'a"#b'
http.request.uri.path matches r##"a"#b"##
# Replace '\' (backslash) with 'a'
# You must still escape the '\' character in the following raw string because it has a special meaning in regular expressions
regex_replace(http.host, r"\\", "a")
# Test if URI path ends with '/api/login.aspx'
# You must still escape the '.' character in the following raw string because it has a special meaning in regular expressions ("any character")
http.request.uri.path matches r"/api/login\.aspx$"Чувствительность к регистру при сравнении строк
Поскольку сравнение строковых литералов в выражениях учитывает регистр, для учёта возможных вариантов написания в выражении используйте один из следующих вариантов:
- Используйте
wildcardоператор, который не учитывает регистр, для сравнения со строковым литералом. - Используйте
lower()функцию, чтобы преобразовать строку в нижний регистр перед сравнением. - Используйте
matchesоператор (доступен только на тарифах Business и Enterprise) с регулярным выражением, которое соответствует разным вариантам. - Напишите несколько подвыражений с помощью
eqилиcontainsоператор, объединённый сorоператор, чтобы охватить разные варианты строкового литерала (например,<field> eq "a" or <field> eq "A").
Ограничения регулярных выражений
В Cloudflare действует ряд ограничений на регулярные выражения. В частности, каждое правило поддерживает не более 64 регулярных выражений (regex) независимо от тарифного плана вашего домена.
Чтобы уменьшить количество регулярных выражений в правиле, можно использовать следующие подходы:
- Используйте
containsоператор. - Используйте
wildcard/strict wildcardоператоров. - Используйте
starts_with()иends_with()функции.
Логические значения
Простым выражениям с логическими (boolean) полями не нужны операторы сравнения или значения. Вам достаточно указать само поле, как показано в ssl пример ниже.
sslЭто простое выражение соответствует запросам, в которых значение ssl поле: true.
Чтобы находить запросы, где ssl это false, используйте логическое not оператор :
not sslМассивы
Cloudflare Rules language включает поля Array типа и функции с Array аргументов и возвращаемых значений.
Вы можете обращаться к отдельным элементам массива с помощью индекса (неотрицательного значения) в квадратных скобках ([]). Индексы массива начинаются с 0 (ноль).
Используйте специальную нотацию [*] при указании выражения, которое будет вычисляться для каждого элемента массива (например, map функция высшего порядка ↗). Эта специальная индексная нотация распаковывает массив, вызывает охватывающую функцию отдельно для каждого его элемента и возвращает новый массив, содержащий все полученные значения.
Примеры
Рассмотрим http.request.headers.names поле с типом Array<String> в следующих примерах:
-
Получение первого элемента массива:
http.request.headers.names[0] -
Проверить, равен ли первый элемент массива значению
Content-Type(с учетом регистра):
http.request.headers.names[0] == "Content-Type" -
Проверить, равен ли хотя бы один элемент массива значению
Content-Type(с учетом регистра):
any(http.request.headers.names[*] == "Content-Type") -
Проверить, равен ли хотя бы один элемент массива значению
Content-Type, без учета регистра:
any(lower(http.request.headers.names[*])[*] == "content-type")
В последнем примере lower() функция включает [*] нотацию, чтобы функция вычислялась для каждого элемента массива. Эта функция, используемая вместе с [*], возвращает новый массив, в котором каждый элемент исходного массива приведен к нижнему регистру. Затем для сравнения строк используется [*] чтобы преобразовать массив, полученный в результате применения lower() к каждому имени заголовка, получая массив логических значений. Наконец, any() принимает значение true, если хотя бы один из элементов этого массива равен true.
Примечания
Определить собственные массивы нельзя. Можно использовать только массивы, которые возвращают поля, как непосредственно, так и после обработки функциями.
Обращение к индексу массива за пределами допустимого диапазона приводит к появлению «отсутствующего значения». Отсутствующее значение ведёт себя следующим образом:
- Любое сравнение
<expr> <op> <literal>где<expr>возвращает отсутствующее значение, тоже вернёт false. - Вызовы функций, например
function(<expr>), где<expr>возвращает отсутствующее значение, в большинстве случаев тоже вернёт отсутствующее значение, но точное поведение может отличаться в зависимости от функции.
Вы можете использовать только [*] несколько раз в одном и том же выражении, если оно применяется к одному и тому же массиву. Кроме того, вы можете использовать [*] в первом аргументе вызова функции.
Rules language операторы напрямую не поддерживают массивы или [*] оператор, однако они поддерживают индексированные элементы массива, такие как array_value[0]. Например, нельзя использовать [*] на == оператор вне контекста охватывающего вызова функции:
http.request.headers.names[*] == "Content-Type", Недействительно выражениеany(http.request.headers.names[*] == "Content-Type"), Действительное выражение
Карты
Map, также называемый ассоциативным массивом, представляет собой структуру данных, которая хранит набор пар ключ-значение, где ключ должен быть String а значение может быть любого типа (например, String или массивом значений). Все значения в Map должны быть одного типа.
Cloudflare Rules language включает несколько поля Map тип данных. Обозначение типа для полей map, например Map<Array<String>>, указывает тип данных значений, связанных с ключами (Array String элементов). Это означает, что при обращении к значению по ключу "foo" вы получите либо массив String элементов или отсутствующее значение.
Чтобы получить значение из Map, укажите ключ в квадратных скобках ([]):
<MAP_FIELD>[<KEY>]Для словарей, значения которых имеют Array типа нельзя напрямую использовать операторы с полученным значением (массивом), поскольку эти операторы не поддерживают массивы напрямую. Чтобы применить оператор к отдельному элементу массива, используйте специальную нотацию [*] при указании выражения. Эта специальная индексная нотация распаковывает массив, вызывает охватывающую функцию отдельно для каждого его элемента и возвращает новый массив, содержащий все полученные значения.
Примеры
Следующий пример основан на http.request.headers поле с типом данных Map<Array<String>>, где элементы массива имеют тип String тип данных.
Если входящий HTTP-запрос содержал один Accept: application/json HTTP-заголовка приведенные ниже выражения примут указанные значения:
http.request.headers["accept"] # ==> ["application/json"]
http.request.headers["accept"][0] # ==> "application/json"
any(http.request.headers["accept"][*] == "application/json") # ==> true
any(http.request.headers["accept"][*] == "text/plain") # ==> falseСледующий пример основан на http.request.uri.args поле с типом данных Map<Array<String>>, где элементы массива имеют тип String тип данных.
Если HTTP-запрос содержал три filter аргументы URI waf, botm, а также cdn, следующие выражения примут указанные значения:
# Example request URL:
# https://example.com/?filter=waf&filter=botm&filter=cdn
http.request.uri.args["filter"] # ==> ["waf", "botm", "cdn"]
len(http.request.uri.args["filter"][1]) # ==> 4
# Check if the length of all 'filter' values is always 3 or 4
all(len(http.request.uri.args["filter"][*])[*] in {3 4}) # ==> true
# Check if the length of 'filter' values (if any) is never 3 or 4
all(not len(http.request.uri.args["filter"][*])[*] in {3 4}) # ==> false
# Check if the http.request.uri.args map contains a "filter" key
len(http.request.uri.args["filter"]) >= 0 # ==> true
# Check if the http.request.uri.args map does not contain an "order" key
not len(http.request.uri.args["order"]) >= 0 # ==> trueПодробнее о any(), all(), len(), и других доступных функциях см. Функции.
Примечания
Определить собственные карты нельзя. Можно использовать только карты, которые возвращают поля.
Обращение к несуществующему ключу в map приводит к появлению «отсутствующего значения». Отсутствующее значение ведёт себя следующим образом:
- Любое сравнение
<expr> <op> <literal>где<expr>возвращает отсутствующее значение, тоже вернёт false. - Вызовы функций, например
function(<expr>), где<expr>возвращает отсутствующее значение, в большинстве случаев тоже вернёт отсутствующее значение, но точное поведение может отличаться в зависимости от функции.
Lists
Списки позволяют объединить элементы в группу и ссылаться на них по имени в своих выражениях. Каждый тип списка поддерживает элементы определённого типа данных. Все элементы списка должны быть одного типа данных. Подробнее о доступных типах списков см. Lists.
Чтобы сослаться на список в выражении правила, используйте $<list_name> и укажите in оператор. Достаточно, чтобы только одно значение из списка совпадало с левой частью выражения (перед in оператор) для того, чтобы простое выражение вычислилось в true. Если совпадений нет, выражение принимает значение false.
Следующее примерное выражение отфильтровывает запросы с IP-адресов, входящих в Список IP-адресов с именем office_network:
(ip.src in $office_network)Имена списков могут содержать только буквы в нижнем регистре, цифры и символ подчёркивания (_) символ. Сведения о создании списков и управлении ими см. в Lists.
Встроенные списки
Встроенные списки позволяют напрямую включить список значений в простое выражение, которое использует in оператор.
Элементами встроенного списка могут быть строки, целые числа или IP-адреса и диапазоны. Все элементы встроенного списка должны быть одного типа данных и являться литеральными значениями. Чтобы указать элементы встроенного списка, вводите их по отдельности, разделяя пробелом. Встроенные списки могут содержать повторяющиеся значения.
Кроме того, для некоторых типов данных в качестве элементов можно использовать диапазоны:
-
Для целочисленных значений указывайте диапазоны в формате
<start_value>..<end_value>. Встроенный список может содержать как диапазоны целых чисел, так и целочисленные значения. -
Для IP-адресов можно указать:
- Явные диапазоны IP-адресов в формате
<start_address>..<end_address>(например,198.51.100.3..198.51.100.7). - Диапазоны CIDR (например,
192.0.2.0/24или2001:0db8::/32).
Встроенный список может содержать явные диапазоны IP-адресов, диапазоны CIDR и отдельные IP-адреса.
- Явные диапазоны IP-адресов в формате
http.host in {"example.com" "example.net"}
ip.src in {198.51.100.1 198.51.100.3..198.51.100.7 192.0.2.0/24 2001:0db8::/32}
tcp.dstport in {8000..8009 8080..8089}