CloudFrontからS3コンテンツを配信する際に、OACでオリジンへのアクセスを制限する
以下のページで、S3バケットのコンテンツをCloudFrontから配信する際に、OAIを使用することで、オリジンへのアクセスを制限する方法をご紹介しました。
2022年8月にOAC(Origin Access Control)が発表されました。
AWS公式としては、OAIよりもOACを使用することを推奨しています。
OAC は以下をサポートしているため、OAC の使用をお勧めします。
すべての AWS リージョンのすべての Amazon S3 バケット (2022 年 12 月以降に開始されたオプトインリージョンを含む)
AWS KMS による Amazon S3 サーバー側の暗号化 (SSE-KMS)
Amazon S3 に対する動的なリクエスト (POST、PUT など)
Amazon S3 オリジンへのアクセスの制限
今回はOACを使用して、CloudFront経由でS3バケットにアクセスする方法を確認します。
構築する環境
全体的な構成は冒頭のページと同様です。
CloudFrontのOACを作成し、S3バケットポリシーには、このOACからのアクセスを許可する設定を行います。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-fa/tree/main/096
テンプレートファイルのポイント解説
S3バケット
Resources:
Bucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Ref Prefix
AccessControl: Private
Code language: YAML (yaml)
コンテンツを配置するバケットです。
OACを使用する上で、S3バケット側は特別な設定は不要です。
OAC
Resources:
OAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Name: !Ref Prefix
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
Code language: YAML (yaml)
OACを作成する上で、重要なパラメータは以下の3つです。
1つ目はOriginAccessControlOriginTypeプロパティです。
本パラメータは「s3」のみ取り得ます。
2つ目はSigningBehaviorプロパティです。
本パラメータはCloudFrontがオリジン(S3)にアクセスする際に、署名するかに関するものです。
AWS公式では、以下の通り言及されています。
CloudFront が常に着信リクエストに署名するので、アプリケーションが常に動作することを保証するため、ほとんどのお客様は “ 署名リクエスト ” オプションを使用することをお勧めします。さらに、CloudFront がリクエストに署名することで、クライアントと CloudFront 間のデータ転送が少なくなり、アプリケーションのパフォーマンスが向上します。
Amazon CloudFront オリジンアクセスコントロール(OAC)のご紹介
上記に従い、常時署名リクエストするように「always」に設定します。
3つ目はSigningProtocolプロパティです。
本パラメータは「sigv4」のみ取り得ます。
CloudFront
Resources:
Distribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
CachedMethods:
- GET
- HEAD
Compress: true
ForwardedValues:
Cookies:
Forward: none
QueryString: false
TargetOriginId: !Ref BucketName
ViewerProtocolPolicy: allow-all
DefaultRootObject: index.html
Enabled: true
Origins:
- DomainName: !Ref BucketRegionalDomainName
Id: !Ref BucketName
#CustomOriginConfig:
OriginAccessControlId: !GetAtt OAC.Id
S3OriginConfig:
OriginAccessIdentity: ""
PriceClass: PriceClass_All
Code language: YAML (yaml)
OACを使用する上で、CloudFrontの設定で重要な項目はOriginsプロパティです。
先ほど定義したOACのIDをOriginAccessControlIdプロパティに設定します。
S3OriginConfigプロパティもポイントです。
内部のOriginAccessIdentityプロパティに空文字列を指定します。
仮に上記を設定しない場合、CloudFormationスタック作成時に、以下のエラーが発生します。
Invalid request provided: Exactly one of CustomOriginConfig and S3OriginConfig must be specified
ちなみにCustomOriginConfigプロパティは、静的Webサイトホスティングを有効化したS3バケット用のパラメータですから、こちらは設定を行いません。
バケットポリシー
Resources:
BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref BucketName
PolicyDocument:
Statement:
Action:
- s3:GetObject
Effect: Allow
Principal:
Service:
- cloudfront.amazonaws.com
Resource: !Sub "arn:aws:s3:::${BucketName}/*"
Condition:
StringEquals:
AWS:SourceArn: !Sub "arn:aws:cloudfront::${AWS::AccountId}:distribution/${Distribution}"
Code language: YAML (yaml)
以下のAWS公式ページを参考に、バケットポリシーを定義します。
先述のCloudFrontディストリビューションがプリンシパルの場合、オブジェクトの取得するアクションを許可する内容です。
(参考)CloudFormationカスタムリソース
Resources:
CustomResource:
Type: Custom::CustomResource
Properties:
ServiceToken: !GetAtt Function.Arn
Function:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
import boto3
import cfnresponse
import os
bucket_name = os.environ['BUCKET_NAME']
object_name = 'index.html'
object_body = """<html>
<head></head>
<body>
<h1>index.html</h1>
<p>{bucket_name}</p>
</body>
</html>""".format(bucket_name=bucket_name)
content_type = 'text/html'
char_code= 'utf-8'
s3_client = boto3.client('s3')
CREATE = 'Create'
DELETE = 'Delete'
response_data = {}
def lambda_handler(event, context):
try:
if event['RequestType'] == CREATE:
put_response = s3_client.put_object(
Bucket=bucket_name,
Key=object_name,
Body=object_body.encode(char_code),
ContentEncoding=char_code,
ContentType=content_type)
print(put_response)
elif event['RequestType'] == DELETE:
list_response = s3_client.list_objects_v2(
Bucket=bucket_name)
for obj in list_response['Contents']:
delete_response = s3_client.delete_object(
Bucket=bucket_name,
Key=obj['Key'])
print(delete_response)
cfnresponse.send(event, context, cfnresponse.SUCCESS, response_data)
except Exception as e:
print(e)
cfnresponse.send(event, context, cfnresponse.FAILED, response_data)
Environment:
Variables:
BUCKET_NAME: !Ref BucketName
FunctionName: !Sub "${Prefix}-function"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole.Arn
Code language: YAML (yaml)
CloudFormationカスタムリソースを使用して、S3バケットに自動的にオブジェクトを作成します。
今回は先述のS3バケット内にindex.htmlを作成します。
詳細につきましては、以下のページをご確認ください。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- CloudFrontディストリビューション:E3YPSL0CON945
- CloudFrontドメイン名:d1ujnv40lgk4sa.cloudfront.net
- OAC:fa-096
- S3バケット:fa-096
作成されたリソースをAWS Management Consoleから確認します。
OACを確認します。
OACが作成されています。
SigV4で常に署名を実施するように設定されていることがわかります。
CloudFrontディストリビューションに関連づいていることもわかります。
CloudFrontディストリビューション側の設定ページを確認しても、OACの設定が有効化されていることがわかります。
またオリジンとしてS3バケットが指定されていることもわかります。
バケット内にindex.htmlファイルが設置されています。
CloudFormationカスタムリソースに紐づくLambda関数によって、HTMLファイルが自動的に生成されて設置されました。
バケットポリシーを確認すると、CloudFrontをプリンシパルとして、S3バケットへのアクセスを許可する内容であることがわかります。
動作確認
準備が整いましたので、CloudFrontディストリビューションにアクセスします。
S3バケット内のHTMLファイル(index.html)にアクセスすることができました。
本来バケットポリシーによって制限されているバケット内オブジェクトへのアクセスが、OACを使用することによって、CloudFront経由でのアクセスが許可されたということです。
次に直接S3バケットへアクセスします。
拒否されました。
バケットポリシーが動作しており、直接のアクセスが制限されているということです。
まとめ
OACを使用して、CloudFront経由でS3バケットにアクセスする方法をご紹介しました。