この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので十分ご注意ください。
はじめに
半年前にCloudFrontでOrigin Access Control (OAC)を利用したアクセス制御のハンズオンを検証しましたが、検証とは名ばかりの簡単な確認だけだったので、改めてS3と組み合わせたCFn構築をしてみようと思います。
ざっくりOACとは
CloudFrontのオリジンにS3を指定する際、S3へのアクセスをCloudFrontだけに制限するための設定。
もともとのS3へアクセス制限を行う OAIという機能よりも、OACでは以下にも対応しているため より細かい設定ができる。
・AWS KMS による Amazon S3 サーバー側の暗号化 (SSE-KMS)
・Amazon S3 に対する動的なリクエスト (PUT と DELETE)
構成図
ハンズオン
構築のながれ
1.Amazon S3作成 :index.htmlの保存先を構築
2.Amazon CloudFront作成:OAC + CloudFrontの構築
3.AWS KMS作成 :S3オブジェクト暗号用鍵構築
4.Amazon S3更新 :KMSを利用した暗号化 + バケットポリシー更新
1.Amazon S3作成:index.htmlの保存先を構築
・コメントアウト部分の2箇所は、CloudFront
とKMS
が構築された後にアンコメントして更新
AWSTemplateFormatVersion: '2010-09-09'
Description: S3 Stack
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "S3 Configuration"
Parameters:
- S3BucketName
- AccessControl
- BlockPublicAcls
- BlockPublicPolicy
- IgnorePublicAcls
- RestrictPublicBuckets
- ExpirationInDays
- EventBridgeConfiguration
- TagsName
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
S3BucketName:
Type: String
Default: "【ユニーク バケット名】"
Description: Type of this BacketName.
VersioningConfiguration:
Type: String
Default: "Enabled"
Description: VersioningConfiguration.
AccessControl:
Type: String
Description: AccessControl.
Default: "Private"
AllowedValues: [ "Private", "PublicRead", "PublicReadWrite", "AuthenticatedRead", "LogDeliveryWrite", "BucketOwnerRead", "BucketOwnerFullControl", "AwsExecRead" ]
BlockPublicAcls:
Type: String
Description: BlockPublicAcls.
Default: "True"
AllowedValues: [ "True", "False" ]
BlockPublicPolicy:
Type: String
Description: BlockPublicPolicy.
Default: "True"
AllowedValues: [ "True", "False" ]
IgnorePublicAcls:
Type: String
Description: IgnorePublicAcls.
Default: "True"
AllowedValues: [ "True", "False" ]
RestrictPublicBuckets:
Type: String
Description: RestrictPublicBuckets.
Default: "True"
AllowedValues: [ "True", "False" ]
ExpirationInDays:
Type: String
Description: Lifecycle Days.
Default: "7"
TagsName:
Type: String
Description: UserName
Default: "【タグ名】"
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------#
Resources:
# ------------------------------------------------------------#
# S3
# ------------------------------------------------------------#
S3Bucket:
Type: AWS::S3::Bucket
Properties:
# BucketEncryption:
# ServerSideEncryptionConfiguration:
# - BucketKeyEnabled: true
# ServerSideEncryptionByDefault:
# KMSMasterKeyID: !ImportValue KMSKeyId
# SSEAlgorithm: aws:kms
BucketName: !Ref S3BucketName
VersioningConfiguration:
Status: !Ref VersioningConfiguration
AccessControl: !Ref AccessControl
PublicAccessBlockConfiguration:
BlockPublicAcls: !Ref BlockPublicAcls
BlockPublicPolicy: !Ref BlockPublicPolicy
IgnorePublicAcls: !Ref IgnorePublicAcls
RestrictPublicBuckets: !Ref RestrictPublicBuckets
LifecycleConfiguration:
Rules:
- Id: LifeCycleRule
Status: Enabled
ExpirationInDays: !Ref ExpirationInDays
VersioningConfiguration:
Status: Enabled
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: 404.html
Tags:
- Key: "User"
Value: !Ref TagsName
# S3BucketPolicy:
# Type: AWS::S3::BucketPolicy
# Properties:
# Bucket: !Ref S3Bucket
# PolicyDocument:
# Version: "2008-10-17"
# Statement:
# - Sid: "AllowCloudFrontServicePrincipal"
# Effect: "Allow"
# Principal:
# Service:
# - "cloudfront.amazonaws.com"
# Action:
# - "s3:GetObject"
# Resource:
# - !Sub ${S3Bucket.Arn}/*
# Condition:
# StringEquals:
# AWS:SourceArn:
# - !Join
# - ''
# - - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/'
# - !ImportValue CloudFrontID
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
S3BucketName:
Value: !Ref S3Bucket
Export:
Name: cfn-s3-BucketName
S3DomainName:
Value: !GetAtt S3Bucket.DomainName
Export:
Name: cfn-s3-DomainName
2.Amazon CloudFront作成:OAC + CloudFrontの構築
・S3のDNSをOriginにして設定(静的Webサイトホスティングではない)
・「S3OriginConfig」の「OriginAccessIdentity」は空で指定しOAIを利用しないように設定(記載がないとエラー表示)
AWSTemplateFormatVersion: "2010-09-09"
Description: CloudFront Stack
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "OriginAccessControl Configuration"
Parameters:
- OACName
- Label:
default: "CloudFront Configuration"
Parameters:
- TagsName
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
OACName:
Type: String
Description: OAC Name
Default: "【OAC Name】"
TagsName:
Type: String
Description: UserName
Default: "【タグ名】"
Resources:
# ------------------------------------------------------------#
# CloudFront
# ------------------------------------------------------------#
OAC:
Type: AWS::CloudFront::OriginAccessControl
Properties:
OriginAccessControlConfig:
Description: Access Control
Name: !Ref OACName
OriginAccessControlOriginType: s3
SigningBehavior: always
SigningProtocol: sigv4
CloudFront:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Comment: "CloudFront-S3 URL"
DefaultRootObject: index.html
DefaultCacheBehavior:
AllowedMethods:
- GET
- HEAD
CachedMethods:
- GET
- HEAD
CachePolicyId: 658327ea-f89d-4fab-a63d-7e88639e58f6
TargetOriginId: S3
ViewerProtocolPolicy: allow-all
Enabled: true
Origins:
- DomainName: !ImportValue cfn-s3-DomainName
Id: S3
OriginAccessControlId: !GetAtt OAC.Id
S3OriginConfig:
OriginAccessIdentity: ''
PriceClass: PriceClass_200
Tags:
- Key: UserName
Value: !Ref TagsName
Outputs:
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
CloudFrontID:
Value: !Ref CloudFront
Export:
Name: CloudFrontID
3.AWS KMS作成:S3オブジェクト暗号用鍵構築
・PrincipalとしてCloudFront
が利用できるようにします。
AWSTemplateFormatVersion: "2010-09-09"
Description: KMS Stack
# ------------------------------------------------------------#
# Metadata
# ------------------------------------------------------------#
Metadata:
"AWS::CloudFormation::Interface":
ParameterGroups:
- Label:
default: "KMS Configuration"
Parameters:
- KMSId
- UserName
# ------------------------------------------------------------#
# InputParameters
# ------------------------------------------------------------#
Parameters:
KMSId:
Type: String
Description: KMSId
Default: "DEV"
UserName:
Type: String
Description: UserName
Default: "【IAM ユーザー名】"
Resources:
# ------------------------------------------------------------#
# KMS
# ------------------------------------------------------------#
KMSAlias:
Type: AWS::KMS::Alias
Properties:
AliasName: !Sub alias/cfn-${UserName}
TargetKeyId: !Ref KMSKey
KMSKey:
Type: AWS::KMS::Key
Properties:
Description: Encryption KMS key
Enabled: true
KeyPolicy:
Version: '2012-10-17'
Id: !Sub cfn-${KMSId}-kms
Statement:
- Sid: Enable IAM User Permissions
Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:root
Action: 'kms:*'
Resource: '*'
- Sid: 'Allow access for Key Administrators'
Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:user/${UserName}
Action:
- 'kms:Create*'
- 'kms:Describe*'
- 'kms:Enable*'
- 'kms:List*'
- 'kms:Put*'
- 'kms:Update*'
- 'kms:Revoke*'
- 'kms:Disable*'
- 'kms:Get*'
- 'kms:Delete*'
- 'kms:TagResource'
- 'kms:UntagResource'
- 'kms:ScheduleKeyDeletion'
- 'kms:CancelKeyDeletion'
- 'kms:CreateGrant'
- 'kms:ListGrants'
- 'kms:RevokeGrant'
Resource: '*'
- Sid: 'Allow use of the key'
Effect: Allow
Principal:
AWS: !Sub arn:aws:iam::${AWS::AccountId}:user/${UserName}
Action:
- 'kms:Encrypt'
- 'kms:Decrypt'
- 'kms:ReEncrypt*'
- 'kms:GenerateDataKey*'
- 'kms:DescribeKey'
Resource: '*'
- Sid: 'Allow use of the key'
Effect: Allow
Principal:
Service:
- cloudfront.amazonaws.com
Action:
- 'kms:Decrypt'
- 'kms:Encrypt'
- 'kms:GenerateDataKey*'
Resource: '*'
Condition:
StringEquals:
Aws:SourceArn:
- !Join
- ''
- - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/'
- !ImportValue CloudFrontID
PendingWindowInDays: 7
Tags:
- Key: UserName
Value: !Ref UserName
# ------------------------------------------------------------#
# Outputs
# ------------------------------------------------------------#
Outputs:
KMSKeyId:
Value: !GetAtt KMSKey.KeyId
Export:
Name: KMSKeyId
KMSArn:
Value: !GetAtt KMSKey.Arn
Export:
Name: KMSArn
4.Amazon S3更新:KMSを利用した暗号化 + バケットポリシー更新
・手順1で構築したテンプレートのコメントアウトされていた2箇所をアンコメント
【KMSによる暗号化部分】
BucketEncryption:
ServerSideEncryptionConfiguration:
- BucketKeyEnabled: true
ServerSideEncryptionByDefault:
KMSMasterKeyID: !ImportValue KMSKeyId
SSEAlgorithm: aws:kms
【CloudFrontからのアクセスを許可するバケットポリシー】
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref S3Bucket
PolicyDocument:
Version: "2008-10-17"
Statement:
- Sid: "AllowCloudFrontServicePrincipal"
Effect: "Allow"
Principal:
Service:
- "cloudfront.amazonaws.com"
Action:
- "s3:GetObject"
Resource:
- !Sub ${S3Bucket.Arn}/*
Condition:
StringEquals:
AWS:SourceArn:
- !Join
- ''
- - !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/'
- !ImportValue CloudFrontID
挙動の確認
1.S3バケットに index.html をアップロード
1.1.index.htmlの詳細
<html>
<head>
<meta charset="UTF-8">
<title>テストページ</title>
</head>
<body bgcolor="#10100E" text="#cccccc">Hello World!!<br/></body>
</html>
1.2.S3に index.html を保存した画面
2.CloudFront のドメイン名を取得
・CloudFront > ディストリビューション より取得
3.ブラウザで確認
4.CloudFrontでOAC
の設定を確認
・CloudFront > ディストリビューション > タブ:オリジンを押下 > オリジンS3
を編集を押下
さいごに
CloudFront
のデフォルトルートオブジェクト名(index.html)をテンプレートに入力していないなどで、何回かAccessDeniedにもなりましたが無事に表示することが出来ました。
新しいものを億劫がらずに よりセキュアな構築の考え方を取得していきたいです。