CloudFront & S3 Versioning
Tags: aws, s3
Published: 2022-08-29 15:25:27

I was looking into options how to serve specific versions of objects on a versioned S3 bucket with CloudFront. After doing some diggin it turned out to be pretty easy. As described in the [documentation of S3 on retrieving versions](Retrieving object versions from a versioning-enabled bucket), a specific version can be retrieved from S3 by adding _?version={version}_ to the url. When configuring CloudFront you must also allow the _versionId_ as query string parameter in the cache settings so it will be passed to the S3 Origin. After this is done and you try to retrieve a specific version you will get a permission denied error. To solve this, you must give CloudFront _s3:GetObjectVersion_ permission to your S3 bucket in the policy.

Complete example in CloudFormation:

```
Resources:
  S3:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub '${AWS::StackName}-assets'
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      VersioningConfiguration:
        Status: Enabled
  CloudFrontOriginAccessIdentity:
    Type: AWS::CloudFront::CloudFrontOriginAccessIdentity
    Properties:
      CloudFrontOriginAccessIdentityConfig:
        Comment: !Sub '${AWS::StackName}'
  S3Policy:
    Type: AWS::S3::BucketPolicy
    Properties:
      Bucket: !Ref S3
      PolicyDocument:
        Statement:
          - Sid: CloudFront
            Action:
              - 's3:GetObject'
              - 's3:GetObjectVersion'
            Effect: Allow
            Resource: { 'Fn::Join': [ '/', [ !GetAtt S3.Arn , '*' ] ] }
            Principal:
              AWS: { 'Fn::Join': [ ' ', [ 'arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity', { Ref: CloudFrontOriginAccessIdentity } ] ] }
  CloudFront:
    Type: AWS::CloudFront::Distribution
    Properties:
      DistributionConfig:
        IPV6Enabled: true
        Enabled: true
        Comment: !Sub '${AWS::StackName}'
        Origins:
          - Id: S3
            DomainName: !GetAtt S3.DomainName
            S3OriginConfig:
              OriginAccessIdentity: { 'Fn::Join': [ '/', [ 'origin-access-identity', 'cloudfront', { Ref: CloudFrontOriginAccessIdentity } ] ] }
        DefaultCacheBehavior:
          AllowedMethods: [ HEAD, OPTIONS, GET ]
          CachedMethods: [ HEAD, OPTIONS, GET ]
          ForwardedValues:
            QueryString: true
            QueryStringCacheKeys: [ versionId ]
          TargetOriginId: S3
          ViewerProtocolPolicy: https-only
          Compress: true

```

You can now get the latest version of your asset by accessing:

```
https://xyz.cloudfront.net/some_image.jpg
```

and you can request a specific version like this:

```
https://xyz.cloudfront.net/some_image.jpg?versionId=abcdef
```

You now have the benefits of servering versioned content over CloudFront, also giving you the possibility to [restrict access to your content with signed cookies or signed urls](https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html).