AWSでWebアプリケーションを構築する際、ALB (Application Load Balancer) を使ってHTTPS化するのはもはや常識です。しかし、多くの開発者が一度は疑問に思う点があります。
「ALBでHTTPS化したのに、なぜ後ろにいるECSコンテナはポート80(HTTP)のままで通信できるんだろう?」
この一見不思議な挙動は、ALBが持つ2つの重要な役割を理解することで、スッキリと解決します。今回は、このALBの「魔法」の仕組みを、セキュリティグループの設定から紐解いていきましょう。
最終的な構成:安全な3層アーキテクチャ
まず、私たちが目指すのは、セキュリティと可用性のベストプラクティスに基づいた、以下のような3層アーキテクチャです。
- Web層: ALBがインターネットからのリクエストをすべて受け付けます。
- AP層: ECSコンテナがプライベートな領域でアプリケーションを動かします。
- DB層: RDSがさらに内側のプライベートな領域でデータを安全に保管します。
この構成のセキュリティの肝は「ECSはALBからの通信しか受け付けない」というルールです。これを実現するのが、次に説明するセキュリティグループです。
門番の役割を担う「セキュリティグループ」
セキュリティグループは、リソースのドアを守る「バーチャルな門番」です。今回は3人の門番を配置します。
- ALBの門番: インターネットからの訪問者(ポート80, 443)を全員受け入れます。
- ECSの門番: ALBの門番から紹介された人(ALBからのトラフィック)だけを通します。それ以外の人は全員お断りです。
- RDSの門番: ECSの門番から紹介された人(ECSからのトラフィック)だけを通します。
この連携により、たとえ攻撃者がECSに直接アクセスしようとしても、門前払いされる仕組みが完成します。
ALBがこなす2つの全く異なる仕事
さて、ここからが本題です。ユーザーがhttp://
でアクセスしてきた時に、ALBとコンテナの間で何が起きているのでしょうか。実は、これは2段階の独立した処理になっています。
ポイントは役割が2つ存在するということです。
ブラウザへの「リダイレクト指示」
最初のやり取りは、ユーザーのブラウザとALBの間だけで完結します。
- ユーザー: ブラウザで
http://example.com
(ポート80) にアクセス。 - ALB (ポート80の受付係): リクエストを受け取ると、中身は見ずにこう応答します。「すみません、こちらの窓口は安全ではありません。向かいにある安全な443番の窓口へ行き直してください」。これがHTTP 301リダイレクトです。
この時点では、リクエストはまだECSコンテナには一切届いていません。
ステップ2:コンテナへの「リクエスト転送」
ブラウザはALBからの指示に従い、今度は最初から安全な窓口へ向かいます。
- ユーザー: ブラウザが自動的に
https://example.com
(ポート443) に新しいリクエストを送信します。 - ALB (ポート443の受付係):
- 暗号化されたリクエストを受け取り、持っているSSL証明書を使って通信を復号します(SSL/TLS終端)。
- 安全な平文になったリクエストを、宛先であるECSコンテナのポート80へ転送(フォワード)します。
- ECSコンテナ (ポート80): ALBから来た通常のHTTPリクエストを受け取り、処理を実行します。
このように、ALBはユーザーを正しい入口へ案内する「案内係」の仕事と、リクエストを内部の担当者へ届ける「取次係」の仕事という、2つの全く異なる役割をこなしているのです。
コンテナからすれば、常にALBという信頼できる取次係から、暗号化が解かれた分かりやすいリクエストが届くだけなので、ポート80で待ち受けていれば良い、というわけです。
非常に便利ですね。。
CDKの実装
SecurityGroup の実装
// ALB用セキュリティグループ
const albSecurityGroup = new ec2.SecurityGroup(this, 'AlbSg', { vpc });
albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP');
albSecurityGroup.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS');
// ECS用セキュリティグループ
const ecsSecurityGroup = new ec2.SecurityGroup(this, 'EcsSg', { vpc });
// ALBからの通信のみを、コンテナのポート80で許可
ecsSecurityGroup.addIngressRule(
albSecurityGroup,
ec2.Port.tcp(80),
'Allow traffic only from ALB'
);
ALBリスナー
// ALB本体を作成
const alb = new elbv2.ApplicationLoadBalancer(this, 'MyAlb', {
vpc,
internetFacing: true,
securityGroup: albSecurityGroup,
});
// HTTPリスナー (ポート80): HTTPSへリダイレクトするだけ
alb.addListener('HttpListener', {
port: 80,
defaultAction: elbv2.ListenerAction.redirect({
protocol: 'HTTPS',
port: '443',
permanent: true,
}),
});
// HTTPSリスナー (ポート443): ECSへリクエストを転送する
alb.addListener('HttpsListener', {
port: 443,
certificates: [certificate], // ACMで発行した証明書
defaultAction: elbv2.ListenerAction.forward([ecsTargetGroup]), // ECSのターゲットグループ
});
まとめ
- ALBのHTTP→HTTPSリダイレクトは、「リダイレクト指示」と「リクエスト転送」の2段階で行われる。
- SSL/TLSによる暗号化・復号の処理はALBがすべて肩代わりしてくれる(SSL/TLS終端)。
- コンテナは、ALBから転送されてくる暗号化されていないHTTPリクエストを受け取るだけで良い。
- セキュリティグループを正しく設定することで、この安全な通信経路が完成する。
役割が2つあることが分からなければ理解できなかったなぁ。