5分でできるS3とCloudFrontを利用したセキュアな静的Webサイトの作り方
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の静的ホスティングをご活用ください。