Cloudflare で gRPC を通す (1. プロキシ + Full TLS 化)

## はじめに
Cloudflare で gRPC を通すテストをしたので紹介します。
![Screenshot 2023-01-29 at 11.37.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/591a5fe7-9eb5-85b6-9e1a-9b67f9f20335.png)

2020年の [Blog](https://blog.cloudflare.com/ja-jp/road-to-grpc-ja-jp/) に記載がありましたが、試していませんでした。
[サポートドキュメント](https://support.cloudflare.com/hc/en-us/articles/360050483011)もあります。

Cloudflare の特性を活かし、gRPC サービスに対して

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

を順に試していこうかと思います。

:::note
できることを見るため、有償版の機能も一部使っています。
特に明示してないのですが、ご容赦ください。
:::

第1回として、まずプロキシで動くようにします。

## プロキシ + Full TLS 化

gRPC クライアント ←--(TLS)--→ Cloudflare
Cloudflare ←-(TLS)-→ gRPC サーバ

どちらも TLS 化します。
(mTLS はやってないです)

Cloudflare を使うので、プロキシ部分はなるだけ近道するのを目指します。

### gRPC サーバ

#### gRPC プロキシ時の前提事項
Cloudflare で gRPC を通す場合、[要求事項](https://support.cloudflare.com/hc/en-us/articles/360050483011#h_1Z6TA2y1ycBzavrd5n9yss)が下記になっています。
```
Your gRPC endpoint must listen on port 443. 
Your gRPC endpoint must support TLS and HTTP/2.
HTTP/2 must be advertised over ALPN.
Use application/grpc or application/grpc+<message type (for example: application/grpc+proto) for the Content-Type header of gRPC requests.
```

:::note
gRPC サーバはポート 443 で TLS をリッスンする必要があります。
ALPN 対応で、content-type の指定も守る必要があります。
:::

#### TLS サーバ証明書の準備 (gRPC サーバ用)
gRPC サーバを TLS 化するので証明書が必要になります。

OpenSSL とかで作るのもいいですけど、 
Cloudflare の [SSL/TLS Origin Certificates](https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/) から作るとワンクリックで簡単なので、Cloudflare を通す場合はこちらを使うと楽です。
![Screenshot 2023-01-29 at 11.46.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/3f1d6610-fc3b-603b-0f80-3383d65cdb25.png)

:::note
[API](https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/#api-calls) でも管理できるので、自動化にも使えます。
:::

#### gRPC サーバ (greeter_server/main.go) TLS 化

gRPC サーバは [Go/Quick start](https://grpc.io/docs/languages/go/quickstart/) を拝借します。
`ポート 443` で `TLS` をリッスンさせるため、サーバ `greeter_server/main.go`を変更します。

`greeter_server/cert/` を作り、 Cloudflare の Origin Certificates で作成した公開鍵を `cert.pem`、秘密鍵を `key.pem` で配置します。

 `greeter_server/main.go` を編集します。
```
--- greeter_server/main.go	2023-01-28 07:48:37.736281676 +0000
+++ greeter_server/main.tls.go	2023-01-29 03:07:17.779105810 +0000
@@ -27,11 +27,12 @@
 	"net"

 	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
 	pb "google.golang.org/grpc/examples/helloworld/helloworld"
 )

 var (
-	port = flag.Int("port", 50051, "The server port")
+	port = flag.Int("port", 443, "The server port")
 )

 // server is used to implement helloworld.GreeterServer.
@@ -51,7 +52,16 @@
 	if err != nil {
 		log.Fatalf("failed to listen: %v", err)
 	}
-	s := grpc.NewServer()
+	creds, err := credentials.NewServerTLSFromFile(
+		"/home/ubuntu/grpc-go/examples/helloworld/greeter_server/cert/cert.pem",
+		"/home/ubuntu/grpc-go/examples/helloworld/greeter_server/cert/key.pem",
+	)
+	if err != nil {
+		log.Fatal("Failed to generate credentials: %v", err)
+	}
+	s := grpc.NewServer(
+		grpc.Creds(creds),
+	)
 	pb.RegisterGreeterServer(s, &server{})
 	log.Printf("server listening at %v", lis.Addr())
 	if err := s.Serve(lis); err != nil {
```
[参考リンク](https://github.com/grpc/grpc-go/blob/master/interop/server/server.go)

サーバを起動します。
```shell_session
ubuntu@vm1:~/grpc-go/examples/helloworld$ sudo go run greeter_server/main.tls.go
2023/01/29 03:07:23 server listening at [::]:443
```
### Cloudflare プロキシ
Cloudflare で gRPC プロキシの準備をします。

#### gRPC 有効化
gRPC を[有効に](https://support.cloudflare.com/hc/en-us/articles/360050483011-Understanding-Cloudflare-gRPC-support#h_o7k9rF9QcJEBavDBTADzb)します。
![Screenshot 2023-01-29 at 12.16.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/6c8f6436-3b72-59b7-fe74-68fefc62b540.png)

:::note info
[HTTP/2 to Origin](https://developers.cloudflare.com/cache/how-to/enable-http2-to-origin/) というスイッチがありますが、こちらは無効のままでも gRPC さえ有効にしておけばよかったです。

![Screenshot 2023-01-29 at 12.17.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/9c49fab5-b562-cdc7-c68a-088a6f6668f7.png)
gRPC 以外で HTTP/2 でオリジンと接続する場合に有効にします。
:::

#### 権威 DNS 設定
ホスト名でアクセスするので [DNS の設定](https://developers.cloudflare.com/dns/manage-dns-records/how-to/create-dns-records/)をします。
gRPC のパブリック IP (今回は GCP ) を A レコードで指定しています。
<font color="orange">オレンジ雲</font>に倒すとプロキシさせることになります。
![Screenshot 2023-01-29 at 12.25.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/cad26c6e-8edc-bd14-ebfb-935ce8db3fcc.png)

プロキシ中はオリジンの IP の代わりに、Cloudflare の IP が返ります。
```shell-session
$ dig rpc.example.com +short
104.18.xxx.xx
104.18.xxx.xx
```
#### TLS 設定
gRPC サーバの TLS 設定は終わってます。
プロキシサーバの TLS はどうしましょう、というところですが、こちらはすでに[自動で適用](https://developers.cloudflare.com/ssl/get-started/)されていると思います。
こういうところも楽ですね。

:::note
前提として、TLS のモードは Full あるいは Full Strict にしておきます。上記のように Cloudflare の Origin Certificate を発行した場合は Full Strict が使えて安心です。

全体の設定で合わせるか、
![Screenshot 2023-01-29 at 12.39.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/cbd20ed4-771b-f4a1-397f-c639b17e2179.png)
[Page Rules](https://support.cloudflare.com/hc/en-us/articles/218411427) または [Configuration Rules](https://developers.cloudflare.com/rules/configuration-rules/) を使って条件を絞って個別に設定することもできます。

なお、モードの違いについては[こちら](https://qiita.com/khayama/items/842d68f617e1ba2f16e9)が詳しいです。
:::

### gRPC クライアント

#### クライアント (greeter_client/main.go) TLS 化
クライアントも [Go/Quick start](https://grpc.io/docs/languages/go/quickstart/) を拝借しますが、`TLS` でリクエストさせるため、クライアント `greeter_client/main.go` を編集します。

```
--- greeter_client/main.go	2023-01-29 04:08:18.455501186 +0000
+++ greeter_client/main.tls.go	2023-01-29 15:00:49.520768741 +0000
@@ -26,6 +26,7 @@
 	"time"

 	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials/insecure"
 	pb "google.golang.org/grpc/examples/helloworld/helloworld"
 )
@@ -37,12 +38,19 @@
 var (
 	addr = flag.String("addr", "localhost:50051", "the address to connect to")
 	name = flag.String("name", defaultName, "Name to greet")
+	useTLS = flag.Bool("use_tls", false, "Connection uses TLS if true")
 )

 func main() {
 	flag.Parse()
+	var creds credentials.TransportCredentials
+	if *useTLS {
+        creds = credentials.NewClientTLSFromCert(nil, "")
+        } else {
+        creds = insecure.NewCredentials()
+        }
 	// Set up a connection to the server.
-	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
+	conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(creds))
 	if err != nil {
 		log.Fatalf("did not connect: %v", err)
 	}
```
[参考リンク](https://github.com/grpc/grpc-go/blob/master/interop/client/client.go)

### 接続

#### クライアント
```shell-session
ubuntu@vm2:~/grpc-go/examples/helloworld$ go run greeter_client/main.tls.go -addr=rpc.example.com:443 -use_tls=true
2023/01/29 04:20:08 Greeting: Hello world
ubuntu@vm2:~/grpc-go/examples/helloworld$ go run greeter_client/main.tls.go -addr=rpc.example.com:443 -use_tls=true -name=hoge
2023/01/29 04:21:54 Greeting: Hello hoge
```
#### サーバ
```shell_session
ubuntu@vm1:~/grpc-go/examples/helloworld$ sudo go run greeter_server/main.tls.go
2023/01/29 04:12:45 server listening at [::]:443
2023/01/29 04:20:08 Received: world
2023/01/29 04:21:54 Received: hoge
```

### オブザバビリティ
#### ログ
HTTP リクエストの生ログをリアルタイムに確認するには [Instant Logs](https://developers.cloudflare.com/logs/instant-logs/) です。
リクエストを投げながらログの確認ができるので、楽ちんです。

出力フィルタが用意されているので、欲しいログにマッチするフィルタを掛けてからログを開始しましょう。

:::note
Instant Logs の Host フィルタは host ヘッダそのものを取るようです。今回のようにクライアントが `Hostname:443` とポートを追加してくるケースでは、`equals` する場合は `:443` も付けるようにします。(下の例では `starts with` でやっているので、そこは無関係)
:::

![Screenshot 2023-01-29 at 13.26.16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/459f2ec9-5e1f-86a9-209e-f5a99eb92ddf.png)

行をクリックすると詳細を確認できます。
リクエストやレスポンスの詳細情報が見えてます。
(下記は一部抜粋で、その他にも色んな情報を見ることができます)
![Screenshot 2023-01-29 at 13.28.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/c9bb3c0a-40f9-613b-2bca-673e1f6bdb82.png)


#### 統計情報
統計情報は [Analytics Traffic](https://support.cloudflare.com/hc/en-us/articles/360037684251-Understanding-Cloudflare-Site-Analytics) で見ます。
裏では HTTP リクエストログをソースに Graphql が動いてますので、ソースログに対して多様な分析ができます。
いろいろなところをクリックすると面白いです。

![Screenshot 2023-01-29 at 13.38.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2218045/87be0f1d-26f7-2760-ad03-8d0edb3a2aeb.png)

以上、 gRPC の単純なプロキシ (+ Full TLS) として動作させることができました。

[第2回](https://qiita.com/KeioCF/items/77b54c50f39318a473fc)はファイアウォールを噛まして、必要なリクエストのみオリジンに届けようと思います。