この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
AWSチームのすずきです。
CloudFrontとS3の静的ウェブサイトを採用する利点について、諏訪より紹介させて頂きました。
今回、実際の設置に利用しているCloudFormationテンプレートの紹介と、 主に非機能要件のため実施している設定内容について、紹介させていただきます。
設定
S3(静的コンテンツ)
静的ウェブサイト(WebsiteConfiguration)を有効としたS3バケットです。
S3Bucket:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
BucketName: !Sub '${AWS::StackName}'
LifecycleConfiguration:
Rules:
- Id: NoncurrentVersionExpiration
Status: Enabled
NoncurrentVersionExpirationInDays: 45
LoggingConfiguration:
DestinationBucketName: !Ref 'S3BucketAccesslogs'
LogFilePrefix: !Sub 's3/${AWS::StackName}'
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: false
IgnorePublicAcls: true
RestrictPublicBuckets: false
VersioningConfiguration:
Status: Enabled
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: 404.html
Tags:
- Key: CloudFormationArn
Value: !Sub '${AWS::StackName}'
- 誤操作などに備え、バージョニング(VersioningConfiguration)を有効とします。
- S3のストレージ課金の抑制のため、ライフサイクル設定で過去バージョン保持期間を抑制します。
- 意図せぬACL設定に起因するWebコンテンツの改竄を回避するため、ブロックパブリックアクセスはアクセスコントロールリスト (ACL)に対して設定します。
- S3アクセスログを設定し(LoggingConfiguration)、万一不正な利用があった場合でも追跡を可能にします。
- 任意のFQDNで S3のWebホスティング用のエンドポイントをHTTPで公開しない場合、S3のバケット名は公開FQDNに揃える必要はありません。今回、CloudFormation のスタック名を利用する設定としましたが、S3バケットの命名規則の範囲内で利用しやすいものをご利用ください。
S3(アクセスログ)
S3、CloudFront のアクセスログ用のS3バケットです。
S3BucketAccesslogs:
Type: AWS::S3::Bucket
DeletionPolicy: Retain
Properties:
AccessControl: LogDeliveryWrite
BucketName: !Sub '${AWS::StackName}-accesslogs-${AWS::Region}-${AWS::AccountId}'
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
LifecycleConfiguration:
Rules:
- Id: AutoDelete
Status: Enabled
ExpirationInDays: 15
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
Tags:
- Key: CloudFormationArn
Value: !Sub '${AWS::StackName}'
- S3のアクセスログ書込のために必要なACL(LogDeliveryWrite)を設定します。
- 保管費用の抑制のため、ライフサイクルでアクセスログの保持期間を限定します。
- アクセスログに含まれる接続元などの情報の保護のため、追加コストがかからず、S3のログ機能に影響しない範囲で暗号化(SSE)を実施します。
- 意図せぬ公開を防ぐため、パブリックアクセスは全て禁止(RestrictPublicBuckets)とします。
バケットポリシー
静的コンテンツ用S3の公開設定はバケットポリシーを利用します。
S3BucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref 'S3Bucket'
PolicyDocument:
Id: !Sub '${AWS::StackName}-BucketPolicy'
Statement:
- Sid: AddPerm
Effect: Allow
Principal: '*'
Action:
- s3:GetObject
Resource:
- !Sub 'arn:aws:s3:::${S3Bucket}/*'
Condition:
StringEquals:
aws:UserAgent: Amazon CloudFront
- Sid: AddPerm
Effect: Allow
Principal: '*'
Action:
- s3:GetObject
Resource:
- !Sub 'arn:aws:s3:::${S3Bucket}/*'
Condition:
IpAddress:
aws:SourceIp:
- 127.0.0.1/32
#- 0.0.0.0/0
#- ::/0
- UserAgentが「Amazon CloudFront」のアクセスのみを許可し、通常のWebブラウザやBotなどからのアクセスを抑止します。
- 公開用のS3には 秘匿情報は無いものとして、UserAgentのなりすましや、第三者が設置したCloudFrontからの参照させる可能性については容認します。
- 万一S3のAPI課金が嵩むなどの実害が生じた場合には、アクセスログの情報を元に対策を行うものとします。
- CloudFrontのキャッシュ影響がない、コンテンツ確認用の環境が求められる場合に備え、特定拠点のIPについてはS3の直接参照を許可するものとします。
CloudFront
S3の静的ホスティングのエンドポイントをカスタムオリジンとして設定します。
CloudFrontDistribution:
Type: AWS::CloudFront::Distribution
Properties:
DistributionConfig:
Origins:
- Id: CustomOrigin
DomainName: !Sub '${S3Bucket}.s3-website-${AWS::Region}.amazonaws.com'
CustomOriginConfig:
HTTPPort: 80
OriginProtocolPolicy: http-only
Enabled: true
DefaultRootObject: index.html
Logging:
IncludeCookies: 'false'
Bucket: !Sub '${S3BucketAccesslogs}.s3-${AWS::Region}.amazonaws.com'
Prefix: !Sub 'cloudfront/${AWS::StackName}'
CustomErrorResponses:
- ErrorCachingMinTTL: 300
ErrorCode: 403
ResponseCode: 200
ResponsePagePath: /index.html
Comment: !Sub '${AWS::StackName}-distribution'
DefaultCacheBehavior:
TargetOriginId: CustomOrigin
ForwardedValues:
QueryString: false
DefaultTTL: 300
MaxTTL: 300
MinTTL: 300
ViewerProtocolPolicy: redirect-to-https
#Aliases:
#- dummy.example.com
#ViewerCertificate:
# SslSupportMethod: sni-only
# AcmCertificateArn: arn:aws:acm:us-east-1:000000000000:certificate/dummy-example-com
Tags:
- Key: CloudFormationArn
Value: !Sub '${AWS::StackName}'
- オリジンとして利用するS3、低コストで高い配信性能が期待できる事からキャッシュのTTLは長くせず(一律300秒)、コンテンツ更新後のキャッシュ消去(Invalidation)は極力実施しない設定とします。
- HTTPを利用したアクセスは、HTTPSにリダイレクトで誘導します。(ViewerProtocolPolicy: redirect-to-https)
- 独自ドメインで公開する場合、ACM(ViewerCertificate)、CNAME(Aliases)は別途実施するものとします。
IAM
S3を操作するWebコンテンツの管理者用、IAMロールを用意します。
S3の操作は、AWSコンソール、CyberDuckなど、多要素認証(MFA)が利用できる環境を利用するものとします。
IamRoleS3access:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub 'iam-role-${AWS::StackName}'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Ref 'IamUserArn'
Action: sts:AssumeRole
Condition:
Bool:
aws:MultiFactorAuthPresent: 'true'
IpAddress:
aws:SourceIp:
- 0.0.0.0/0
- ::/0
Policies:
- PolicyName: S3accessPolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:ListAllMyBuckets
- s3:GetBucketLocation
Resource:
- arn:aws:s3:::*
- Effect: Allow
Action:
- s3:*
Resource:
- !Sub 'arn:aws:s3:::${S3Bucket}/*'
- !Sub 'arn:aws:s3:::${S3Bucket}'
- Effect: Deny
Action:
- s3:PutBucket*
- s3:CreateBucket
- s3:DeleteBucket
- s3:PutObjectAcl
- s3:PutObjectVersionAcl
Resource:
- arn:aws:s3:::*
- Effect: Allow
Action:
- cloudfront:Get*
- cloudfront:List*
Resource:
- '*'
- Effect: Allow
Action:
- cloudfront:CreateInvalidation
Resource:
- !Sub 'arn:aws:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}'
- 信頼できるログイン環境、IAMユーザを限定してロールの利用許可(sts:AssumeRole)を付与します。
-
IAMロールの利用にあたり、接続元のIPアドレス(aws:SourceIp)制限、IPv4、IPv6の範囲とも実施しない設定としました。特定拠点やVPNのIPに限定できる場合には、よりセキュアなS3利用が可能になります。
-
CloudFront のTTLは短い設定としますが、キャッシュ操作(Invalidation)の権限は付与します。
実行例
今回紹介した上記のテンプレート、CloudFormationの設置所要時間は5分42秒でした。
まとめ
S3の静的ウェブサイトを設定したS3、パブリックアクセスや、コンテンツ更新に利用するIAMを適切に管理することで、セキュアな利用を実現可能です。
CloudFront、S3の静的ウェブサイトのエンドポイント間のHTTP、暗号化されていない通信となりますが、 静的ウェブサイトのS3はユーザ情報を受取る利用はできません。
また、AWSのグローバルネットワーク内の通信が盗聴されたり、S3のエンドポイント「amazonaws.com」の名前解決結果の改竄されるなど、意図せぬオリジンが利用される可能性について起き得ないリスクとして許容できる場合、Webサーバとしての機能に優れるS3の静的ホスティングをご活用ください。