How to create your own low cost website using CDK (part 2)
Chapter V: Understanding what have been done
Let's go back to this code from part 1:
import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3Deploy from 'aws-cdk-lib/aws-s3-deployment';
class s3Stack extends Stack {
public readonly webSiteBucket: s3.Bucket;
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
this.webSiteBucket = new s3.Bucket(this, 'myWebSiteBucket', {
publicReadAccess: true,
websiteIndexDocument: 'index.html',
});
new s3Deploy.BucketDeployment(this, 'myWebsiteBucketDeployement', {
sources: [s3Deploy.Source.asset('../website-source/public')],
destinationBucket: this.webSiteBucket,
});
new CfnOutput(this, 'Bucket', { value: this.webSiteBucket.bucketName });
new CfnOutput(this, 'Bucket URL path', {
value: this.webSiteBucket.bucketWebsiteUrl,
});
}
}
export class InfraStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
new s3Stack(this, 'S3Stack');
}
}
We've created a stack that we've called s3Stack. This stack inherits from the class Stack that represents a single CloudFormation Stack. In our s3Stack constructor we create a new s3 bucket with public read access and we've specified for AWS that this bucket will be used to host a website by indicating the websiteIndexDocument parameter. To specify the sources of our S3 bucket we used BucketDeployment class which is in aws-cdk-lib/aws-s3-deployment module. CfnOutput give us the ability to write some results of our deployment execution to CloudFormation output and also in the output of cdk deploy --all command.
Chapter VI: Adding CloudFront
Let's modify our code now to include a CloudFront distribution and fix policies on our S3 bucket.
What we've done here is to change the permissions on the S3 bucket; disabling publicReadAccess and blocking all public access. We then create a CloudFront Origin Access Identity and give it permission to access objects on our S3 bucket. Finally we create a CloudFront distribution parameters to have as an origin our S3 bucket with restricted HTTP methods to GET HEAD and OPTIONS.
import { Stack, StackProps, CfnOutput } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as s3Deploy from 'aws-cdk-lib/aws-s3-deployment';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
// Interface for stack props for our Route 53 config
interface IStackProps extends StackProps {
subDomain?: string;
domainName?: string;
}
class staticWebSiteStack extends Stack {
constructor(scope: Construct, id: string, props: IStackProps) {
super(scope, id, props);
new cfConstruct(this, id, props);
}
}
class cfConstruct extends Construct {
constructor(scope: Stack, id: string, props: IStackProps) {
super(scope, id);
// Create S3 bucket with the right permissions
const webSiteBucket = new s3.Bucket(this, 'myWebSiteBucket', {
publicReadAccess: false,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
websiteIndexDocument: 'index.html',
websiteErrorDocument: '404.html',
});
new CfnOutput(this, 'Bucket', { value: webSiteBucket.bucketName });
// New Cloudfront Origin Access Identity
const cloudfrontOIA = new cloudfront.OriginAccessIdentity(
this,
'cloudfrontOia'
);
// Setting up permissions to access S3 bucket from CloudFront
webSiteBucket.addToResourcePolicy(
new iam.PolicyStatement({
actions: ['s3:GetObject'],
resources: [webSiteBucket.arnForObjects('*')],
principals: [
new iam.CanonicalUserPrincipal(
cloudfrontOIA.cloudFrontOriginAccessIdentityS3CanonicalUserId
),
],
})
);
// New CloudFront distribution to serve the S3 bucket
const distribution = new cloudfront.CloudFrontWebDistribution(
this,
'siteDist',
{
originConfigs: [
{
s3OriginSource: {
s3BucketSource: webSiteBucket,
originAccessIdentity: cloudfrontOIA,
},
behaviors: [
{
isDefaultBehavior: true,
compress: true,
allowedMethods:
cloudfront.CloudFrontAllowedMethods.GET_HEAD_OPTIONS,
},
],
},
],
}
);
new CfnOutput(this, 'DistributionDomainName', {
value: distribution.distributionDomainName,
});
new s3Deploy.BucketDeployment(this, 'myWebsiteBucketDeployement', {
sources: [s3Deploy.Source.asset('../website-source/public')],
destinationBucket: webSiteBucket,
distribution: distribution,
distributionPaths: ['/*'],
});
}
}
export class InfraStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
new staticWebSiteStack(this, 'staticWebsiteStack', {
env: {
account: this.node.tryGetContext('accountId'),
region: this.node.tryGetContext('region'),
},
});
}
}
To deploy this new version of our infrastructue you need to add context variables (accountId and region) to cdk.json file and then execute this command:
cdk deploy --all
Chapter VII: Adding our own domain name
You can register a new domain name or migrate your domain name to AWS Route 53. It is pretty straight forward. Once that is done, we can configure Route 53 now to redirect traffic to our CloudFront distribution. We can also create TLS certificate for our website.
Here is the final version of our file
To deploy the final version of our infrastructue you need to add two more context variables (subdomain and domain) to cdk.json file and then execute the same command as always.