---
title: "Закройте сервер и поставьте WAF перед приложением"
description: "erpc worker закрывает сервер с первого развёртывания, а одна таблица в erpc.toml ставит WAF перед публичным API. Что он делает и чего не делает."
canonical: "https://erpc.global/ru/doc/erpc-cli/worker-firewall-and-waf/"
locale: "ru"
---

# Закройте сервер и поставьте WAF перед приложением

С первого развёртывания `erpc worker` пускает SSH только с вашего адреса и отбрасывает все остальные входящие соединения. Таблица `[waf]` ставит фильтрующий прокси перед вашим API.

Эта страница описывает ERPC CLI `v0.1.0-rc.4` — пререлиз.

## Брандмауэр

Развёртывание настраивает брандмауэр сервера до первого запуска приложения:

- SSH — только с адресов из `firewall.ssh_allow` и с адреса, с которого идёт развёртывание;
- все остальные входящие соединения отбрасываются, кроме `run.port` для адресов из `firewall.app_allow`; для приложения за WAF вместо него — порт WAF, и только там, где WAF на сервере уже работает по правилам, которые сейчас задаёт `[waf]` ([ниже](https://erpc.global/ru/doc/erpc-cli/worker-firewall-and-waf/#поставьте-waf-перед-приложением)).

Каждое изменение выполняется под 120-секундным таймером отката и сохраняется только после того, как проходит новое соединение, поэтому ошибочное изменение не может закрыть вам доступ.

Однако allowlist переживает адрес, с которого он был составлен: если ваш домашний адрес изменится, вернуть доступ можно через поддержку ERPC для сервера ERPC или через консоль провайдера для собственного сервера. Прежде чем полагаться на домашний адрес, добавьте в `firewall.ssh_allow` второй адрес, с которого можно достучаться до сервера (офис, выход VPN).

Если ваш адрес меняется, добавьте все постоянные адреса, которые используют ваши администраторы:

```bash
erpc worker firewall allow --to ssh 203.0.113.10 198.51.100.7
```

| Команда | Что делает |
| --- | --- |
| `erpc worker firewall list` | Показывает, кто может достучаться до сервера: `erpc.toml` рядом с таблицей сервера |
| `erpc worker firewall allow --to ssh\|app <addresses>` | Пропускает адреса и применяет изменение. SSH разрешается с отдельных адресов; сеть принимается только с `--to app` |
| `erpc worker firewall deny --to ssh\|app <addresses>` | Перестаёт их пропускать |
| `erpc worker firewall sync` | Снова приводит брандмауэр сервера к тому, что указано в `erpc.toml` |

## Поставьте WAF перед приложением

Добавьте в `erpc.toml` одну таблицу:

```toml
[waf]
port = 8080
```

После этого `erpc worker deploy` запускает на сервере обратный HTTP-прокси в отдельном юните systemd (`erpc-waf-<app>-<env>`): от имени непривилегированного динамического пользователя без ключа, без секрета и без чего-либо, принадлежащего воркеру, с ограничением в 1 GiB памяти. Он слушает `waf.port`, читает каждый запрос раньше приложения, отклоняет то, что названо в его правилах, и передаёт остальное приложению на `127.0.0.1:<run.port>`.

- **Приложение остаётся на loopback.** Его собственный порт никогда не открывается снаружи. `erpc worker api` и проверка работоспособности обращаются к приложению через loopback, в обход WAF.
- **Кто может подключаться, по-прежнему определяет список брандмауэра**, и порт WAF — единственное место, где этот список может разрешить всех:

  ```sh
  erpc worker firewall allow --to app 0.0.0.0/0     # and ::/0
  ```

  Если в списке никого нет, WAF слушает, но никто не пропускается, и развёртывание сообщает об этом.
- **Порт открывается только после того, как WAF начнёт его слушать**, и никогда раньше. Развёртывание, при котором WAF не запустился, завершается ошибкой и сообщает причину, а приложение продолжает работать.
- **Когда вы меняете `[waf]`,** порт не открывается ни для кого нового, пока развёртывание не установит новые правила.
- **Если убрать `[waf]`** из `erpc.toml` и выполнить развёртывание, WAF будет удалён с сервера.

## Настройки WAF

Обязателен только `port`. Все ключи со значениями по умолчанию:

```toml
[waf]
port = 8080          # the public port, 1024 or above; the app keeps [run] port
methods = ["GET", "POST", "PUT", "DELETE"]
allow_paths = []     # every path
deny_paths = []
max_body_bytes = 1048576
max_header_bytes = 16384
rate = { per_second = 50, burst = 100 }
max_connections_per_address = 32
ban = { after = 100, window_secs = 60, for_secs = 600 }
block_probes = true
header_timeout_secs = 10
body_timeout_secs = 30
upstream_timeout_secs = 30
new_connections = { per_second = 50, burst = 100 }
```

| Ключ | Что делает | Что получает отклонённое |
| --- | --- | --- |
| `methods` | Передаваемые методы, заглавными буквами (не более 16; никогда `CONNECT`) | 405, с `Allow` |
| `allow_paths` | Передаваемые пути; если ничего не указано — все пути. Шаблон — это один путь или всё под префиксом, если шаблон заканчивается на `*`: `/api/*` — это `/api` и всё ниже. Сравнивается с путём после percent-декодирования, с учётом регистра | 404 |
| `deny_paths` | Отклоняемые пути; записываются так же и проверяются первыми | 403 |
| `max_body_bytes` | Наибольший размер тела запроса, до 64 MiB | 413 |
| `max_header_bytes` | Наибольший размер строки запроса и заголовков вместе, от 1024 до 65536 | 431 |
| `rate` | Число запросов в секунду с одного адреса и сколько запросов разом допускается, прежде чем начнёт действовать ограничение скорости | 429, с `Retry-After` |
| `max_connections_per_address` | Соединения, которые один адрес держит одновременно | Соединение закрывается без ответа |
| `ban` | После `after` заблокированных запросов в пределах `window_secs` адрес отклоняется на `for_secs` (до 30 дней) | Его соединения закрываются без ответа |
| `block_probes` | Отклоняет то, что запрашивают сканеры (`/.env`, `/.git`, `/wp-login.php` и подобное) | 403 |
| `header_timeout_secs` | Сколько времени у клиента есть на отправку заголовочной части запроса, от 1 до 120. Столько же ждут клиента, который остановился посреди тела или перестал принимать ответ | 408 для заголовочной части; 400 для остановившегося тела; для непринятого ответа соединение закрывается |
| `body_timeout_secs` | Сколько всего времени может занимать тело запроса, от 1 до 600, не считая времени, которое WAF ждёт приложение | 408 |
| `upstream_timeout_secs` | Сколько времени у приложения есть, чтобы принять соединение и начать отвечать, от 1 до 600 | 504; 502, если приложение не слушает |
| `new_connections` | Собственное ограничение брандмауэра на новые TCP-соединения в секунду с одного адреса к порту WAF; лишние отбрасываются nftables до того, как их увидит прокси. Пока набор адресов nftables заполнен, адрес, которого в нём нет, не учитывается, поэтому это ограничение не применяется к нему, пока в наборе снова не появится место; собственные ограничения прокси (`rate`, `max_connections_per_address`, `ban`) по-прежнему действуют | Пакет отбрасывается |

`rate`, `max_connections_per_address`, `ban` и `new_connections` отключаются значением `false` (`ban = false`), а пропущенный ключ принимает значение по умолчанию. `erpc worker check` ещё до любого развёртывания сообщает, что будет делать таблица `[waf]` и что в ней не так.

## Что WAF делает без всяких настроек

- Запрос, который не читается как один однозначный запрос HTTP/1, получает 400; то же получает путь, записанный не в нормальной форме.
- Запрос на смену протокола (`Upgrade`) получает 501.
- Каждый отказ засчитывается в бан, и каждый отказ завершает своё соединение.
- Адресом считается адрес IPv4 или IPv6 `/64`.
- WAF не передаёт дальше заголовки соединения клиента с прокси (`Connection` и названные в нём заголовки, `Keep-Alive`, `Proxy-Authenticate`, `Proxy-Authorization`, `Proxy-Connection`, `TE`, `Trailer`), `Expect`, заголовки, в имени которых есть подчёркивание, а также собственные `Forwarded`, `X-Real-IP` и `X-Forwarded-Proto` клиента. Он устанавливает в `X-Real-IP` адрес, с которого пришло соединение, и добавляет этот адрес в конец `X-Forwarded-For`; только последняя запись `X-Forwarded-For` исходит от самого прокси.

## Как увидеть, что делает WAF

| Команда | Что показывает |
| --- | --- |
| `erpc worker status` | Порт WAF, куда он передаёт запросы, с какого времени и счётчики с того момента |
| `erpc worker firewall list` | Кто может достучаться до порта WAF и что было заблокировано, с разбивкой по правилам |
| `erpc worker logs --waf` | Каждый заблокированный запрос: правило, статус, адрес клиента, метод и первые 200 байт цели запроса |

Переданный запрос не оставляет строки в журнале, а заголовки и тела никогда не записываются в журнал; query заблокированного запроса записывается — насколько хватает этих 200 байт. То, что отправляет клиент, не может ничего написать в вашем терминале: WAF пишет свой журнал печатаемыми символами ASCII.

## Чего WAF не делает

- **Нет TLS.** На своём порту он работает по обычному HTTP/1.1.
- **Нет HTTP/2:** на него он отвечает 400.
- **Нет WebSocket** и никакого другого `Upgrade`: 501.
- **Не заглядывает в тела.** Тело измеряется, но никогда не читается. Список запросов сканеров короткий и служит фильтром шума, а не гарантией: приложение по-прежнему само проверяет свои входные данные.
- **Нет аутентификации.** Кто может вызывать приложение, решает само приложение и список брандмауэра.
- **Один адрес — один клиент.** Клиенты за одним NAT, прокси или CDN делят одно ограничение скорости и один бан; у клиента с несколькими адресами столько же ограничений скорости, сколько адресов.
- **Баны и счётчики забываются** при перезапуске WAF.
- **Он запоминает не более 8192 адресов** и одновременно держит не более 1024 соединений.
- **Он слушает только IPv4,** поэтому запись IPv6 в `app_allow` пока ни к чему не ведёт.
- **Путь сравнивается как байты, в которые он декодируется.** До приложения, которое читает пути менее строго (без учёта регистра, с параметрами `;`, с двойным декодированием), можно добраться по имени, которое `deny_paths` не покрывает. У `allow_paths` такой бреши нет: то, что не указано, не передаётся.
- **Медленный читатель обслуживается до конца.** Клиент, который продолжает принимать ответ, как бы медленно он это ни делал, обслуживается до конца.
- **Наплыв запросов может оборвать большую загрузку.** WAF работает в одном потоке, поэтому наплыв запросов может оборвать большую загрузку с кодом 408. Приложение, принимающее большие тела, повышает `body_timeout_secs` вместе с `max_body_bytes`.

[Руководство по воркерам](https://github.com/elsoul/erpc-cli/blob/v0.1.0-rc.4/docs/worker.md) содержит все правила, включая то, когда изменение `app_allow` откладывается до следующего развёртывания.
