Перейти к основному содержимому

Политики трафика

Обзор

Функция позволяет описать правила и действия обработки трафика для входящих запросов и более гибко настроить взаимодействие с вашим приложением.

Внимание

Функция находится на стадии отрытого BETA тестирования. Критические изменения могут произойти в любое время без предварительного уведомления, включая изменения в структуре документов политики, поведении политик и ценообразовании этой функции.

к сведению

Доступно только по подписке в тарифе Команда.

Чтобы понять, зачем нужны политики и как они работают, разберём пример включения базовой авторизации только для префикса /admin:

---
rules:
- name: Authorization for admin panel
expressions:
- req.URL.Path contains "/admin"
actions:
- name: Enable basic auth
type: basic_auth
config:
userList:
admin: password

Тут описано 1 правило(rules), в нём есть выражение(expressions), оно содержит условие в котором мы проверяем, что запрос предназначается для префикса /admin, если условие истинно, наступает действие(actions), в примере мы применяем базовую авторизацию, задаём список из пар username, password. Если пользователь проходит авторизацию, то далее трафик направляется к вашему сервису. Это правило позволит вам открыть доступ к разрабатываемому сайту для всех, например для заказчиков или коллег, но при этом вы можете запретить доступ к приватным частям сайта. Это лишь один пример, нижу вы найдёте полное описание и больше живых примеров.
Чтобы вам было проще написать нужное правило мы добавили отладчик прямо в личном кабинете, там вы можете проверить корректно ли работает правило, а так же приведено несколько примеров:

policy_debug

Правила (rules)

Правила состоят из действий - actions (обязательно), которые должны вступить в силу и выражений - expressions (не обязательно), фильтрующих трафик, к которому они применяются. Правила оцениваются и выполняются последовательно в порядке их описания, без прерывания их выполнения. Т.е. если запрос подпадает сразу под несколько правил, то все действия также будет применены к данному запросу. Также можно указать имя name. Описание правил хранится в файле .tuna.yml в текущем каталоге, в YAML формате. Путь к файлу можно переопределить с помощью флага --policy-file или переменной окружения TUNA_POLICY_FILE.

Например существует 2 правила. В первом мы включаем базовую авторизацию для всех запросов к префиксу /admin. Во втором проверяем есть ли заголовок Accept-Language и присутствует ли в нём русский язык ru-RU. Клиент сначала должен будет ввести username и password, а далее сработает запрет по заголовку Accept-Language если в значении заголовка есть ru-RU.

---
rules:
- expressions:
- req.URL.Path contains "/admin"
actions:
- type: basic_auth
config:
userList:
admin: admin
- expressions:
- any(req.Headers["Accept-Language"], {# matches "ru-RU"})
actions:
- type: deny

Выражения (expressions)

примечание

Сайт с описанием синтаксиса expr-lang

Выражения основаны на синтаксисе expr-lang. Это богатый язык с множеством функций, операциями сравнения, вхождения, работы с строками и многим другим. Перечисление выражений в списке воспринимается как логическое И (and).

Пример блокировки POST запросов от ботов:

---
rules:
- expressions:
- lower(req.UserAgent) matches "(chatgpt-user|gptbot)"
- req.Method in ["POST"]
actions:
- type: deny

Переменные с данными из запроса

Чтобы работать с входящим запросом есть несколько переменные для получения информации или метаданных:

НазваниеОписание
req.ProtoСодержит версию протокола HTTP, которая была использована при отправке запроса (например HTTP/1.1 или HTTP/2.0)
req.HostСодержит хост URL, включая порт, если он указан (алиас для req.URL.Host)
req.MethodСодержит метод запроса GET / POST и другие
req.URLПолный URL запроса (имеет свои методы, которые описаны в таблице ниже)
req.UserAgentСодержимое заголовка User-Agent
req.RemoteAddrIP адрес клиента с которого пришёл запрос
req.ContentLengthВозвращает длину тела запроса в байтах, если она известна, берётся из заголовка Content-Length.
req.HeadersСписок всех заголовков

Переменные внутри req.URL позволяют получить отдельные части URL:

НазваниеОписание
req.URL.SchemeСодержит схему URL, которая определяет протокол (например, http, https, ftp и т.д.).
req.URL.OpaqueСодержит непрозрачные (закодированные) данные URL, которые не могут быть напрямую разобраны на части, такие как схема, хост и путь.
req.URL.UserСодержит информацию о пользователе, содержащую имя пользователя и, возможно, пароль в URL.
req.URL.HostСодержит хост URL, включая порт, если он указан (например, example.com:8080).
req.URL.PathСодержит путь URL, который указывает на конкретный ресурс (например, /articles/2024/).
req.URL.RawPathСодержит закодированную версию пути (если она отличается), может быть полезен для случаев, когда путь содержит специальные символы.
req.URL.RawQueryСодержит закодированные параметры запроса без символа ? (например, name=John&age=30).
req.URL.FragmentСодержит фрагмент URL, который указывает на определенную часть документа, без символа # (например, section2).
req.URL.RawFragmentСодержит закодированную версию фрагмента, если он содержит специальные символы (например, пробелы или символы Unicode).

Метаданные запроса:

НазваниеОписание
meta.TimeТочное время в которое пришёл запрос, +- текущее время
meta.RequestIDИндивидуальный идентификатор запроса

Встроенные функции

Помимо достаточно богатого синтаксиса expr-lang, доступны также и встроенные функции для быстрой работы с данными.

НазваниеОписаниеПример
IpInRangeПроверяет входит ли IP в подсетьIpInRange(req.RemoteAddr, "127.0.0.0/8")
IsIPV6Проверяет является ли строка IPv6 адресомIsIPV6(req.RemoteAddr)
IsIPV4Проверяет является ли строка IPv6 адресомIsIPV4(req.RemoteAddr)
IsIPПроверяет является ли строка IP адресомIsIP(req.RemoteAddr)

Примеры expressions

Проверяем, что запрос пришёл по https:

- expressions:
- req.URL.Scheme == "https"

Запрос содержит префикс /admin:

- expressions:
- req.URL.Path contains "/admin"

Запрос не из подсети 10.0.0.0/8:

- expressions:
- not IpInRange(req.RemoteAddr, "10.0.0.0/8")

Действия (actions)

Выполнение действий зависит от того истинно ли условие в выражениях (expressions), если выражение отсутствует, то действие выполняется всегда. Действие должно содержать тип type и конфигурацию config (не всегда обязательно). Также можно указать имя name.

Типы действий (type)

Доступно несколько типов действий, одни модифицируют запрос, другие отвечают за самостоятельную обработку. Можно указывать несколько типов, они будут обработаны по порядку.

add_headers

Добавить заголовки.

Можно добавлять заголовки как при передаче их к вышестоящему приложению context: request, так и при ответе клиенту context: response. Ключевое слово headers определяет карту ключ=значение, где ключ - название заголовка, а значение - содержимое заголовка.

---
rules:
- actions:
- type: add_headers
config:
context: request
headers:
foo: bar
qwe: rty
- actions:
- type: add_headers
config:
context: response
headers:
foo: bar
qwe: rty
remove_headers

Удалить заголовки.

Можно удалять заголовки как при передаче их к вышестоящему приложению context: request, так и при ответе клиенту context: response. Ключевое слово headers определяет список заголовков.

---
rules:
- actions:
- type: remove_headers
config:
context: request
headers:
- foo
- qwe
- actions:
- type: remove_headers
config:
context: response
headers: ["foo", "qwe"]
basic_auth

Включить базовую авторизацию.

Ключевое слово userList определяет карту ключ=значение, где ключ - username, а значение - password.

---
rules:
- actions:
- type: basic_auth
config:
userList:
admin: admin
foma: kiniaev
ащьф: лштшфум
url_rewrite

Перезаписать URI.

Ключевое слово from определяет path с которого нужно перезаписать запрос к вышестоящему серверу, а to определяет path который будет передан к к вышестоящему серверу. Также работают регулярные выражения.

---
rules:
- actions:
- type: url_rewrite
config:
from: /foo
to: /bar
redirect

Перенаправление URL.

Ключевое слово to определяет Location на который перенаправлять, также можно переопределить statusCode (по умолчанию 307). Ключевое слово headers определяет карту ключ=значение, где ключ - название заголовка, а значение - содержимое заголовка.

---
rules:
- actions:
- type: redirect
config:
to: /foobar
statusCode: 304
headers:
foo: bar
deny

Запретить обращение к вышестоящему приложению.

Можно переопределить statusCode (по умолчанию 403).

---
rules:
- actions:
- type: deny
config:
statusCode: 451
restrict_ips

Ограничить обращения по подсетям.

Ключевое слово allow определяет список подсетей с которых разрешены обращения. Ключевое слово deny определяет список подсетей с которых запрещены обращения.

---
rules:
- actions:
- type: restrict_ips
config:
allow:
- 127.0.0.1/32
deny:
- 10.0.0.10/32
rate_limit

Ограничить обращения по количеству запросов.

Ключевое слово rate определяет промежуток времени, а capacity количество запросов которые можно совершить. При превышении этого порога, клиент начнёт получать 429 Too Many Requests.

---
rules:
- actions:
- type: rate_limit
config:
rate: "60s"
capacity: 2
custom_response

Пользовательский ответ.

Обязательный параметр statusCode определяет HTTP статус. Можно также передать тело, содержимое задаётся в параметре content. И заголовки, headers определяет карту ключ=значение, где ключ - название заголовка, а значение - содержимое заголовка.

---
rules:
- actions:
- type: custom_response
config:
statusCode: 200
content: |
User-agent: *
Disallow: /
headers:
content-type: "text/plain"
reverse_proxy

Реверс-прокси в другой сервис.

Обязательный параметр url указывает альтернативную точку для реверс-прокси. С помощью этого действия можно отдельные запросы направлять в сторонний сервис, это может быть полезно к примеру при разработке микро-сервисов.

---
rules:
- actions:
- type: reverse_proxy
config:
url: "http://127.0.0.1:8000"

Примеры

Блокировка запросов по заголовкам

Блокируем запросы, если нет заголовка X-Api-Version, и отвечаем HTTP 400, если ниже 3:

---
rules:
- name: Блокируем API v0
expressions:
- len(req.Headers["X-Api-Version"]) == 0
actions:
- name: Запретить
type: deny
- name: 400 на API v1 и v2
expressions:
- req.Headers["X-Api-Version"][0] in ["1", "2"]
actions:
- name: Ответить 400 JSON
type: custom_response
config:
statusCode: 400
headers:
content-type: "application/json"
content: |
{"msg":"Вы используете старую версию API, минимальная версия 3"}

Реверс-прокси в сторонние сервисы по префиксу и заголовкам

Перенаправляем запросы к /v1 в сторонний сервис на порту 8000, а запросы с User-Agent содержащий mobile к сервису на порту 3000. При этом команда запуска tuna http 8080 по умолчанию проксирует все запросы на 8080.

---
rules:
- expressions:
- hasPrefix(req.URL.Path, "/v1")
- lower(req.UserAgent) contains "mobile"
actions:
- type: reverse_proxy
config:
url: "http://127.0.0.1:3000"
- expressions:
- hasPrefix(req.URL.Path, "/v1")
actions:
- type: reverse_proxy
config:
url: "http://127.0.0.1:8000"

Пользовательский ответ в выходные дни

---
rules:
- expressions:
- now().Weekday().String() in ["Saturday", "Sunday"]
actions:
- type: custom_response
config:
statusCode: 200
content: Take a rest!

Базовая аутентификация

---
rules:
- expressions:
- req.URL.Path contains "/admin"
actions:
- type: basic_auth
config:
userList:
admin: password

Несколько действий без выражений

Ограничение запросов с 127.0.0.1 и 10.0.0.10, а для всех остальных ограничить количество запросов в секунду до 2х.

---
rules:
- actions:
- type: restrict_ips
config:
allow:
- 127.0.0.1/32
deny:
- 10.0.0.10/32
- actions:
- type: rate_limit
config:
rate: "1s"
capacity: 2

Перезапись (rewrite) с регулярным выражением

---
rules:
- expressions:
- hasPrefix(req.URL.Path, "/blog")
actions:
- type: url_rewrite
config:
from: "/blog/([0-9]+)/([a-zA-Z]+)/"
to: /index.php?p=$1&title=$2"