From 1df6526214c91852a2d5db2e7623bad32f13d93a Mon Sep 17 00:00:00 2001 From: Michael Fischer Date: Thu, 10 Oct 2024 13:10:48 -0700 Subject: [PATCH] Use Origin Access Control instead of deprecated OAI Use Origin Access Control to allow CloudFront to authenticate to S3 for retrieving objects from the origin S3 bucket. This replaces the now-deprecated Origin Access Identity (OAI) mechanism. Also add support for creating an S3 bucket for CloudFront access logging. --- README.md | 24 +++++++++++- template.yaml | 101 +++++++++++++++++++++++++++++++------------------- 2 files changed, 85 insertions(+), 40 deletions(-) diff --git a/README.md b/README.md index 7bceb3c..62c6891 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,29 @@ When following this route, also provide parameter `AlternateDomainNames` upon de ## I already have an S3 bucket, I want to use that one -You can use a pre-existing S3 bucket (e.g. from another region), by providing the buckets regional endpoint domain through parameter "S3OriginDomainName" upon deploying. In this case it's a good practice to also use a CloudFront Origin Access Identity, so you don't have to make your bucket public. If you indeed have a CloudFront Origin Access Identity (make sure to grant it access in the bucket policy), specify its ID in parameter "OriginAccessIdentity". +You can use a pre-existing S3 bucket (e.g. from another region) by specifying the bucket's regional endpoint domain in the parameter `S3OriginDomainName`. An Origin Access Control will automatically be configured for the CloudFront distribution. We recommend applying an S3 bucket policy that restricts requests only from CloudFront, such as: + +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "AllowCloudFrontServicePrincipal", + "Effect": "Allow", + "Principal": { + "Service": "cloudfront.amazonaws.com" + }, + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::/*", + "Condition": { + "StringEquals": { + "AWS:SourceArn": "arn:aws:cloudfront::123456789012:distribution/" + } + } + } + ] +} +``` Alternatively, go for the more barebone deployment, so you can do more yourself––i.e. reuse your bucket. Refer to scenario: [I already have a CloudFront distribution, I just want to add auth](#i-already-have-a-cloudfront-distribution-i-just-want-to-add-auth). diff --git a/template.yaml b/template.yaml index b81c6d1..83fc9f2 100644 --- a/template.yaml +++ b/template.yaml @@ -182,8 +182,7 @@ Parameters: Default: index.html S3OriginDomainName: Description: > - The S3 origin you want to front with CloudFront. Specify the bucket's region specific hostname, i.e. .s3..amazonaws.com, - and (optionally) also specify the parameter OriginAccessIdentity. + The S3 origin you want to front with CloudFront. Specify the bucket's region specific hostname, in the form of .s3..amazonaws.com. If you don't provide an origin, and "CreateCloudFrontDistribution" is set to "true" (the default), then an S3 bucket will be created for you. Type: String Default: "" @@ -205,12 +204,18 @@ Parameters: Only of use if you are also specifying parameter "CustomOriginDomainName". Type: String Default: "" - OriginAccessIdentity: - Description: The Origin Access Identity you want to associate with your S3 origin, e.g. 'ABCDEFGHIJKLMN'. + CreateCloudFrontAccessLogsBucket: + Description: > + Set to "true" to create a CloudFront access logs bucket and deliver logs to it. Ignored if CloudFrontAccessLogsBucket is also specified. Type: String - Default: "" + Default: "false" + AllowedValues: + - "true" + - "false" CloudFrontAccessLogsBucket: - Description: The (pre-existing) Amazon S3 bucket to store CloudFront access logs in, for example, myawslogbucket.s3.amazonaws.com. Only of use if "CreateCloudFrontDistribution" is set to "true" (the default). + Description: > + The (pre-existing) Amazon S3 bucket to store CloudFront access logs in, for example, myawslogbucket.s3.amazonaws.com. Only of use if "CreateCloudFrontDistribution" is set to "true" (the default). + This bucket must have ACLs enabled. If this value is unset and CreateCloudFrontAccessLogsBucket is set to "true", a bucket will be created. Type: String Default: "" ResourceSuffix: @@ -263,12 +268,6 @@ Conditions: - !Equals [!Ref CreateCloudFrontDistribution, "true"] - !Equals [!Ref CustomOriginDomainName, ""] - !Equals [!Ref S3OriginDomainName, ""] - CreateOriginAccessIdentity: !And - - !Equals [!Ref CreateCloudFrontDistribution, "true"] - - !Equals [!Ref CustomOriginDomainName, ""] - - !Equals [!Ref S3OriginDomainName, ""] - - !Equals [!Ref OriginAccessIdentity, ""] - OriginAccessIdentityProvided: !Not [!Equals [!Ref OriginAccessIdentity, ""]] UseS3Origin: !And - !Equals [!Ref CreateCloudFrontDistribution, "true"] - !Equals [!Ref CustomOriginDomainName, ""] @@ -287,6 +286,9 @@ Conditions: - !Condition StaticSiteMode - !Condition NoExistingUserPoolProvidedOrExistingUserPoolIsInThisAccount - !Equals [!Ref UserPoolClientSecret, ""] + CreateCloudFrontAccessLogsBucket: !And + - !Equals [!Ref CreateCloudFrontAccessLogsBucket, "true"] + - !Not [!Condition CloudFrontAccessLogsBucketProvided] Globals: Function: @@ -307,6 +309,18 @@ Resources: - ServerSideEncryptionByDefault: SSEAlgorithm: AES256 + AccessLogsBucket: + Type: AWS::S3::Bucket + Condition: CreateCloudFrontAccessLogsBucket + Properties: + OwnershipControls: + Rules: + - ObjectOwnership: BucketOwnerPreferred + BucketEncryption: + ServerSideEncryptionConfiguration: + - ServerSideEncryptionByDefault: + SSEAlgorithm: AES256 + CheckAuthHandler: Type: AWS::Serverless::Function Properties: @@ -548,7 +562,10 @@ Resources: Logging: !If - CloudFrontAccessLogsBucketProvided - Bucket: !Ref CloudFrontAccessLogsBucket - - !Ref AWS::NoValue + - !If + - CreateCloudFrontAccessLogsBucket + - Bucket: !GetAtt AccessLogsBucket.DomainName + - !Ref AWS::NoValue Origins: - DomainName: !If - UseS3Origin @@ -570,15 +587,16 @@ Resources: - - HeaderName: !Ref CustomOriginHeaderName HeaderValue: !Ref CustomOriginHeaderValue - !Ref AWS::NoValue + OriginAccessControlId: !If + - UseS3Origin + - !GetAtt CloudFrontOriginAccessControl.Id + - !Ref AWS::NoValue S3OriginConfig: !If - UseS3Origin - - OriginAccessIdentity: !If - - CreateOriginAccessIdentity - - !Sub "origin-access-identity/cloudfront/${CloudFrontOriginAccessIdentity}" - - !If - - OriginAccessIdentityProvided - - !Sub "origin-access-identity/cloudfront/${OriginAccessIdentity}" - - !Ref AWS::NoValue + # If you're using origin access control (OAC) instead of origin access identity, + # specify an empty OriginAccessIdentity element. For more information, see + # Restricting access to an AWS in the Amazon CloudFront Developer Guide. + - OriginAccessIdentity: "" - !Ref AWS::NoValue - DomainName: will-never-be-reached.org Id: dummy-origin @@ -592,14 +610,17 @@ Resources: ResponsePagePath: /index.html - !Ref AWS::NoValue - CloudFrontOriginAccessIdentity: - Type: AWS::CloudFront::CloudFrontOriginAccessIdentity - Condition: CreateOriginAccessIdentity + CloudFrontOriginAccessControl: + Type: AWS::CloudFront::OriginAccessControl + Condition: UseS3Origin Properties: - CloudFrontOriginAccessIdentityConfig: - Comment: "CloudFront OAI" + OriginAccessControlConfig: + Name: !If [CreateS3Bucket, !GetAtt S3Bucket.DomainName, !Ref S3OriginDomainName] + OriginAccessControlOriginType: s3 + SigningBehavior: always + SigningProtocol: sigv4 - CloudfrontBucketPolicy: + CloudFrontBucketPolicy: Type: AWS::S3::BucketPolicy Condition: CreateS3Bucket Properties: @@ -607,24 +628,26 @@ Resources: PolicyDocument: Version: "2012-10-17" Statement: - - Action: + - Sid: AllowCloudFrontDistributionToGetObjects + Action: - "s3:GetObject" Effect: "Allow" - Resource: !Join ["/", [!GetAtt S3Bucket.Arn, "*"]] + Resource: !Sub "${S3Bucket.Arn}/*" Principal: - CanonicalUser: !If - - CreateOriginAccessIdentity - - !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId - - !Ref OriginAccessIdentity - - Action: + Service: cloudfront.amazonaws.com + Condition: + StringEquals: + "AWS:SourceArn": !Sub "arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}" + - Sid: AllowCloudFrontDistributionToListBucket + Action: - "s3:ListBucket" Effect: "Allow" Resource: !GetAtt S3Bucket.Arn Principal: - CanonicalUser: !If - - CreateOriginAccessIdentity - - !GetAtt CloudFrontOriginAccessIdentity.S3CanonicalUserId - - !Ref OriginAccessIdentity + Service: cloudfront.amazonaws.com + Condition: + StringEquals: + "AWS:SourceArn": !Sub "arn:${AWS::Partition}:cloudfront::${AWS::AccountId}:distribution/${CloudFrontDistribution}" UserPool: Type: AWS::Cognito::UserPool @@ -1059,7 +1082,7 @@ Resources: - > { "userPoolArn": "${UserPoolArn}", - "jwks": ${FetchedJwks.Jwks}, + "jwks": ${FetchedJwks.Jwks}, "clientId": "${ClientId}", "clientSecret": "${ClientSecret}", "oauthScopes": ${OAuthScopesJsonArray}, @@ -1122,7 +1145,7 @@ Resources: - > { "userPoolArn": "${UserPoolArn}", - "jwks": ${FetchedJwks.Jwks}, + "jwks": ${FetchedJwks.Jwks}, "clientId": "${ClientId}", "clientSecret": "${ClientSecret}", "oauthScopes": ${OAuthScopesJsonArray},