ALB のカスタムヘッダー認証を VPC Origin に置き換える手順と注意点

この記事はコインチェック株式会社(以下、コインチェック)のアドベントカレンダー22日目の記事です。
目的
今回は、ALB のカスタムヘッダー認証を CloudFront VPC Origin に置き換える手順 と、その構成を組む際に気をつけたい 「ALB 構築時のセキュリティグループの落とし穴(一瞬だけ全公開されちゃう問題)」 について共有します。
特に今回は、「諸事情で ALB 自体はパブリック (Internet-facing) のままにしなければならない」 ケースを想定して構成を組みます。
これまでの課題:カスタムヘッダーによる制限
これまで、CloudFront 経由のアクセスのみを ALB に許可したい場合、一般的に以下の構成をとっていました。
- ALB を Internet-facing に配置
-
アクセス制限の実装(以下のいずれか、または組み合わせ)
- SG で CloudFront IP 帯を許可 (Managed Prefix List)
- ALB リスナールールで「特定のカスタムヘッダー」をチェック
解決策:CloudFront VPC Origin への置き換え
CloudFront VPC Origin を利用すると、CloudFront から VPC 内のリソース(ALB や EC2)へ、AWS のプライベートネットワークを通じて直接アクセスできるようになります。
この構成に置き換えることで、以下のようなメリットがあります。
1. カスタムヘッダーが不要になる
通信が VPC 内の ENI 経由で ALB に届きます。経路自体がセキュアであるため、アプリケーション層(L7)でのヘッダー認証が不要になります。
結果として、ALB のリスナールールが本来のルーティング定義だけになり、管理がシンプルになります。
2. パブリック ALB でも「インターネット遮断」が可能
これが今回のポイントです。
ALB 自体はパブリック IP を持っていますが、VPC Origin を利用する場合、ALB のセキュリティグループでインターネット(0.0.0.0/0) や CloudFront の Prefix List を許可する必要がなくなります。
代わりに 「VPC 内(VPC Origin ENI)からの通信のみ許可」 すれば良いため、パブリック ALB でありながら、事実上インターネットからの直接アクセスをネットワーク層(L4)で遮断できます。
構築時の注意点: SG 設定の空白期間と対策
ALB が Internet-facing(パブリック)である場合、構築時のリスクはさらに高まります。手順を間違えると、一瞬でも世界中にポートを開放してしまう可能性があるからです。
危険なパターン:一瞬だけ全公開
AWS CLI や Terraform 等でパブリック ALB を作成する際、もしセキュリティグループ(SG)を明示しない(デフォルトSGが当たる)、あるいは「とりあえず疎通確認用」として 0.0.0.0/0 を許可した SG をアタッチしてしまうとどうなるでしょうか?
安全な構築手順
このリスクを防ぐために、以下の順序で構築することを推奨します。
-
Ingress(インバウンドルール)が空の SG を作成する
- まずはどこからの通信も受け付けない SG を作ります。
-
その SG をアタッチして ALB を構築する
- この時点で ALB はパブリック IP を持ちますが、SG が閉じているためアクセスできません(安全)。
- VPC Origin(ENI)を構築する
-
SG の Ingress を更新する
- ここで初めて、VPC Origin の ENI に付与された SG ID からの通信を許可します。
CLI での実装イメージは以下のようになります。
# 1. ルールなしのSG作成(これが重要!)
ALB_SG_ID=$(aws ec2 create-security-group \
--group-name alb-sg-empty \
--description "Empty SG for ALB" \
--vpc-id <VPC_ID> \
--query 'GroupId' \
--output text)
# 2. ALB作成 (Internet-facing で作成)
# この時点で Public IP が付与されるが、SG が空なので誰もアクセスできない
aws elbv2 create-load-balancer \
--name my-alb \
--scheme internet-facing \
--security-groups $ALB_SG_ID ...
# 3. VPC Origin の作成 (コンソール等で実施し、自動作成された SG ID を確認)
# 例: VPC Origin 用 SG ID が sg-vpcorigin123 だとする
# 4. VPC Origin SG からの通信のみ許可
# ここで初めて穴を開ける。ソースに SG ID を指定するのがポイント。
aws ec2 authorize-security-group-ingress \
--group-id $ALB_SG_ID \
--protocol tcp \
--port 80 \
--source-group sg-vpcorigin123
CloudFormation による実装例
最後に、この「安全な構成」を構築する CloudFormation テンプレートを紹介します。
VPC Origin が自動作成するセキュリティグループ ID は、リソース作成前には分からないため、「最初は空パラメータでデプロイし、後から SG ID を入力して更新する」 というフローをとることで、安全かつ厳密な設定が可能になります。
AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFront VPC Origin with Internet-facing ALB
Parameters:
VpcId:
Type: AWS::EC2::VPC::Id
SubnetIds:
Type: List<AWS::EC2::Subnet::Id>
Description: Public Subnets for Internet-facing ALB
# 【ポイント】
# 初回デプロイ時は空欄にしておく。
# VPC Origin 作成後に、ここに SG ID を入力してスタックを更新する。
CloudFrontVpcOriginSecurityGroupId:
Type: String
Description: (Optional) The Security Group ID of the CloudFront VPC Origin. Leave empty initially.
Default: ""
Conditions:
# IDが入力された時だけルールを作成する条件定義
HasVpcOriginSgId: !Not [!Equals [!Ref CloudFrontVpcOriginSecurityGroupId, ""]]
Resources:
# ------------------------------------------------------------
# 1. Security Group for ALB
# ------------------------------------------------------------
# ポイント: 初期状態では Ingress ルールを持たない SG
# これにより ALB 作成時の不正アクセスを防止する
AlbSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Security Group for ALB
VpcId: !Ref VpcId
SecurityGroupIngress: []
# ------------------------------------------------------------
# 2. Internet-facing ALB
# ------------------------------------------------------------
# パブリックALBだが、SGが閉じているので安全
PublicAlb:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Scheme: internet-facing
SecurityGroups:
- !Ref AlbSecurityGroup
Subnets: !Ref SubnetIds
Type: application
AlbListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref PublicAlb
Port: 80
Protocol: HTTP
DefaultActions:
- Type: fixed-response
FixedResponseConfig:
ContentType: "text/plain"
MessageBody: "Hello from VPC Origin via Public ALB!"
StatusCode: "200"
# ------------------------------------------------------------
# 3. CloudFront VPC Origin & Distribution
# ------------------------------------------------------------
CfVpcOrigin:
Type: AWS::CloudFront::VpcOrigin
Properties:
VpcOriginEndpointConfig:
Name: MyVpcOrigin
Arn: !Ref PublicAlb
HTTPPort: 80
HTTPSPort: 443
OriginProtocolPolicy: http-only
OriginSSLProtocols:
- TLSv1.2
CfDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Enabled: true
Origins:
- Id: MyVpcOriginSource
DomainName: !GetAtt PublicAlb.DNSName
VpcOriginConfig:
VpcOriginId: !GetAtt CfVpcOrigin.Id
DefaultCacheBehavior:
TargetOriginId: MyVpcOriginSource
ViewerProtocolPolicy: redirect-to-https
AllowedMethods: [GET, HEAD]
CachedMethods: [GET, HEAD]
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
# ------------------------------------------------------------
# 4. Security Group Ingress Rule (後から追加)
# Condition を使い、ID が渡された時だけ作成される
# ------------------------------------------------------------
AlbIngressFromVpcOrigin:
Type: AWS::EC2::SecurityGroupIngress
Condition: HasVpcOriginSgId
Properties:
GroupId: !Ref AlbSecurityGroup # ALB側
IpProtocol: tcp
FromPort: 80
ToPort: 80
SourceSecurityGroupId: !Ref CloudFrontVpcOriginSecurityGroupId # VPC Origin側
Description: Allow traffic only from CloudFront VPC Origin
デプロイの手順
-
初回デプロイ:
CloudFrontVpcOriginSecurityGroupIdパラメータは空欄のままデプロイします。- -> ALB は作成されますが、アクセスはできません。VPC Origin も同時に作成されます。
-
SG ID 確認: マネジメントコンソールの CloudFront > VPC origins 画面で、作成された VPC Origin の詳細を確認し、「セキュリティグループ ID」 (例:
sg-0123...) をコピーします。 -
スタック更新: CloudFormation スタックを更新し、パラメータに先ほどの SG ID を入力します。
- ->
AlbIngressFromVpcOriginリソースが作成され、CloudFront からの通信のみが許可されます。
- ->
まとめ
CloudFront VPC Origin は、アーキテクチャをシンプルにしつつ、セキュリティレベルを上げてくれる便利な機能です。
- カスタムヘッダー運用からの解放
- パブリック ALB でもインターネットからの直接アクセスを遮断可能
- VPC Origin SG をソースに指定することで、より厳密なアクセス制御を実現
「諸事情で ALB はパブリックにしておく必要があるが、CloudFront 以外からのアクセスは防ぎたい」というケースにおいて、VPC Origin は非常に強力な選択肢になります。構築の際は SG の設定順序に気をつけてください!
Discussion