限制对服务器的访问,并在应用前放置 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] 现在生成的规则时才如此(见下文)。
每项更改都在 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 MiB413
max_header_bytes请求行与请求头合计的最大大小,1024 到 65536431
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 到 600504;应用未在监听时为 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 statusWAF 的端口、转发目标、起始时间,以及自那时起的计数
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 指南包含所有规则,包括对 app_allow 的更改何时会被暂缓到下一次部署。