サーバーへの接続を絞り、アプリの前に WAF を置く
erpc worker は、最初のデプロイから、SSH を自分のアドレスからだけ受け入れ、ほかの受信接続をすべて落とします。[waf] テーブルを書くと、API の前にフィルタリングするプロキシが置かれます。このページは、プレリリースの ERPC CLI
v0.1.0-rc.4 について説明しています。ファイアウォールで接続を絞る
デプロイは、アプリが初めて起動する前に、サーバーのファイアウォールを設定します。
- SSH は
firewall.ssh_allowと、デプロイを実行したアドレスからだけ受け入れます。 - ほかの受信接続はすべて落とします。例外は
firewall.app_allowからのrun.portです。WAF の後ろにあるアプリでは、代わりに WAF のポートが例外になります。ただしそれは、サーバーの WAF が、[waf]が今作るルールをすでに動かしている場合だけです(下)。
変更はそれぞれ 120 秒のロールバックのタイマーの下で行われ、新しい接続が入れたときにだけ確定します。そのため、間違った変更で締め出されることはありません。
ただし、許可リストは、それを作ったときのアドレスが変わっても残ります。自宅のアドレスが変わった場合、戻る手段は、ERPC のサーバーなら ERPC のサポート、自分のサーバーならプロバイダーのコンソールです。自宅のアドレスを頼りにする前に、サーバーに届く 2 つ目のアドレス(オフィス、VPN の出口)を
firewall.ssh_allow に入れておいてください。アドレスが変わるなら、管理者が使う固定のアドレスをすべて加えます。
bash
アプリの前に WAF を置く
erpc.toml にテーブルを 1 つ加えます。toml
すると
erpc worker deploy は、サーバーで HTTP のリバースプロキシを、専用の systemd ユニット(erpc-waf-<app>-<env>)で動かします。このプロキシは特権のない動的ユーザーとして動き、鍵もシークレットも、ワーカーのものは何も持たず、メモリは 1 GiB に抑えられます。waf.port で待ち受け、各リクエストをアプリより先に読み、ルールが名指すものを拒否し、残りを 127.0.0.1:<run.port> のアプリへ転送します。-
アプリはループバックに置いたままです。 アプリ自身のポートが外に開くことはありません。
erpc worker apiとヘルスチェックは、WAF を通らずにループバックでアプリに届きます。 -
接続してよい相手は、今もファイアウォールのリストが決めます。 そのリストに「全員」と書けるのは、WAF のポートだけです。sh誰も入っていなければ、WAF は待ち受けますが誰も入れず、デプロイがそのことを伝えます。
-
ポートが開くのは、WAF がそこで待ち受けてからで、それより前には開きません。WAF が立ち上がらなかったデプロイは、理由を示して失敗し、アプリは動いたままです。
-
[waf]を変えたとき、新しいルールをデプロイが置き終えるまで、ポートは新しい相手には開きません。 -
erpc.tomlから[waf]を外してデプロイすると、サーバーから WAF が取り除かれます。
WAF の設定
必須なのは
port だけです。すべてのキーと、その既定値は次のとおりです。toml
rate、max_connections_per_address、ban、new_connections は false で無効にでき(ban = false)、書かなかったキーは既定値になります。erpc worker check は、どのデプロイよりも前に、[waf] テーブルが何をするか、どこが間違っているかを伝えます。設定がなくても WAF が行うこと
- 曖昧さのない 1 つの HTTP/1 リクエストとして読めないリクエストには 400 を返します。正規形でないパスにも 400 を返します。
- プロトコルの切り替え(
Upgrade)を求めるリクエストには 501 を返します。 - 拒否はどれも ban の件数に数えられ、拒否するたびにその接続を終えます。
- アドレスとは、IPv4 アドレス 1 つ、または IPv6 の
/641 つです。 - 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 の動きを見る
転送したリクエストはログに残らず、ヘッダーやボディがログに残ることはありません。止めたリクエストのクエリは、200 バイトに入る範囲でログに残ります。クライアントが送ったものがターミナルに書き込まれることはありません。WAF はジャーナルを表示可能な ASCII で書きます。
WAF がしないこと
- TLS はありません。 ポートでは素の HTTP/1.1 で通信します。
- HTTP/2 はありません。 HTTP/2 には 400 を返します。
- WebSocket も、ほかのどの
Upgradeもありません。 501 を返します。 - ボディの中は見ません。 ボディは大きさを測るだけで、読みません。スキャナーの一覧は短く、雑音を減らすフィルターであって保証ではありません。入力の検査は、引き続きアプリが行います。
- 認証はありません。 アプリを呼んでよい相手は、アプリと、ファイアウォールのリストが決めます。
- 1 つのアドレスを 1 つのクライアントとして扱います。 1 つの NAT、プロキシ、CDN の後ろにいるクライアントは、率も ban も共有します。多くのアドレスを持つクライアントは、アドレスの数だけ率を持ちます。
- ban と件数は、WAF が再起動すると忘れられます。
- 覚えておけるアドレスは最大 8192 個で、同時に持てる接続は最大 1024 です。
- 待ち受けるのは IPv4 だけです。 そのため、
app_allowの IPv6 の項目は、今はどこにも届きません。 - パスは、デコードした後のバイト列として比べます。 パスをもっと緩く読むアプリ(大文字・小文字を区別しない、
;のパラメーターを扱う、2 回デコードする)には、deny_pathsが覆わない名前のまま届くことがあります。allow_pathsにはこの隙間はありません。並べていないものは転送しません。 - 読むのが遅いクライアントにも最後まで応答します。 どれほどゆっくりでも応答を受け取り続けるクライアントには、最後まで応答を返します。
- 大量のリクエストで大きなアップロードが途切れることがあります。 WAF は 1 つのスレッドで動くので、大量のリクエストが来ると、大きなアップロードが 408 で途切れることがあります。大きなボディを受けるアプリは、
max_body_bytesと一緒にbody_timeout_secsも上げてください。
ワーカーのガイドには、
app_allow の変更が次のデプロイまで保留される場合を含め、すべてのルールが載っています。





