This week, I changed my blog’s theme from butterfly to cactus. I also managed to convert all AWS resources (except Route53) into a single CloudFormation template (gist). For this post, I want to write down thoughts and problems I encountered.
Circular Dependency
The Problem
The biggest problem I have encounter is the circular dependency problem between KMS and other services.
Because CloudFront distribution cannot access AWS Managed S3 key (you cannot modify key policy of AWS Managed key), you need your own KMS key, named ResourceKey here, to encrypt the S3 Bucket.
CodeBuild need a IAM service role in order to put the output artifact to the S3 bucket. And for the sake of least privilege, ResourceKey‘s key policy will reference service role in order to restrict access to this CodeBuild.
The AllowCodeBuildPrinciple policy in ResourceKey has the principle refering CodeBuildRole. However, because CodeBuildRole needs to define a S3::PutObject policy. The S3 Bucket, BlogArtifactBucket, must be created in order for the CodeBuildRole’s policy to reference the bucket Arn. But we need ResourceKey in order to create the bucket. Do you see the problem here?
ResourceKey requires CodeBuildRole
CodeBuildRole requires BlogArtifactBucket
BlogArtifactBucket requries ResourceKey
There is a circular dependency between three resources.
The Discovery
I did not realize the solution until I started to work on the BlogArtifactBucket‘s bucket policy. You see, in CloudFormation, the AWS::S3::Bucket resource does not a “Policies” or “PolicyDocument” property to let you embed the policy directly in the Bucket definition. Instead, you need a seperate resource called AWS::S3::BucketPolicy that references to the bucket.
The outcome this, in my observation of CloudFormation stack creation, is that BlogArtifactBucket is created at first; then the CloudFront distribution; at the end BlogArtifactBucketPolicy is attched to BlogArtifactBucket.
The Solution
I want to use the same method to solve the circular problem above. After a quick digging, I found out that CloudFormation has resources AWS::IAM::Role and AWS::IAM::RolePolicy. The AWS::IAM::RolePolicy has a property called “RoleName” that can attach the policy to specific role. So wala, the solution is solved. Now, the stack creation order becomes
This is something when I am almost done with the CloudFormation template. I realize that you can use aws cli to describe a existing resources. Since I did not start from a blank, I can use for instance aws cloudfront get-distribution --id <DistributionId> to get the current setting of my CloudFront distribution. That’s a neat trick to save time from deciding which configuration value to use.
Another Circular Dependency
The Problem
This section was written the other day. In my original architecture, my S3 Bucket, BlogArtifactBucket, will invoke a Lambda function, CloudFrontInvalidationLambda, every time CodeBuild put the artifact into the bucket. As name suggests, the function will create a request to invalidate current BlogDistribution in order for the index page to be updated immediately.
Moving forward to CloudFormation, this creates yet another circular dependency problem:
BlogArtifactBucket requires CloudFrontInvalidationLambda (reference for EventConfiguration )
CloudFrontInvalidationLambda requires BlogDistribution (reference for using DistributionId as request argument)
BlogDistribution requires BlogArtifactBucket (reference for origin)
I cannot find a proper way to implement this nicely because unlick Role and RolePolicy discussed above, AWS::S3::Bucket’s NotificationConfiguration property cannot be configured in a seperate resource. Nor that AWS::Lambda::Function have a seperate resource to achieve our goal.
The Discovery
After some heavy digging, I found out that AWS::CloudFormation::CustomResource can be used to invoke Lambda functions during stack creation. Bsed on the documentation, I can invoke a Lambda function to add a lambda NotificationConfiguration to S3 bucket for any new object created.
The solution
Simply define another lambda resource that was granted a RolePolicy to s3:PutBucketNotification. Then define a AWS::CloudFormation::CustomResource with ServiceToken refering to the new lambda function and supply the invalidation lambda function’s arn and the target s3 bucket as argument.
Comparing to Terraform, I definitely think that CloudFormation’s syntax and language features is little outdated. However, its integration with aws concole/cli is very helpful development friendly. So it is hard to judge what’s best. My next goal is probably to experiment with the CloudFormation CDK and see if developing in Python/Javascript is better than YAML.