この記事ではCloudFrontとALB(Beanstalk)の間のセキュリティ対策についてハンズオン形式でまとめます。
前提条件
- 前述の環境は設定済みでサイトも問題なく表示できている前提で進めます
- Beanstalkのログも取れることを前提としています
- コマンドも使えることを前提としています
テスト環境
当方では以下のような環境で行っていますが、Beanstalkを使用していなくても基本的な考え方は同じです。
Route53 → CloudFront → Elastic Beanstalk(ALB → EC2)→ RDS
そしてALBまでの通信はHTTPSになっています。つまりALBとEC2の間はHTTPで通信する形です。
設定検証
まずはデフォルトの設定のまま検証してみます。
ALBのDNS名
ALBのコンソールに表示されている以下のようなアドレスです。
awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
http://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
https://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
上記のアドレスをブラウザに打ち込みアクセスするとサイトが表示されると思います。
httpsはブラウザの警告が出ますがそのまま進むとサイトが表示されますね。
※今回自分管轄内のドメインですので警告をスルーしています。自分の管轄外のドメインの場合はアクセスすると危険ですのでご注意ください。
またターミナルからcurlを実行してみます。
curl -I http://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
HTTP/x.x 200 OK
Date: xxx, xx xxx 2026 xx:xx:xx GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Server: nginx/x.xx.x
curl -I https://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
curl: (60) SSL: no alternative certificate subject name matches target host name 'awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com'
More details here: https://curl.se/docs/sslcerts.html
curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
httpsは証明書のエラーが出ていますが、ブラウザ、curl共にhttpは通信が通ってしまいます。
CloudFrontとALBの間の通信を最小限に
通常ALBが入り口になっている構成ではhttps、http共に0.0.0.0/0という全開放の設定でどこからでも通信を受け付けられるようにしてユーザーからのアクセスを受け止めています。しかし、ALBの前段にCloudFrontを設置する場合はALBはCloudFrontからの通信を受ければよいだけというケースがほとんどです。動的な処理をするパスもCLoudFront側でキャッシュを無効に設定することが可能なのでALBに直接アクセスするケースというのは少ないと思います。ですからここの通信を最小限に絞っていきます。
最小限の設定要件
以下の要件を設定していきます。
- ALBの80番ポートリスナーを削除
- ALBの443番ポートリスナーをカスタマイズ
- ALBのセキュリティグループをカスタマイズ
ALBの80番ポートリスナーを削除
ALB80番ポートのリスナーを削除します。こうすることでhttpでのアクセスをALBが受け付けなくなります。
コンソールから設定する場合
コンソールから設定する場合は以下のようにします。
- ALBのコンソールにアクセスします。
- 「リスナーとルールタブ」を選択し、HTTP:80にチェックを入れて、「リスナーの管理」→「リスナーの削除」を行います。
EBCLI設定ファイル
以下EBCLIを使用している場合は設定ファイルに書くとeb create時にALBの80番ポートリスナーを作成せずに立ち上げることができます。
.ebextensions/origin-protection.config
option_settings:
aws:elbv2:listener:default:
ListenerEnabled: "false"
ALBの443番ポートリスナーをカスタマイズ
ALBの443番ポートリスナーは以下の設定をします。
- デフォルトのレスポンスを403に設定します。
- CloudFrontのカスタムヘッダの値が一致したらターゲットグループに通信をフォワードします。
コンソールから設定する場合
CloudFrontコンソールでの設定
まずはCloudFront側にカスタムヘッダーを設定します。これはCloudFrontからALBに通信する際に、CloudFront側で任意のヘッダーを付与する設定です。
CloudFrontのコンソールから以下のように設定します。
- CloudFrontのコンソールにアクセスします。
- 対象のディストリビューションを選択します。
- 「オリジン」タブを選択します。
- ALBを向いているオリジンを選択し、「編集」をクリックします。
- 「カスタムヘッダーを追加」をクリックします。
- 以下のような値を設定します。
ヘッダー名
X-Origin-Verify
(ここでは説明のためX-Origin-Verifyを使用しています。本番では任意の長いランダム文字列を使用します。最大40文字です。)
値
任意の長いランダム文字列(最大128文字)
ランダム文字列は以下のようなコマンドで作成できます。
openssl rand -hex 32
この値はCloudFrontとALBの間で共有する秘密値です。公開リポジトリなど誰でも見られる場所に書くと、CloudFront以外のHTTPクライアントがアクセスできてしまうので実際の値を書かないように注意してください。
ALBコンソールでの設定
次にALB側で、このカスタムヘッダーが付いている通信だけをターゲットグループへフォワードするように設定します。
- ALBのコンソールにアクセスします
- 「リスナーとルール」タブを開きます
- HTTPS:443のリスナーを選択します
- 「ルールの管理」をクリックします
- 新しいルールを追加します
- 条件に「HTTPヘッダー」を選択します
- ヘッダー名に以下を設定します
X-Origin-Verify
8.値にはCloudFront側に設定したランダム文字列と同じ値を設定します。9.アクションには「ターゲットグループへ転送」を選択し、使用しているターゲットグループを選択します。10.ルールの優先度をデフォルトルールより上に設定します。
最後にデフォルトルールを変更します。
デフォルトルールは、CloudFrontのカスタムヘッダーが付いていない通信に対する処理です。ここではターゲットグループへフォワードせず、403を返すようにします。
- ALBコンソールのHTTPS:443のリスナーのルール管理画面を開きます。
- デフォルトルールを編集します。
- アクションを「固定レスポンスを返す」に変更します。
- ステータスコードに403を設定します。
- レスポンス本文は任意ですが、今回は以下のようにします。
Forbidden
これで以下のような動きになります。
CloudFrontから来た通信
かつ
X-Origin-Verify の値が一致する
→ ターゲットグループへフォワード
それ以外の通信
→ ALBで403を返す
つまりALBのアドレスやBeanstalkのドメインに直接アクセスしても、CloudFrontが付けるカスタムヘッダーが存在しないため、EC2まで通信が届かなくなります。
EBCLI設定ファイル
EBCLIを使用している場合は、以下のように設定ファイルに書くことができます。
注意点として、既に以下のような設定でBeanstalk管理の443番リスナーを作っている場合は、この設定と競合しますので削除しましょう。
aws:elbv2:listener:443:
Protocol: HTTPS
SSLCertificateArns: arn:aws:acm:ap-northeast-1:xxxxxxxxxxxx:certificate/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
.ebextensions/origin-protection-sample.config
option_settings:
aws:elbv2:listener:default:
ListenerEnabled: "false"
Resources:
OriginVerifiedHTTPSListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn:
Ref: AWSEBV2LoadBalancer
Port: 443
Protocol: HTTPS
Certificates:
- CertificateArn: ACM_CERTIFICATE_ARN_HERE
SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06
DefaultActions:
- Type: fixed-response
FixedResponseConfig:
ContentType: text/plain
MessageBody: Forbidden
StatusCode: "403"
OriginVerifyHeaderAllowRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
ListenerArn:
Ref: OriginVerifiedHTTPSListener
Priority: 1
Conditions:
- Field: http-header
HttpHeaderConfig:
HttpHeaderName: X-Origin-Verify
Values:
- ORIGIN_VERIFY_SECRET_HERE
Actions:
- Type: forward
TargetGroupArn:
Ref: AWSEBV2LoadBalancerTargetGroup
以下の値は自分の環境に合わせて置き換えます。
ACM_CERTIFICATE_ARN_HERE
ALBのHTTPSリスナーで使用するACM証明書ARN
ORIGIN_VERIFY_SECRET_HERE
CloudFrontのカスタムヘッダーに設定したランダム文字列
この設定では、ALBの443番リスナーのデフォルトアクションを403にしています。そして、CloudFrontから送られてくる X-Origin-Verify ヘッダーの値が一致した場合のみ、Beanstalkのターゲットグループへフォワードします。
そのため、ALBに直接アクセスされた場合は、カスタムヘッダーが存在しないので403になります。
ALBのセキュリティグループをカスタマイズ
次にALBのセキュリティグループをカスタマイズします。
ここまでの設定では、ALBに直接アクセスされた場合でもALBのリスナーで403を返すようになりました。しかし、そもそもALBに対してCloudFront以外からの通信を受け付ける必要はありません。
そこで、ALBのセキュリティグループのインバウンドルールをCloudFrontからの通信(自分が使用している以外のCloudFrontエッジロケーションも含む)だけに絞ります。
このとき使用するのがCloudFront managed prefix listです。
CloudFront managed prefix list
CloudFront managed prefix listは、CloudFrontがオリジンにアクセスする際に使用するIPレンジをAWS側で管理してくれているプレフィックスリストです。自分でCloudFrontのIPレンジを追跡して更新する必要がなくなります。
東京リージョンで使用する場合は、以下のコマンドでPrefix List IDを確認できます。
aws ec2 describe-managed-prefix-lists \
--region ap-northeast-1 \
--filters Name=prefix-list-name,Values=com.amazonaws.global.cloudfront.origin-facing \
--query 'PrefixLists[0].[PrefixListId,PrefixListName]' \
--output table
以下のような値が取得できます。
pl-xxxxxxxxxxxxxxxxx com.amazonaws.global.cloudfront.origin-facing
この pl-xxxxxxxxxxxxxxxxx をALBのセキュリティグループの送信元に指定します。
コンソールから設定する場合
コンソールから設定する場合は以下のようにします。
- EC2コンソールにアクセスします。
- 「セキュリティグループ」を開きます。
- ALBに紐づいているセキュリティグループを選択します。
- 「インバウンドルール」を編集します。
- 既存の以下のようなルールを削除します。
HTTP 80 0.0.0.0/0
HTTPS 443 0.0.0.0/0
80番ポートはすでにリスナーを削除しているため不要です。
次に、443番ポートだけCloudFront managed prefix listから許可します。
タイプ: HTTPS
プロトコル: TCP
ポート範囲: 443
送信元: com.amazonaws.global.cloudfront.origin-facing
コンソール上でPrefix List名が選択できない場合は、先ほど確認したPrefix List IDを指定します。
pl-xxxxxxxxxxxxxxxxx
これでALBはCloudFrontのオリジン向け通信元からの443番通信だけを受け付けるようになります。
注意点として、既存の 0.0.0.0/0 のルールを編集してPrefix Listに変更しようとするとエラーになる場合があります。その場合は、既存ルールを削除してから、新しいルールとしてPrefix Listを指定してください。
EBCLI設定ファイル
ALBのセキュリティグループもEBCLIで指定することができます。
.ebextensions/origin-protection.config
option_settings:
aws:elbv2:loadbalancer:
SecurityGroups: '`{ "Ref" : "SecurityGroupSampleCloudFrontOnlyALB" }`'
ManagedSecurityGroup: '`{ "Ref" : "SecurityGroupSampleCloudFrontOnlyALB" }`'
Resources:
SecurityGroupSampleCloudFrontOnlyALB:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTPS from CloudFront origin-facing only to Beanstalk ALB
VpcId:
Fn::GetOptionSetting:
Namespace: aws:ec2:vpc
OptionName: VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourcePrefixListId: pl-xxxxxxxx
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
上記でALBにアタッチされるセキュリティグループの設定を行なっています。セキュリティグループではCloudFront managed prefix listにあるサーバーからの通信のみ受け付けるという設定にしています。
※既存環境に適用する場合、Elastic Beanstalkが作成済みのロードバランサー用セキュリティグループとの入れ替えで失敗する場合があります。
まず検証環境で eb deploy し、ALBに意図したセキュリティグループだけが付与されていることを確認してください。
EC2側のセキュリティグループについては、Beanstalkに管理させる構成にしています。BeanstalkがロードバランサーからEC2への通信に必要なルールを設定してくれるためです。
もしEC2側のセキュリティグループも自分で管理する場合は、EC2側ではALBのセキュリティグループからのHTTP 80のみを許可します。
環境や設定によっては、EC2側のセキュリティグループにSSH 22が広く開いている場合があります。
不要であれば削除し、必要な場合も自分の固定IPや踏み台サーバーなどに限定してください。
EC2インバウンド:
HTTP 80
送信元: ALBのセキュリティグループ
今回行った設定まとめ
CloudFront→ALB間の通信を最小限に設定しました。
今回設定した内容は以下です。
1. ALBの80番リスナーを無効化
2. CloudFrontのオリジンにカスタムヘッダーを追加
3. ALBの443番リスナーのデフォルトアクションを403に変更
4. カスタムヘッダーが一致した場合のみターゲットグループへフォワード
5. ALBのセキュリティグループをCloudFront managed prefix listのみに制限
これにより、CloudFrontを通らない通信はALBで拒否されるか、ALBのセキュリティグループで遮断されます。
通常のアクセス経路は以下になります。
ユーザー
↓
CloudFront
↓ X-Origin-Verify ヘッダー付与
ALB
↓ ヘッダー値が一致した場合のみフォワード
EC2
一方、ALBのアドレスやBeanstalkのドメインに直接アクセスした場合は、CloudFrontのカスタムヘッダーが付かないため通信が通りません。
EB CLI設定ファイルまとめ
EB CLI設定ファイルのまとめたものは以下となります。
.ebextensions/origin-protection-sample.config
option_settings:
aws:elbv2:listener:default:
ListenerEnabled: "false"
aws:elbv2:loadbalancer:
SecurityGroups: '`{ "Ref" : "SecurityGroupSampleCloudFrontOnlyALB" }`'
ManagedSecurityGroup: '`{ "Ref" : "SecurityGroupSampleCloudFrontOnlyALB" }`'
Resources:
OriginVerifiedHTTPSListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn:
Ref: AWSEBV2LoadBalancer
Port: 443
Protocol: HTTPS
Certificates:
- CertificateArn: ACM_CERTIFICATE_ARN_HERE
SslPolicy: ELBSecurityPolicy-TLS13-1-2-2021-06
DefaultActions:
- Type: fixed-response
FixedResponseConfig:
ContentType: text/plain
MessageBody: Forbidden
StatusCode: "403"
OriginVerifyHeaderAllowRule:
Type: AWS::ElasticLoadBalancingV2::ListenerRule
Properties:
ListenerArn:
Ref: OriginVerifiedHTTPSListener
Priority: 1
Conditions:
- Field: http-header
HttpHeaderConfig:
HttpHeaderName: X-Origin-Verify
Values:
- ORIGIN_VERIFY_SECRET_HERE
Actions:
- Type: forward
TargetGroupArn:
Ref: AWSEBV2LoadBalancerTargetGroup
SecurityGroupSampleCloudFrontOnlyALB:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow HTTPS from CloudFront origin-facing only to Beanstalk ALB
VpcId:
Fn::GetOptionSetting:
Namespace: aws:ec2:vpc
OptionName: VPCId
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 443
ToPort: 443
SourcePrefixListId: pl-xxxxxxxx
SecurityGroupEgress:
- IpProtocol: -1
CidrIp: 0.0.0.0/0
設定後テスト
まずはブラウザからアクセスしてみます。
http://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
https://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
共にタイムアウトになるのが確認できると思います。
curlコマンドでも試してみましょう。
curl -I http://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
curl: (7) Failed to connect to http://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com port 80 after 150148 ms: Couldn't connect to server
curl -I https://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com
curl: (28) Failed to connect to https://awseb--AWSEB-xxxxxxxxxxxxxxxx.ap-northeast-1.elb.amazonaws.com port 443 after 150051 ms: Couldn't connect to server
curlコマンドでもアクセスが拒否されることが確認できました。
Beanstalkドメイン
Beanstalkでは環境を立ち上げると以下のようなドメインが払い出されます。
xxxxxxxxxxxxxxxxxx.ap-northeast-1.elasticbeanstalk.com
そしてこちらでもサイトが表示できます。
このアドレスはBeanstalk環境にALBがあればALBへ、ALBがなければEC2へというようにアプリケーションへの通信をルーティングするものです。
CloudFrontを前段に置いても今回のような設定をしなければ通信は通り、サイトへのアクセスは可能になります。
しかし今回CloudFront→ALBの通信を最小限にしたことでこのBeanstalkドメインへの通信も防ぐことができるようになりました。
ブラウザからアクセスしてもタイムアウトになります。
振り返り
AWSのサービスを触り始めたばかりだとどのサービスがどの位置にいるのかがふわっとしていると思います。AWS管理のネットワーク内なのか、その内側のVPC内なのか、サブネット内なのかなどですね。特に今回のようにCloudFrontなどのマネージドサービスを組み合わせる場合は通信が今どの位置にいるのかを意識する必要があります。そう言った意味でもこのハンズオンを行うことで基本的な通信の流れや、入り口の理解にお役に立てれば幸いです。
