September 19, 2024
Setting up AWS S3 and CloudFront with Signed URLs using CDK
Private S3 bucket and CloudFront distribution with signed URLs
In this post, we'll walk through how to set up a private AWS S3 bucket and CloudFront distribution using the AWS CDK (Cloud Development Kit). We'll focus on creating a secure setup that uses presigned URLs for content access.
Why CloudFront Signed URLs in TagBug?
At TagBug, we prioritize both the performance and privacy of our users' data. When handling sensitive information such as screenshots, videos, console logs, and network requests uploaded by users, it's crucial to ensure not only that this content is securely stored and not publicly accessible, but also that it can be quickly and efficiently delivered when needed. This is where CloudFront signed URLs play a vital role in our infrastructure, offering a perfect balance of security and speed.
CloudFront's global network of edge locations allows us to serve content with low latency, regardless of where our users are located. At the same time, signed URLs provide a robust security mechanism, ensuring that only authorized users can access the sensitive data. This combination of performance and privacy is essential for maintaining the trust of our users while delivering a smooth, responsive experience.
How CloudFront Signed URLs help for privacy?
- Time-Limited Access: Signed URLs can be set to expire after a certain period, ensuring that access to the content is temporary.
- Restricted Access: Only users with the correct signed URL can access the content, preventing unauthorized viewing or sharing.
- IP Restriction: We can optionally restrict access to specific IP addresses, adding an extra layer of security.
At TagBug, we are using time-limited access for cloudfront signed urls with private S3 bucket.
Prerequisites
- AWS account
- AWS CDK installed
- Node.js and TypeScript
A private S3 bucket
// I prefer using a fixed bucket name,
// you can leave it blank to let CDK generate a random name
const bucketName = "my-bucket-name";
const s3CorsRule: s3.CorsRule = {
allowedMethods: [
s3.HttpMethods.GET,
s3.HttpMethods.HEAD,
s3.HttpMethods.POST,
s3.HttpMethods.PUT,
],
allowedOrigins: ["*"],
allowedHeaders: ["*"],
exposedHeaders: ["ETag"],
// 1 day
maxAge: 60 * 60 * 24,
};
const s3Bucket = new s3.Bucket(this, "S3Bucket", {
bucketName,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
accessControl: s3.BucketAccessControl.PRIVATE,
cors: [s3CorsRule],
// I enabled transfer acceleration to improve the upload speed,
// you can disable it if you don't want to use it.
transferAcceleration: true,
});
Setup CloudFront
First, please follow Creating CloudFront Key Pairs to generate a public key and private key.
Then put your public key to CF_PUBLIC_KEY
environment variable.
Setup your cloudfront distribution with the following code:
if (!process.env.CF_PUBLIC_KEY) {
throw new Error("CF_PUBLIC_KEY is not set");
}
const pubKey = new cloudfront.PublicKey(this, "pubkey_1", {
encodedKey: process.env.CF_PUBLIC_KEY,
});
const keyGroup = new cloudfront.KeyGroup(this, "pubkey_group_1", {
items: [pubKey],
});
const distribution = new cloudfront.Distribution(this, "BackendCF", {
defaultBehavior: {
origin: new origins.S3Origin(s3Bucket),
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
trustedKeyGroups: [keyGroup],
cachePolicy: cloudfront.CachePolicy.CACHING_OPTIMIZED,
originRequestPolicy: cloudfront.OriginRequestPolicy.CORS_S3_ORIGIN,
responseHeadersPolicy:
cloudfront.ResponseHeadersPolicy.CORS_ALLOW_ALL_ORIGINS,
},
});
The code above use the recommended way by AWS to setup CORS settings.
Create signed URL in your application's code
After setup the infrastructure, you can create a signed URL using the following code:
export function getCDNSignedUrl(key: string, expiresInSeconds: number): string {
const url = `${env.CF_BASE_URL}/${key}`;
const dateLessThan = new Date(
Date.now() + expiresInSeconds * 1000,
).toISOString();
const signedUrl = getCFSignedUrl({
url,
keyPairId: env.CF_KEY_ID ?? "",
dateLessThan,
privateKey: env.CF_PRIVATE_KEY ?? "",
});
return signedUrl;
}
In the above code:
env.CF_BASE_URL
is the base URL of the CloudFront distribution.env.CF_KEY_ID
is the ID of the CloudFront key pair. You can find it in the CloudFront console.env.CF_PRIVATE_KEY
is the private key of the CloudFront key pair you generated in the previous step.