限制對伺服器的存取,並在您的應用前加上 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 會在伺服器上以獨立的 systemd 單元(erpc-waf-<app>-<env>)執行 HTTP 反向代理:它以無特權的動態使用者身分執行,不持有任何金鑰、任何 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 的變更何時會被暫緩到下一次部署。