---
title: "限制对服务器的访问，并在应用前放置 WAF"
description: "erpc worker 从首次部署起就限制对服务器的访问，erpc.toml 中的一个表即可在公开 API 前放置 WAF。本页说明它做什么、不做什么。"
canonical: "https://erpc.global/zh/doc/erpc-cli/worker-firewall-and-waf/"
locale: "zh"
---

# 限制对服务器的访问，并在应用前放置 WAF

从首次部署起，`erpc worker` 只允许来自您地址的 SSH 进入，并丢弃其他所有入站连接。`[waf]` 表会在您的 API 前放置一个过滤代理。

本页介绍 ERPC CLI `v0.1.0-rc.4`，这是一个预发布版本。

## 防火墙

部署会在应用首次启动之前设置服务器的防火墙：

- 只允许来自 `firewall.ssh_allow` 和部署发起地址的 SSH；
- 丢弃其他所有入站连接，但来自 `firewall.app_allow` 的 `run.port` 除外；对于位于 WAF 后面的应用，则以 WAF 的端口代替，并且仅当服务器上的 WAF 已经在运行 `[waf]` 现在生成的规则时才如此（[见下文](https://erpc.global/zh/doc/erpc-cli/worker-firewall-and-waf/#在应用前放置-waf)）。

每项更改都在 120 秒的回滚计时器下运行，只有在新的连接成功进入后才会保留，因此错误的更改不会让您无法进入。

不过，白名单会比创建它时所用的地址存在得更久：如果您家里的地址变了，对于 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>`）：以无特权的动态用户运行，没有密钥、没有 Secret，也没有任何属于 worker 的东西，内存限制为 1 GiB。它在 `waf.port` 上监听，在应用之前读取每个请求，拒绝其规则所指定的请求，并将其余请求转发到 `127.0.0.1:<run.port>` 上的应用。

- **应用只在 loopback 上**。应用自身的端口从不对外开放。`erpc worker api` 和健康检查绕过 WAF，在 loopback 上访问应用。
- **谁可以连接仍由防火墙的白名单决定**，而 WAF 的端口是该名单唯一可以写成所有人的地方：

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

  名单中没有任何人时，WAF 会监听，但不放任何人进入，部署会说明这一点。
- **端口在 WAF 开始监听后才开放**，绝不会提前。WAF 未能启动的部署会失败并说明原因，而应用仍在运行。
- **更改 `[waf]` 时**，在部署使新规则就位之前，端口不会向任何新的对象开放。
- **从 `erpc.toml` 中删除 `[waf]`** 并部署，会从服务器上移除 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` 及其下的所有路径。与经过一次百分号解码后的路径进行比较，区分大小写 | 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` | 在 `window_secs` 内出现 `after` 个被阻止的请求后，该地址在 `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` | 防火墙自身对从一个地址到 WAF 端口的每秒新 TCP 连接数的限制，由 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 字节 |

被转发的请求不会留下日志行，任何请求头或请求体都从不被记录；被阻止请求的查询字符串会被记录，但仅限于这 200 字节所及的范围。客户端发送的内容无法写到您的终端上：WAF 以可打印 ASCII 写入其日志。

## WAF 不做的事

- **没有 TLS**。它在自己的端口上使用明文 HTTP/1.1。
- **没有 HTTP/2**：对其响应 400。
- **没有 WebSocket**，也没有其他任何 `Upgrade`：501。
- **不查看请求体内部**。请求体只被测量大小，从不被读取。探测列表很短，只是噪声过滤，而不是保证：应用仍要自行检查其输入。
- **没有身份验证**。谁可以调用应用，由应用自己以及防火墙的白名单决定。
- **一个地址就是一个客户端**。位于同一 NAT、代理或 CDN 后面的客户端共享同一个速率和同一个封禁；拥有多个地址的客户端则有相应数量的速率。
- **WAF 重启时，封禁和计数会被遗忘。**
- **它最多记住 8192 个地址**，同时最多持有 1024 个连接。
- **它只在 IPv4 上监听**，因此 `app_allow` 中的 IPv6 条目目前还到达不了任何地方。
- **路径按其解码后的字节进行比较**。以更宽松方式读取路径的应用（忽略大小写、`;` 参数、两次解码）可以通过 `deny_paths` 未覆盖的名称被访问到。`allow_paths` 没有这样的缺口：未列出的路径不会被转发。
- **读取缓慢的客户端也会一直得到服务，直到结束**。只要客户端一直在接收响应，无论多慢，都会一直得到服务，直到结束。
- **洪泛可能会中断大型上传**。WAF 在单个线程上运行，因此洪泛可能会以 408 中断大型上传。接收大型请求体的应用要同时提高 `body_timeout_secs` 和 `max_body_bytes`。

[worker 指南](https://github.com/elsoul/erpc-cli/blob/v0.1.0-rc.4/docs/worker.md)包含所有规则，包括对 `app_allow` 的更改何时会被暂缓到下一次部署。
