セキュリティと互換性の狭間で:AWS ALBにおけるTLSポリシー設計の「正解」
現場でインフラを預かる我々SREにとって、TLSのバージョンと暗号スイートの選定は、単なる「設定値の変更」以上の重みを持っています。PCI DSSや金融系のセキュリティ要件に直面したとき、あるいはレガシーなクライアントからの接続を断ち切らなければならないとき、ALB(Application Load Balancer)のリスナーポリシーは、我々がコントロールできる最も強力な防壁の一つとなります。
今日は、公式ドキュメントの羅列ではなく、パケットがどのように暗号化され、現場でどのようなトラブルが起きるのか、その「リアルな挙動」に焦点を当てて解説します。
—
1. TLSの握手(ハンドシェイク)で何が起きているのか
TLSの通信は、クライアントとALBが「どの言語(暗号スイート)で会話するか」を決める交渉から始まります。この交渉において、ALBのSecurity Policyが強力なフィルターとして機能します。
1. Client Hello: クライアントが「私はTLS 1.2と1.3を喋れます。暗号スイートはこれらを持っています」と提案する。
2. Server Hello: ALBが設定されたポリシーに基づき、「じゃあ、TLS 1.3のTLS_AES_256_GCM_SHA384でいこう」と合意する。
3. Key Exchange: 鍵を交換し、以降の通信を暗号化する。
ここで最も重要なのは、「クライアントがサポートしていても、ALB側で許可していなければ握手は失敗する(Alert: Handshake Failure)」という点です。特にTLS 1.0や1.1は現代では脆弱性とみなされており、セキュリティ要件が厳しい環境では、これらを排除するELBSecurityPolicy-TLS13-1-2-2021-06のようなモダンなポリシーを選択する必要があります。
—
2. 推奨されるセキュリティポリシーの選び方
AWSが提供するSecurity Policyは数多く存在しますが、迷ったら以下の基準で選んでください。
- 最強のモダン構成:
ELBSecurityPolicy-TLS13-1-2-2021-06 - TLS 1.3 と 1.2 のみを許可。レガシーなクライアントがいないなら、これがデフォルトの選択肢です。
- 互換性とのバランス:
ELBSecurityPolicy-2016-08 - TLS 1.0からのサポートが必要な「どうしても古いガラケーや組み込み機器」を切り捨てられない場合の妥協案です。ただし、PCI DSS等の基準ではTLS 1.0/1.1はNGとされることが多いため、注意が必要です。
—
3. 実践:クライアント側からデバッグする
「なぜか接続できない」というアラートを受けたとき、サーバー側のログを見る前に、まずクライアント(手元の端末)からどのプロトコルで接続を試みているかを確認するのが、SREの鉄則です。
openssl を使った確認コマンド
特定のTLSバージョンを指定して、ALBが受け付けるかを確認します。
# TLS 1.2 で接続を試みる
openssl s_client -connect example.com:443 -tls1_2
# TLS 1.3 で接続を試みる
openssl s_client -connect example.com:443 -tls1_3
もし1.0で接続を強制して失敗させたい場合は、-tls1オプションを試してください。ここでHandshake failureが返ってくれば、ALBの設定が機能している証拠です。
—
4. アプリケーションコードからの検証(Python/Fetch API)
フロントエンドやバックエンドからAPIを叩く際、TLSバージョンが低いとALBに拒絶されることがあります。
Python (requestsライブラリ) の場合
標準のrequestsはシステム側のOpenSSLに依存しますが、TLSバージョンを明示的に指定してテストするコードがこちらです。
import ssl
import requests
from requests.adapters import HTTPAdapter
from urllib3.poolmanager import PoolManager
class TLSAdapter(HTTPAdapter):
def __init__(self, ssl_version=None, **kwargs):
self.ssl_version = ssl_version
super().__init__(**kwargs)
def init_poolmanager(self, connections, maxsize, block=False):
# TLSバージョンを強制的に指定して接続する
ctx = ssl.create_default_context()
ctx.set_ciphers('DEFAULT@SECLEVEL=1')
ctx.minimum_version = self.ssl_version
self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize,
block=block, ssl_context=ctx)
# TLS 1.2 以上を要求するセッションの構築
session = requests.Session()
session.mount('https://', TLSAdapter(ssl.TLSVersion.TLSv1_2))
try:
response = session.get('https://api.your-domain.com')
print(f"Status Code: {response.status_code}")
except Exception as e:
print(f"Connection Failed: {e}")
—
5. SREとしての現場Tips
最後に、運用でよくある落とし穴をいくつか共有しておきます。
1. 「とりあえず全部許可」は厳禁: TLS 1.0を残すと、脆弱性を突く攻撃(POODLEなど)の対象になります。要件が許す限り、ELBSecurityPolicy-TLS13-1-2-2021-06への移行をロードマップに入れてください。
2. 暗号スイートの順序: ALBは「クライアントが提示したスイートの中で、サーバー側で優先度が高いもの」を選びます。ポリシーを選択する際は、Forward Secrecy (FS)(前方秘匿性)をサポートする暗号スイート(ECDHEで始まるもの)が優先されるポリシーを選びましょう。
3. ALBログの活用: 接続エラーが頻発している場合、ALBのアクセスログのssl_protocolとssl_cipherフィールドを確認してください。ここに値が入っていない、あるいは意図しないプロトコルが記録されている場合、そこがトラブルの温床です。
TLSの設定変更は、ときに広範囲に影響を及ぼします。必ず開発環境やステージング環境でopensslコマンドを使い、意図したポリシーが適用されていることを確認してから、本番環境へデプロイしてください。
「動いているから触らない」のではなく、「セキュアにアップデートし続ける」ことこそが、我々エンジニアの誇りです。皆さんの環境が、今日も堅牢なパケットで満たされていることを願っています。
コメント