Skip to content

AWS | S3 & Cloudfront

IAM Policies

# Allow Cloudfront using s3 block public access
{
        "Version": "2008-10-17",
        "Id": "PolicyForCloudFrontPrivateContent",
        "Statement": [
            {
                "Sid": "AllowCloudFrontServicePrincipal",
                "Effect": "Allow",
                "Principal": {
                    "Service": "cloudfront.amazonaws.com"
                },
                "Action": "s3:GetObject",
                "Resource": "arn:aws:s3:::<>.com/*",
                "Condition": {
                    "StringEquals": {
                      "AWS:SourceArn": "arn:aws:cloudfront::<>:distribution/<>"
                    }
                }
            }
        ]
      }

# Use to access with block public access turned iff
      {
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AllowS3SyncCommand",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:GetObject",
                "s3:Listbucket",
                "s3:PutObject"
            ],
            "Resource": [
                "arn:aws:s3:::<>.com",
                "arn:aws:s3:::<>.com/*"
            ]
        }
    ]
}

Cloudfront setup

Need Alternative domains because of route53
Set Default Root object
Use Origin access control settings (recommended)
Update policy in s3

Create iam role for s3 bucket for pipeline

s3-bucket-cloudfront user
Permissions:
allow-invalidation - so clodufront can refresh
Cloudfront refresh cache every 24 hours
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "cloudfront:CreateInvalidation",
            "Resource": "*"
        }
    ]
}
AmazonS3FullAccess 
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:*",
                "s3-object-lambda:*"
            ],
            "Resource": "*"
        }
    ]
}

Using cloudfront with s3 website endpoint instead requires special policy

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadAccess",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::<>.com/*",
            "Condition": {
                "StringEquals": {
                    "aws:UserAgent": "Amazon CloudFront"
                }
            }
        }
    ]
}

CORS policy for s3

[
    {
        "AllowedHeaders": [
            "*"
        ],
        "AllowedMethods": [
            "GET",
            "PUT"
        ],
        "AllowedOrigins": [
            "http://localhost:3000"
        ],
        "ExposeHeaders": [],
        "MaxAgeSeconds": 3000
    }
]

CORS policy for cloudfront

To fetch and download from s3

Response headers policy - optional
Make a custom policy that allows all origins

AWS SDK

Setup s3

import AWS from "aws-sdk";
yarn add aws-sdk
npm i aws-sdk

const s3 = new AWS.S3({
  credentials: {
    accessKeyId: process.env.NEXT_PUBLIC_REACT_APP_AWS_ACCESS_KEY_ID || "",
    secretAccessKey: process.env.NEXT_PUBLIC_REACT_APP_AWS_SECRET_ACCESS_KEY || ""
  },
  region: process.env.NEXT_PUBLIC_REACT_APP_AWS_REGION || ""
});

Upload file to s3

  const sourceUrl = 'https://images.unsplash.com/...';

  fetch(sourceUrl)
    .then(response => response.blob())
    .then(blob => {
      const splitUrl = sourceUrl.split('/').pop();
      if (!splitUrl) throw new Error("Filename is undefined");
      const s3Params = {
        Bucket: <bucket_name>,
        Key: 'images/' + splitUrl,
        Body: blob,
        ContentType: blob.type
      };

      s3.upload(s3Params, (err: any, data: { Location: any; }) => {
        if (err) {
          console.log('Error uploading file: ', err);
        } else {
          console.log('File uploaded successfully. Location:', data.Location);
        }
      });
    })

Download S3 file using Javascript fetch

const fetchDownloadMaterial = (filename: string) => {
  const extractFilename = filename.split('/').pop();
  if (!extractFilename) throw new Error("Filename is undefined");
  return fetch(`${filename}`, {method: "GET"})
    .then(res => res.blob())
    .then(blob => {
      const url = window.URL.createObjectURL(blob)
      const a = document.createElement("a")
      a.href = url
      a.download = extractFilename
      document.body.appendChild(a)
      a.click()
      a.remove()
    })
    .catch(err => {
      console.error("err: ", err);
    })
};