ALBでClient TLS Negotiation Errorが発生している際にAWS側から情報を得る方法について考えてみた

こんにちは。枡川です。

ALBでClient TLS Negotiation Errorが発生している時に送信元IPや原因をAWS側から特定できたら良いのになと思ったので試してみました。
リンク先に記載されている通り、Client TLS Negotiation Errorが発生している際には下記のような手順が一般的な対応となります。

  • ロードバランサーのセキュリティポリシーを特定する
  • ロードバランサーのセキュリティポリシーがサポートしているプロトコルと暗号を確認する
  • 必要に応じて、ロードバランサーのセキュリティポリシーを更新する

ただ、どんなIPからどのようなプロトコルと暗号で接続してきているかについての情報を得ることができない場合、セキュリテイポリシーを更新する決断ができない可能性があります。
接続しようとした側からは接続元IPやプロトコル、暗号、端末に表示されるエラー文などが取得できますが、ALBをインターネットに公開している場合は必ずしもこれらの情報が取得できるわけではないです。
AWS側から情報を取得できないかと考えた際に、ぱっと浮かぶのは下記3つではないでしょうか。

  • ALBアクセスログ
  • VPCフローログ
  • ALBへのトラフィックをパケットキャプチャ

ALBでサポートされていないTLSプロトコルを用いて接続し、Clinet TLS Negotiation Errorを起こした上で上記方法でどのように情報を取得できるかを確認します。

検証に使用した構成

下記構成で検証します。
別途ALBにはRoute53でドメイン(www.masukawa.classmethod.info)を設定しています。

セキュリティポリシーとしてFS-1-2-Res-2020-10を設定して、端末から下記コマンドを実行します。

$ curl --tls-max 1.1 -v https://www.masukawa.classmethod.info

FS-1-2-Res-2020-10はTLS1.1をサポートしていないので、ALBのメトリクスを確認するとClient TLS Negotiation Errorが発生していることを確認できます。
端末側にも下記エラーが表示されます。

OpenSSL SSL_connect: Connection reset by peer in connection to www.masukawa.classmethod.info:443

※各セキュリテイポリシーがサポートするプロトコル、暗号については下記リンク先に記載されています。

ALBアクセスログ

Clinet TLS Negotiation Errorを起こした上でALBのアクセスログを確認すると、自分のIPが送信元となるログが存在しないことがわかります。
TLSコネクションを確立することに失敗した場合はそもそもアクセスログには残りません。
ただ、厳しいセキュリテイポリシーを使用している際に試験的に緩めのポリシーに変更してALBログに残るようにしてしまえば、TLSプロトコルバージョンの内訳を確認することが可能です。
確認方法は下記ブログに記載されています。

VPC フローログ

次にVPCフローログを確認してみます。
Client TLS Negotiation Errorが発生している時は下表のようなログになりました。
1行目が接続を試行した端末から見た行きの通信で、2行目が戻りの通信です。

srcport dstport protocol packets bytes tcp-flags action log-status
33678 443 6 3 328 2 ACCEPT OK
443 45200 6 3 164 22 ACCEPT OK

Client TLS Negotiation Errorが発生せず、正常にセッションを張れた時は下表のようなログになりました。

srcport dstport protocol packets bytes tcp-flags action log-status
63147 443 6 18 1921 3 ACCEPT OK
443 63147 6 16 6682 19 ACCEPT OK

ここで、tcp-flagsだけはデフォルトのフローログ設定に含まれていないため、カスタム形式で追加する必要があります。
その他にも追加できる項目は複数ありますが、今回の要件であればtcp-flagsのみで十分と考えられます。

どちらの場合もTCPのレイヤでは通信が成功しているため、ACCEPTとなっています。
ただ、Client TLS Negotiation Errorが発生している時はパケット数とバイト数が極端に少ないです。
接続を試みているプロトコルをALB側がサポートしないことから、HTTPS通信の初期段階で通信を終了していることが原因と考えられます。
また、tcp-flagsも明らかに異なります。
tcp-flagsは集計期間以内に観察されたTCPフラグのビットマスクとなります。
ここでClient TLS Negotiation Errorが発生している時の2SYN、戻りの22ACK+RST+SYNとなります。
エラーが発生していない時の3SYN+FIN、戻りの19ACK+SYN+FINとなります。
全てのRSTパケットがClient TLS Negotiation Error起因である保証はありませんが、tcp-flagsが22などRSTにフラグが立っている場合はNegotiation Errorを起こしている通信である可能性が高いと考えることができます。
パケット数とtcp-flagsでなんとなく見当をつけることができそうとはいえ、そのIPがClient TLS Negotiation Errorを起こしている送信元である確証は得られません。
また、どのプロトコルと暗号方式で接続してきているかを確認できないために、セキュリテイポリシーを変更した際にエラーが解消されるのかも不明です。
実際に送信してきているプロトコルと暗号方式を特定するためには、パケットキャプチャを行う必要がありそうです。

トラフィックミラーリングを使用したALBのパケットキャプチャ

ALBにSSH接続してパケットキャプチャすることはできませんが、VPCにはトラフィックミラーリングの機能が存在します。

ALBへのアクセスのトラフィックをSSH接続可能なEC2のENIにミラーリングしてパケットキャプチャを実施することが可能です。
この場合、接続を試行しているプロトコルや対応している暗号方式まで見ることができるので、セキュリテイポリシーを変更した際にエラーが解消されそうかも含めて情報を得ることが可能です。
実際にClient Helloパケットを確認すると、プロトコルや暗号方式まで確認できています。

また、パケットキャプチャで確認すると、今回Client TLS Negotiation Errorが発生した通信では3ウェイハンドシェイク→TLS Client Helloの後にALBからACK+RSTパケットが飛んで来て通信が終了していました。
3ウェイハンドシェイクとTLS Client Helloだけで3パケット分なので、443番ポートに接続してACCEPTされているのに、3パケットで終了しているTCP通信はセッションを上手く確立できずに終わった通信である可能性が極めて高いと言えます。
例えば、フローログをCloudWatchに出力している際に下記のようなフィルタをかけることで、エラーを起こしている可能性が高い通信を洗い出すことができます。
(今回は、AWSのデフォルト形式のログレコードに対してのフィルタを記載しております。カスタム形式の場合は修正が必要です)

[version, account, interface, srcaddr, dstaddr, srcport, destport="443", protocol="6", packets<5, bytes, start, end, action="ACCEPT", logstatus]

まとめ

パケット数が不当に少ない、tcp_flagでRSTフラグが立っている等の情報から送信元IPにあたりをつけることが可能です。
ただ、それらの通信がどんなプロトコル、暗号で接続してきているのかは、パケットキャプチャをしないとわかりません。
ALBにSSHはできませんが、ALBへのアクセスをEC2のENIにミラーリングすればパケットをキャプチャすることが可能です。
もちろんClient TLS Negotiation Errorが発生するパターンは複数あると考えられ、今回の結果が全ての場合に当てはまるとは言えませんが、このブログが問題解決の際の参考になれば幸いです。