Политики трафика
Обзор
Функция позволяет описать правила и действия обработки трафика для входящих запросов и более гибко настроить взаимодействие с вашим приложением.
Функция находится на стадии отрытого 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
. Если пользователь проходит авторизацию, то далее трафик направляется к вашему сервису. Это правило позволит вам открыть доступ к разрабатываемому сайту для всех, например для заказчиков или коллег, но при этом вы можете запретить доступ к приватным частям сайта. Это лишь один пример, нижу вы найдёте полное описание и больше живых примеров.
Чтобы вам было проще написать нужное правило мы добавили отладчик прямо в личном каби нете, там вы можете проверить корректно ли работает правило, а так же приведено несколько примеров:

Правила (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.RemoteAddr | IP адрес клиента с которого пришёл запрос |
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) |
RandInt | Генерация случайного целого числа | RandInt(0, 10) |
RandDouble | Генерация случайного дробного числа в диапазоне от 0 до 1 | RandDouble() |
Примеры 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"