Cloudflare で gRPC を通す (3. レートリミット)

## はじめに
Cloudflare で gRPC を通すテスト、第3回です。

1. [プロキシ + Full TLS 化](https://qiita.com/KeioCF/items/fb300ff8bd059ca8ca72)
2. [ファイアウォール](https://qiita.com/KeioCF/items/77b54c50f39318a473fc)
3. レートリミット <<====
4. [ロードバランシング](https://qiita.com/KeioCF/items/055db44468311196af65)


## レートリミット
gRPC リクエストにレートリミットを適用しオリジンへの到達を抑制しようと思います。

Advanced Rate Limiting を利用します。
[紹介 Blog](https://blog.cloudflare.com/ja-jp/advanced-rate-limiting-ja-jp/)

以前の [Rate Limiting](https://support.cloudflare.com/hc/en-us/articles/115001635128) は IP アドレス・レンジ毎にレートリミットを行いましたが、現行の [Advanced Rate Limiting](https://developers.cloudflare.com/waf/rate-limiting-rules/) は [Cloudflare Rules language](https://developers.cloudflare.com/ruleset-engine/rules-language/) を利用してより柔軟なコントロールを提供します。

:::note
[第2回](https://qiita.com/KeioCF/items/77b54c50f39318a473fc)で紹介したようにアプリケーションに対するリクエストをいろいろな条件でクラスに分類し、クラスごとにポリシーを当てることが可能になります。

着信リクエストを Wireshark の[ディスプレイフィルタ](https://www.wireshark.org/docs/wsug_html_chunked/ChWorkBuildDisplayFilterSection.html)をベースに、フィルタします。例えばこんな感じ。
`(http.request.uri.path eq "/store" or http.request.uri.path eq "/prices") and (http.host eq "mystore1.com" or http.host eq "mystore2.com") and not cf.client.bot`

この形で定義する `if` は、WAF/レート制限などのセキュリティ、 Cache などパフォーマンス、デフォルト動作の変更やリクエスト書き換えなど、Cloudflare 各機能の `then` で利用できるので、全体で統一感のある設定が可能になります。これだけで別記事になるネタですね。
:::

### テストシナリオ

1つの IP アドレスの下で Ubuntu と Windows 2 つの gRPC クライアントを稼働させています。どちらのクライアントも公平に、平均 20 リクエスト/分 以下に抑制しようと思います。 

#### 現状確認

gRPC アプリケーション全体へのリクエストは下記のとおりです。
![Screenshot 2023-02-04 at 15.13.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/7dd11e0e-9304-8770-23d3-8daa3bcba39e.png)

IP アドレスは 1 つ。
![Screenshot 2023-02-04 at 15.14.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/e0b5c7b2-0b2d-d356-1c5e-8e32c2d64547.png)

うーん、`どうやって gRPC クライアントをクラス分け`しましょう？

[JA3 フィンガープリント](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/)がクライアント分類に使えそうです。
![Screenshot 2023-02-04 at 15.15.00.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/ba678279-2eb3-7e6c-bad0-4b251bd6934e.png)

2 つの値があり、1 つが 40 リクエスト/分を投げています。
こちらを 20 以下に制限することにします。

:::note
テーブルを見ると、区別できそうなポイントは User-Agent あるいは JA3 フィンガープリントになりそうですが、User-Agent のほうは偽造しやすいと思うので、[TLS リクエストを元にする](https://github.com/salesforce/ja3) JA3 フィンガープリントをクラス化の条件にします。
![Screenshot 2023-02-04 at 15.16.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/d32b21b6-3d3c-91df-3d81-46d975317eb4.png)

:::

#### 方針

下記の方針で設定することとします。

|gRPC Client|IP|現在のリクエスト数(分)|レートリミット適用後の期待|
|---|---|---|---|
|Windows|x.x.x.x|40|20 (超過をブロック)|
|Ubuntu|同上|14|14 (ブロックなし)|

### 設定

1\. レート制限するリクエストを絞る 

gRPC アプリケーションへの Japan からのリクエストのみをレート制限の対象にします。
![Screenshot 2023-01-29 at 22.13.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/68341984-482f-84e2-ea91-a668e309b715.png)


2\. カウント条件、カウント値のグループ化、レート制限条件、アクションを決める

![Screenshot 2023-02-04 at 16.41.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/687e5e28-cda0-8bd5-4d48-b77080505f08.png)

`"以下を実行..."`
条件にハマったら、どのアクションを取るか。
今回はブロック。
コード 429 、Body を JSON で指定。

`"理由..."`
アクションを継続する期間。
今回はブロックを 10 秒継続。

:::note
`理由...` 意味がわかりませんね。原文は `For...` なので、 `"...の間”` ということですね…
:::

`”レートが次の値を超えた場合...”`
レートの定義。
今回は 10 秒間に 5 リクエストを超えた場合に発動。

`"同じ値の..."`
クラス化。
今回は "IP アドレス"と "JA3 フィンガープリント"でクラス化し、カウンタ値をまとめる。


`"カウンタ"`
いつカウントするか。
指定しない場合は受信ルールにマッチする場合にカウンタ値が増える。
今回は"カスタムカウント"でレスポンスステータスコードが 200 のときに増加するよう指定。

`”キャッシュ ステータス”`
キャッシュされたアセットに適用するかどうか。
今回のレスポンスはキャッシュ対象としていないので、関係なし。

### 効果確認

#### ファイアウォールイベント

ファイアウォールイベントを確認します。

1 つのクライアント IP に`許可`と`ブロック`のイベント両方が表示されています。
また、”サービス毎のイベント”を見ると、`レート制限ルール`が発動していることもわかります。

![Screenshot 2023-02-04 at 16.59.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/678fe867-fad8-130e-2576-4feab969d751.png)

"アクティビティログ"でもブロックイベントが出ています。IP アドレスは同じです。
![Screenshot 2023-02-04 at 16.59.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/fe09e119-9475-ec57-40c2-932af238dd04.png)

#### リクエスト

"リクエストサマリー"を確認します。
全体のリクエスト総数はレート制限の適用前と変わらないようです。
`JA3 フィンガープリント`別でみると、各クライアントが挙動を変えず同じペースでリクエストをしていることがわかります。
![Screenshot 2023-02-04 at 17.02.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/776f086e-5bbc-9b34-fa05-9679cc81f823.png)

クライアントごとのブロック状況を確認するために、ドリルダウンしていきます。

`エッジステータスコード`の視点に切り替えます。
`200 OK` の他に`429 Too Many Requests` がカウントされており、全体のリクエストの 3~4 割程度がブロックされていることがわかります。

![Screenshot 2023-02-04 at 17.03.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/5b851761-cd0f-30d7-d37b-451ea0edf0bf.png)

JA3 フィンガープリントのフィルタを追加します。

#### Windows クライアント
期待通り、ブロックが動き、20 リクエスト程度に成功リクエストが抑えられています。

![Screenshot 2023-02-04 at 17.03.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/58d252a6-b80a-a793-eb07-b37ac231108f.png)

#### Ubuntu クライアント
そもそも 20 リクエストを超えておらず、ブロックもされていません。

![Screenshot 2023-02-04 at 17.04.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/c41b129d-eae3-45b8-6e4d-5d0e08db8a5e.png)

目的達成です。

## まとめ

以上、gRPC にレート制限を適用し、オリジンサーバへのリクエストを適正化することができました。

次回はロードバランシングで gRPC アプリケーションの可用性、効率性を高めようと思います。