【AWS】CloudFront+ALB構成で遭遇した502(OriginCommError)の調査記録

はじめに


こんにちは、ネクストモードの眞鍋です。

CloudFrontのオリジンにALBを設定したHTTPS構成を構築したところ、ALBへの直接アクセスは正常なのに、CloudFront経由のアクセスだけ502エラーが多発する事象に遭遇しました。

結論を先に言うと、犯人は「SNIとALBのデフォルト証明書の組み合わせ」でした。
この事例を、調査の過程も含めてご紹介します。

システム構成


対象のサイトは、多言語化のためにSaaS型の翻訳プロキシを挟んだ、やや特殊な多段構成です。

cloudfront-alb-502-sni-default-certificate-01

リソース 役割
CloudFront① Webアクセスの入口。www.example.com を割り当て。デフォルトオリジンは翻訳プロキシ
翻訳プロキシ(SaaS) 多言語化サービス。リクエストを中継してCloudFront②へ
CloudFront② 中継用。代替ドメイン名なし(xxx.cloudfront.net のまま)。オリジンはALB
ALB origin.example.com をCNAMEで割り当て。HTTPSリスナーには複数ドメインの証明書を割り当てており、デフォルト証明書は別サービス用(CN=app.example.com

ALBに origin.example.com というドメインを割り当てているのは、CloudFrontを経由せずにALBへ直接アクセスするケースが存在するためです。

また、重要なポイントとして、CloudFront②のデフォルトビヘイビアは全ヘッダー転送(Hostヘッダーを含む)の設定になっていました。

発生した事象


origin.example.com をALBに割り当て、CloudFront②のオリジンドメインとして設定して構築したところ、監視ツールの「5xxErrorRate」アラートが多発しました。

  • ALBへの直接アクセス(https://origin.example.com)は正常に応答する
  • CloudFront経由のアクセスでは502エラーが発生する
  • DNS・オリジン定義を見直しても、誤りは見つからず
  • ALBにはorigin.example.comの証明書を割り当て済(SNI)

調査内容


各所のログとメトリクスの確認

まず、経路上の各リソースのログとメトリクスを確認しました。

  • 前段のCloudFront①のログには、翻訳プロキシオリジンからの502(OriginError)が多数記録
  • CloudFront②で502(OriginCommErrorを確認。前段CloudFront①の502と発生時刻・件数が完全一致
    • time-taken は1秒前後で、タイムアウト(30秒)ではなく接続段階の即時失敗
  • ALBのアクセスログには5xxの痕跡が一切なし。ALB・ターゲットの5xxメトリクスも0
  • DNS・証明書・リスナールール・オリジン定義を精査するも、誤りは見つからず
    ※最終的にはデフォルト証明書の設定が原因でした。

 

OriginCommError とALBにログがないことから、502の発生源はCloudFrontでほぼ確定し、
HTTPリクエストがALBに到達する前、TLSハンドシェイクの段階で失敗している可能性が高いと判断しました。

接続ログ・VPCフローログ:失敗区間の裏付け

さらに、ALBの接続ログVPCフローログを確認しました。

ログ 結果 意味
ALBアクセスログ エラーなし HTTPリクエストは到達していない
ALB接続ログ Failed:UnmappedConnectionError 接続確立段階での失敗と整合
VPCフローログ 全件ACCEPT SG/NACLによる拒否ではない

「TLSハンドシェイクで失敗している」ことはほぼ確定しましたが、ログだけでは詳細な原因特定には至りませんでした。

判明した根本原因


原因はCloudFront②→ALBのTLSハンドシェイクにおける証明書検証エラーでした。

cloudfront-alb-502-sni-default-certificate-02

メカニズムを順に追うと次のとおりです。

  1. CloudFront②は全ヘッダー転送(Hostヘッダーを含む)の設定のため、ALBへのTLS接続時にSNIとしてHostヘッダーの値を送信する
    • この構成では翻訳プロキシから見たCloudFront②のドメイン、つまり xxx.cloudfront.net がSNIに入る
  2. ALBのHTTPSリスナーには xxx.cloudfront.net に一致する証明書が存在しないため、デフォルト証明書(CN=app.example.com)を返却する

  3. CloudFrontは返却された証明書に「オリジンドメイン名」または「転送されるHostヘッダーのドメイン」のいずれかが含まれることを検証する
    • デフォルト証明書(CN=app.example.com)は、オリジンドメイン名(origin.example.com)ともHostヘッダー(xxx.cloudfront.net)とも不一致 → 検証失敗 → 502(OriginCommError)

 

つまり、origin.example.com 用の証明書をALBのSNIの証明書リストに用意していても、CloudFront②からの接続ではSNIが cloudfront.net ドメインになるためその証明書は選ばれず、別ドメイン用のデフォルト証明書が返って検証に失敗していた、という構図でした。

解決方法


対処はシンプルで、ALBのHTTPSリスナーのデフォルト証明書を origin.example.com 用の証明書に変更しました。


SNIに一致しない値(cloudfront.net ドメイン)が来てデフォルト証明書にフォールバックしても、そのデフォルト証明書がオリジンドメイン名と一致していれば検証は成功する、という仕組みを利用した形です。変更後、502は解消しました。

なお、より根本的な対策としては以下も検討が必要です。

  • Hostヘッダー転送をやめ、必要な情報は X-Forwarded-Host 等のカスタムヘッダーで渡す
  • 中継用CloudFrontに代替ドメイン名(CNAME)と証明書を割り当て、ALB側にも同ドメインの証明書を用意して、SNIと証明書を明示的に一致させる

さいごに


「ALBへの直接アクセスは正常なのに、CloudFront経由だけ502になる」という一見不可解な事象でしたが、ひも解いてしまえば、CloudFrontの証明書検証ルールとALBのSNIによる証明書選択という、仕様どおりの振る舞いの組み合わせでした。

Hostヘッダーを転送している環境では、同じ事象を踏む可能性が十分にあります。
CloudFrontのオリジンにALBを設定する際は、ぜひALBのデフォルト証明書にも目を向けてみてください。