gRPC の TLS を NLBで終端しようと思っていたらずいぶんとトラブってしまいました。 結局のところその原因は ALPN にあったのですが、ぼく自身が ALPN に詳しくないため、なかなかよくわからなかった。 このため、きちんと ALPN を理解しようというのが本エントリの趣旨になります。
ALPN
ALPN は Application-Layer Protocol Negotiation の略で、TLS の拡張です。RFC は RFC 7301 - Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension。 ALPN が何をしてくれる拡張かというと、TLS の上のアプリケーションレイヤで使用するプロトコルを Client - Server 間でネゴシエーションするための拡張になります。
Client は TLS の ClientHello
メッセージの中で、自分のサポートできるプロトコルをリスト形式で Server に送ります。このリストは、自分が利用を望むプロトコル順になっています。
Server は、その中で Server 自身が対応しているプロトコルを選び、ServerHello
メッセージの中でその選択結果を返却します。
RFC の中の図で説明すると、以下のようになります。まだコネクション確立前の段階なので、これらのネゴシエーションは平文で行われます。
Client Server ClientHello --------> ServerHello (ALPN extension & (ALPN extension & list of protocols) selected protocol) Certificate* ServerKeyExchange* CertificateRequest* <-------- ServerHelloDone Certificate* ClientKeyExchange CertificateVerify* [ChangeCipherSpec] Finished --------> [ChangeCipherSpec] <-------- Finished Application Data <-------> Application Data
じゃぁどういうアプリケーションプロトコルがこれらの選択肢に入るかというと、HTTP/1.1
や SPDY/3
、 HTTP/2 over TLS
や HTTP/2 over TCP
などがあります。
ALPN と HTTP/2
HTTP/2 の RFC (RFC 7540) には以下の記述があり、TLS で HTTP/2 を使おうとすると、ALPN が前提になっていることがわかります。
A client that makes a request to an "https" URI uses TLS with the application-layer protocol negotiation (ALPN) extension.
なぜ NLB 上で gRPC が使用できなかったのか
ここからは想像を含みます。
- NLB で TLS を終端しようとしていた
- そして NLB ではおそらく ALPN をサポートしていない。
というわけで、gRPC クライアントが NLB との ALPN のネゴシエーションで失敗していたということが理由なんだと理解しています。 訳がわからなかったのは、Go の gRPC クライアントでは成功したにもかかわらず node.js の gRPC のクライアントで失敗し、言語間で挙動が異なっていたということ。 これはおそらく、Go の gRPC クライアントのバグ。