Tutorial: Mastering Secure File Access with Amazon S3 and CloudFront Signed URLs

Nic Lasdoce
21 Apr 20235 minutes read

Learn how to securely upload files using Amazon S3 and enhance access control with CloudFront signed URLs. This comprehensive tutorial guides you through the process, providing step-by-step instructions and insights into the benefits of using signed URLs for restricted file access. Explore the power of AWS S3 for scalable object storage and CloudFront as a content delivery network (CDN) to ensure low-latency, high-speed data delivery. With this tutorial, you'll gain the knowledge and skills needed to confidently manage secure file uploads, protect sensitive content, and tailor access permissions with signed URLs.

In this article, we will discuss the process of uploading files to Amazon S3 and generating a signed URL in CloudFront to access restricted files for a limited time period.

What is AWS S3?

AWS S3, or Amazon Simple Storage Service, is a scalable object storage service offered by Amazon Web Services. It enables users to store and retrieve data from anywhere on the web.

What is CloudFront?

CloudFront is an Amazon Web Services content delivery network (CDN) service. It securely delivers data, videos, applications, and APIs with low latency and high transfer speeds.

Use cases for a signed URL

A signed URL can be beneficial in various scenarios, including:

  • Private content sharing: A signed URL allows sharing private content, such as confidential documents or media files, with specific individuals or a limited group of authorized users.

  • Time-limited access: Signed URLs grant temporary access to content for a specific period, ensuring that the content remains secure and inaccessible after the specified time expires.

  • Secure downloads: By using a signed URL, you can ensure that files are downloaded securely and only by authorized users, making it ideal for distributing sensitive software packages, firmware updates, or proprietary data.

  • Content personalization: Signed URLs enable the provision of personalized content to users while maintaining security. Unique signed URLs can grant access to personalized reports, documents, or media tailored to individual users.

  • Pay-per-access content: Signed URLs can control access to paid content, ensuring that users have paid before accessing the content. This helps prevent unauthorized distribution of premium content.

  • Time-limited previews: Signed URLs can provide temporary previews for unreleased content or trial versions of software, allowing limited access for a specific time period.

  • API access control: Signed URLs can authenticate and authorize requests for accessing sensitive or restricted data through APIs. They help ensure that only authorized clients can access API endpoints.

Into the Process

AWS Setup

1. Create S3 Bucket

To set up the AWS environment, follow these steps:

  • Create an S3 bucket with the name "new-sample-test-bucket-tutorial". Keep all other settings as default.

2. Create CloudFront Public Key

Proceed with the following instructions to create a public key in CloudFront:

  • Navigate to CloudFront and go to "Public Keys."
  • Click on "Create Public Key."

3. Generate RSA Key Pair

To generate an RSA key pair and save it to a file, perform the following steps:

  1. Open your terminal.
  2. Run the command below to generate an RSA key pair with a length of 2048 bits and save it to a file named "sample_private_key.pem":
openssl genrsa -out sample_private_key.pem 2048
  1. Extract the public key from the generated file using the command:
openssl rsa -pubout -in sample_private_key.pem -out sample_public_key.pem
  1. Copy the public key by outputting it to the terminal and selecting and copying the highlighted text:
cat sample_public_key.pem
  1. Paste the copied key back to CloudFront keys.

4. Create CloudFront Key Group

To create a key group in CloudFront, follow these steps:

  • Go to CloudFront and navigate to "Key Pairs."
  • Click on "Create Key Group."
  • Select the previously created key pair (named "sample") and create the key group.

5. Create CloudFront Distribution

To create a CloudFront distribution with specific settings, follow these instructions:

  • Go to CloudFront and navigate to "Distributions."
  • Click on "Create Distribution."
  • Adjust the following settings (leave the rest as default):
    • Origin domain: Choose the S3 bucket previously created (new-sample-test-bucket-tutorial.s3.(region).awazonaws.com).
    • Origin access: Origin access control settings
      • Click on "Create control setting" and use the settings shown in the image below:

  • Restrict viewer access: Yes

    • Trusted authorization type: Trusted key groups
    • Select the key group created earlier.
  • Web Application Firewall (WAF): Do not enable security protections.

  • Create the distribution.

6. Configure Bucket Policy

To configure origin access bucket policy for the newly created distribution, perform the following steps:

  • Go to CloudFront and navigate to "Distribution."
  • Select the previously created origin (new-sample-test-bucket-tutorial.s3.us-east-1.amazonaws.com) and click "Edit."
  • Under Origin Access, click on "Copy Policy" and then click "Go to S3 bucket permissions."
  • Go to Bucket Policy and click "Edit."
  • Copy and save the policy. The policy should resemble the one shown in the image below:

7. Upload the PDF

Download the dummy.pdf file, and manually upload it to the S3 bucket. In the future, we will create scripts using Node.js or Python SDK to automate the upload process.

Code Sample

You can find a sample project here: https://github.com/Triglon/aws.git

const cloudfrontSigner = require('aws-cloudfront-sign');
const MILLISECONDS_PER_SECOND = 1000;
const SECONDS_PER_HOUR = 3600;
const DEFAULT_EXPIRE_PERIOD_HOURS = 24 * 30;
const EXPIRE_PERIOD_MILLISECONDS = DEFAULT_EXPIRE_PERIOD_HOURS * SECONDS_PER_HOUR * MILLISECONDS_PER_SECOND;
const cloudfrontURL = 'https://d2isvd9dkfhjxy.cloudfront.net';
const cloudfrontKeyId = 'K6Z02FA9L040R';
const cloudfrontPrivateKey = `
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEAtAys4lSv8GRI8J7QXb0Ir63oPZ3Qik0gSzOtyDW9NYyZ8+6Y
A0l6MmwqTq9GMWWbnTR2EmFP1p5d/DHZxDsX2JneHph4CYve4a8cYnu7hnMf5n6Q
mcYYqu15zvIpELAOOaGYbDIG/EfCHKDnXt06AgGqiLnzo7Fu9Sd2nMs1GRRJ3VVP
LAnCXY3969HjrBImFxpKLPYokF8Y25uAaOKIhGVeU9YuWns+STe42+Xad6Jf+Yah
AHj0R2H9RVcl48foFaILQbgPc6JpZBH86u4Ry5wGOSzQbn1GXzOlwjGlws1E364R
qUz95HelHxTkeAoiw2NDe0ImA2RQaXLdMKe2uwIDAQABAoIBAQCcfzf2Mi4LAN/1
ZdUu5RQbv0lR5U5SJ9+d/flbQHqJhQB76jLvCHrSQPo1Elwsq2irJ+JI75R5s4V1
o87opYSAnJ1YcqZDhfPgrlg5sdq5bm+X5QLC5lCioW9y1UGkY6K5rR/TS1iPB8BN
Kf5xklDNVa1o0lhXO+554CdU+bvZYzaozyehffKSD9IpMPpLX6fxTOEDMnPDaNur
nrAatFllkha6Hp8OPRBpMkocYWMtQEfVZBipz1Ahej2I7iECDzbk8mx3PbX1MXhe
gTAe5lGZkEUJkzWYCA05+8Q0giaQJHtlbmJ4SCTpsaTs9XyIYOMr2P4f7DgR+nnO
1Q2lXH8hAoGBAOj6JNJ9SE/N4e068c+UeNtDx/tpqCyBu9GCmtUW3QlJnZA1XRaS
FlvJ7x6sdabOC295ImMj5Y0GTnZfBIR1nGTTrZKvzvK0qtqkhG2+AmEBQr+2tBUq
8xBy1cZL6ayH99lBxja9RRqlHEdYNHIW4C9wumxSzJXroVk9zspMCAYNAoGBAMXX
kY6Q7Qonr8kIcQqZYPCUkAu2CmFqvtKNwJphaDjhPjama+SeVD6rWWnH/LTPYM7c
RMGHeewYYqUagmcdT81x6ONOOF0/7mssX9DcQA0LjueomSDjFv6+uHIKDa5CLBqg
CW9uDzLbKebewaD3NJOT93EaSIziF0sKtqi7fAXnAoGAFXtzR8FrmIg4a+KCh4x+
NGGkoAcXDbuMsP3k/v8TtJaII9L32WvxCdet59spIg9fuJCn3hJiSUWqmHmcdgZO
PHHUUHFLmM+V7YE8AM6Dc6RlHj5fjpAeR4b/NUCstE75SJwrBcMgCxvsZpu4gkif
tWAkoHZmDPDkONFdLwQhvUUCgYA9Y9bW1kG3lPkG+IebMlzSSkcoWyR9dhIgY7wQ
K4mbnMkhTCLOnhKmH6VvHY9cy7zOc6siIlfC2w5BDSjJtl688UvCvNLgnKXuu6Y/
uRhm8980Iyzg95Z7FdNGD7iPChmFaYOEADLXJQqriROsTwkRgiiWAAHjNYTk1D45
vXOOoQKBgB2/aNa1hC+8WcO/vdATez9i9HTTnx72Uk5Ni3ByjVQoACtlNFXMUprY
6rMk4utHfFem7JzujmxTPGX8d4k3pWLSfe2O7ALraJASnragipqhXcwc9Ad/yPMO
u/saNNrTt4/wc//+PWQJxmN7U7hGOLv6aDtXnxq8i1/XJaeSskuw
-----END RSA PRIVATE KEY-----
`;
class CloudFrontHelper {
// when inside environment Variables, the RSA key loses the
// required formatting, so we reformat it using this function
static processKeyString = (keyString) => {
const beginString = '-----BEGIN RSA PRIVATE KEY-----';
const endString = '-----END RSA PRIVATE KEY-----';
let processedKey = keyString.replace(beginString, '').replace(/"/g, '\n');
processedKey = processedKey.replace(endString, '\n');
processedKey = processedKey.replace(/\s+/g, '\n');
processedKey = `${beginString}${processedKey}${endString}`;
return processedKey;
};
static getSignedUrlFromPath = (pathName, expiryTime = 0) => {
if (pathName) {
const url = new URL(pathName, cloudfrontURL);
return this.urlSigner(url.href, expiryTime);
} else {
return null;
}
};
static urlSigner = (url, expiryTime = 0) => {
if (expiryTime === 0) {
// unix timestamp in milliseconds
expiryTime = Math.floor(new Date().getTime()) + EXPIRE_PERIOD_MILLISECONDS;
}
const signingParams = {
keypairId: cloudfrontKeyId,
privateKeyString: this.processKeyString(cloudfrontPrivateKey),
expireTime: expiryTime,
};
return cloudfrontSigner.getSignedUrl(url, signingParams);
};
}
const url = CloudFrontHelper.getSignedUrlFromPath('dummy.pdf');
console.log(url);

Bonus

If you are a founder needing help in your Software Architecture or Cloud Infrastructure, we do free assessment and we will tell you if we can do it or not! Feel free to contact us at any of the following:
Social
Contact

Email: nic@triglon.tech

Drop a Message

Tags:
Software Development
TechStack
AWS
NodeJS

Nic Lasdoce

Software Architect

Unmasking Challenges, Architecting Solutions, Deploying Results

Member since Mar 15, 2021

Tech Hub

Unleash Your Tech Potential: Explore Our Cutting-Edge Guides!

Stay ahead of the curve with our cutting-edge tech guides, providing expert insights and knowledge to empower your tech journey.

View All
Struggling with Database Performance? Discover These 5 Aurora Patterns for Better Operations
30 Jun 20242 minutes read
Monolith: From Zero to Millions
14 May 20244 minutes read
View All

Get The Right Job For You

Subscribe to get updated on latest and relevant career opportunities