ALBでトラストストアを使ってみた

はじめに

TLS(SSL)通信でのクライアント認証、お客様からリクエストが来たり要件に含まれてたりすること、ありませんか?
これまではNLBで443/tcpを素通ししてEC2インスタンスで認証する以外の方法がなかったのですが、これからはトラストストアも選択肢として検討できるようになりました。
発表されてからしばらくたっているのですが、どんな使い勝手なのか知るには実際に使ってみるのが一番!ということで試してみました。

もくじ

  • トラストストアとは
  • 証明書を準備する
  • テスト環境を準備する
  • 接続してみる
  • まとめ

トラストストアとは

トラストストアとは、アプリケーションロードバランサー(ALB)で利用可能な証明書認証に関するオプションです。と堅いことを書いてますが、要はTLSクライアント認証をするためにリスナーにアタッチされるリソースのことです。
トラストストアを作成するにあたって、最低限、認証局バンドルファイルがあれば大丈夫です。それでは早速作ってみます。

証明書を準備する

動作確認にあたっては署名したクライアント用の証明書(+鍵ペア)も後で必要になるので、ここでまとめて作ります。AWS Private CAでもいいのですが、今回は実験なのであまりおカネをかけたくない、という思惑もあり、お手軽に利用できるXCAを利用して作成しました。
CA証明書バンドルとは、クライアント側にインストールしておく証明書に署名した証明書を、ルート証明書から順番に列記したものになります。

証明書の構成

今回用意した証明書の構成は下記のとおりです。

用途 署名の状態 用意する形式
ルート証明書 自署 X.509証明書(PEM形式)
署名用証明書 ルート証明書で署名 X.509証明書(PEM形式)
クライアント証明書 署名用証明書で署名 証明書+鍵ペア(PKCS#12形式)

XCAのツリー的にはこのような感じです。

CA証明書バンドルの作成

クライアント証明書までの信頼チェインがつながるよう、ルート証明書からのすべての証明書を結合したファイルを用意します。
今回の場合は「1. ルート証明書」→「2. 署名用証明書」→「3. クライアント証明書」の順番で信頼関係が築かれているので、
1と2を続けて記載したテキストファイルを作成します。
このファイルをS3バケットにアップロードし、ロードバランサーのトラストストアにセットします。

サーバー証明書を準備する

HTTPSリスナーを作成するためのサーバー証明書をACMで作成します。
個別の解説記事はたくさんありますので、ここでは割愛します。

テスト環境を準備する

ロードバランサーを準備する

検証の目的に照らし、バックエンドサーバーは配置せず、固定レスポンスで定型応答を返すようデフォルトルールを構成しています。
作成にあたりキーになりそうなポイントだけ解説します。

EC2コンソール>ロードバランサー>トラストストア、のところにトラストストアを用意してあります。
このリソースを作成するのに、証明書の項で準備した証明書バンドルを使用します。

作成したトラストストアはロードバランサーのリスナーに関連付けておきます。

クライアントを準備する

ブラウザでアクセスする際にクライアント証明書を提示する必要があるので、証明書準備の項で説明した証明書のうち、クライアント証明書を証明書ストアにインポートしておきます。
今回はFirefoxを利用しましたので、ブラウザが独自に持っている証明書ストアにインポートしています。

接続してみる

正しい証明書を提示してみる

作成したALBにhttpsでアクセスしてみると、ブラウザがクライアント証明書の提示を求めてきました。

ここで適切な証明書を選択して送信すればサイトが見られるようになりました。

また、ここで「証明書を送信しない」を押すと、ちゃんと接続エラーになってくれます。

接続中にエラーが発生しました。PR_CONNECT_RESET_ERROR
エラーコード: PR_CONNECT_RESET_ERROR

証明書が違う場合はどうなるのか

今回はクライアント証明書を3階層(ルート証明書 - 署名用証明書 - クライアント証明書)で作成してみましたが、別の証明書も用意して実験してみました。同じ役割を持つ別の証明書を用意しますので、説明のために、「証明書」の末尾にアルファベットを振って呼ぶことにします。
ALBのトラストストアには引き続き「ルート証明書A - 署名用証明書B」の証明書バンドルを設定してある状態です。

  • 当初構成:ルート証明書A - 署名用証明書B - クライアント証明書C
  • 別構成1:ルート証明書A - 署名用証明書D - クライアント証明書E
  • 別構成2:ルート証明書F - 署名用証明書G - クライアント証明書H

それぞれアクセスしてみたところ、別構成1はクライアント認証に成功、別構成2はクライアント認証に失敗する結果となりました。なるほど、署名用証明書が変わったとしても、ルート証明書が同じであれば認証を通す、という挙動みたいです。

ログにはどう出力されるのか

ログも見ておきましょう。
ログには接続ログアクセスログの2種類があります。通常だとアクセスログのみを使うケースが多いと思いますが、今回はクライアント認証に関する情報をとるために接続ログも出力しています。
また、フィールドの後ろにある番号は前述のドキュメントで説明されている列位置番号です。こちらもあわせて掲載しておきます。

当初構成でのログ

まずは当初構成でのログです。
接続ログから見ていきましょう。

  • tls_handshake_latency (7)フィールドにはハンドシェイクに要した時間が表示されます。ブラウザを操作している側としては証明書を選択して「送信ボタン」を押すまでの時間に近いですね。
  • conn_trace_id (12)は接続トレースIDとなっており、これはAWS側が勝手に発行するものですが、アクセスログとの関連付けをするためのキー情報となります。
接続ログのフィールド
tls_handshake_latency (7) 4.135
leaf_client_cert_subject (8) CN=User2,OU=BLOG,O=SKYARCH,L=Minato,ST=Tokyo,C=JP
leaf_client_cert_validity (9) NotBefore=2025-11-14T02:58:00Z;NotAfter=2026-11-14T02:58:00Z
tls_verify_status (11) Success
conn_trace_id (12) TID_7352613d18201b4ebfe802cf6ff3b501

対応するアクセスログにも、接続ログと対になるtrace_id (18)が出ています。
タイムスタンプやクライアントIPを駆使して「これかなあ、たぶんこれだろうなあ」という不毛な突き合わせをやらなくていいのは良い点ですね。

アクセスログのフィールド
trace_id (18) TID_7352613d18201b4ebfe802cf6ff3b501

別構成1でのログ

つづいて別構成1でのログです。

  • tls_handshake_latency (7)フィールドの時間が長くなっています。おそらくキャプチャをとっていたからでしょう。
  • conn_trace_id (12)は接続ごとに固有のIDが生成されるので先ほどとは別のIDになっていることがわかります。
  • leaf_client_cert_subject (8)のCNがDarkUserになっているのは、おそらくエラーになるだろうと予測して証明書につけた名前でした(笑)。まさかの成功になってしまったので、追加で別構成2を試すことになりました...。
接続ログのフィールド
tls_handshake_latency (7) 24.160
leaf_client_cert_subject (8) CN=DarkUser,OU=BLOG,O=SKYARCH,L=Minato,ST=Tokyo,C=JP
tls_verify_status (11) Success
conn_trace_id (12) TID_13d9f0faef7c854182df2940cd675a6b

こちらも同じく、接続ログと対になるtrace_id (18)が出ています。

アクセスログのフィールド
trace_id (18) TID_13d9f0faef7c854182df2940cd675a6b

別構成2でのログ

最後に別構成2でのログです。

  • tls_handshake_latency (7)フィールドは-になっていますね。tls_verify_status (11)Failedになっているのでハンドシェイクとしては失敗、つまりはドキュメントに記載の「ハンドシェイクが正常に確立されていない場合」に該当するためですね。
  • conn_trace_id (12)は接続ごとに固有のIDが生成されるので先ほどとは別のIDになっていることがわかります。
接続ログのフィールド
tls_handshake_latency (7) -
leaf_client_cert_subject (8) CN=Alt User,OU=BLOG,O=SKYARCH,L=Minato,ST=Tokyo,C=JP
tls_verify_status (11) Failed:ClientCertUntrusted
conn_trace_id (12) TID_4d5d46d7c5d76642b793aa04a408f2b2

この構成ではハンドシェイクが失敗しているのでHTTPアクセスは行われず、結果としてアクセスログは出ていませんでした。

まとめ

これまでのEC2インスタンスで終端するクライアント認証に比べればできることはそこまで多くはありませんが、基本的なクライアント認証がマネージドサービスで実現できるようになったのは非常に便利ですね。
トラストストアには執行リスト(CRL)の設定もできるようになっていますが、今回はご予算(とお時間)の問題から見送りました...。だれかたのみます!

投稿者プロフィール

hiroo
根っこはインフラ屋な古いおじさん。