Политики трафика
Обзор
Функция позволяет описать правила и действия обработки трафика для входящих запросов и более гибко настроить взаимодействие с вашим приложением.
Доступно только по подписке в тарифе Команда.
Чтобы понять, зачем нужны политики и как они работают, разберём пример включения базовой авторизации только для префикса /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 указывает альтернативную точку для реверс-прокси, необязательный параметр onError определяет поведение, при котором запрос не удалось передать в сторонний сервис:
halt(по умолчанию) – обработка запроса при ошибке будет прервана;continue– запрос будет обрабатываться дальше согласно нижестоящим правилам.
Стандартное поведение:
---
rules:
- actions:
- type: reverse_proxy
config:
url: "http://127.0.0.1:8000"
С параметром onError в значении continue:
---
rules:
- actions:
- type: reverse_proxy
config:
url: "http://127.0.0.1:8000"
onError: continue
- type: custom_response
config:
statusCode: 500
content: Unavailable
В данном случае, если ресурс http://127.0.0.1:8000 недоступен, в ответ на запрос будет возвращен HTTP 500 и текст Unavailable.
Примеры
Блокировка запросов по заголовкам
Блокируем запросы, если нет заголовка 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"