I'm using CDK to deploy a CodePipeline that builds and deploys a React application to S3. All of that is working, but I want the deployment to update a domain name to point that S3 bucket.
I already have the Zone defined in Route53 but it is defined by a different cloud formation stack because there are a lot of details that are not relevant for this app (MX, TXT, etc). What's the right way for my Pipeline/Stacks to set those domain names?
I could think of two solutions:
example.com
delegates staging.example.com
.I didn't try the delegation zone method. I was slightly concerned about manually maintaining the generated nameservers from staging.example.com
into my CloudFormation for zone example.com
.
I did try injecting the records into the existing zone, but I run into some issues. I'm open to either solving these issues or doing this whichever way is correct.
In my stack (full pipeline at the bottom) I first define and deploy to the bucket:
const bucket = new s3.Bucket(this, "Bucket", {...})
new s3d.BucketDeployment(this, "WebsiteDeployment", {
sources: [...],
destinationBucket: bucket
})
then I tried to retrieve the zone and add the CNAME
to it:
const dnsZone = route53.HostedZone.fromLookup(this, "DNS zone", {domainName: "example.com"})
new route53.CnameRecord(this, "cname", {
zone: dnsZone,
recordName: "staging",
domainName: bucket.bucketWebsiteDomainName
})
This fails due to lack of permissions to the zone, which is reasonable:
[Container] 2022/01/30 11:35:17 Running command npx cdk synth
current credentials could not be used to assume 'arn:aws:iam::...:role/cdk-hnb659fds-lookup-role-...-us-east-1', but are for the right account. Proceeding anyway.
[Error at /Pipeline/Staging] User: arn:aws:sts::...:assumed-role/Pipeline-PipelineBuildSynthCdkBuildProje-1H5AV7C28FZ3S/AWSCodeBuild-ada5ef88-cc82-4309-9acf-11bcf0bae878 is not authorized to perform: route53:ListHostedZonesByName because no identity-based policy allows the route53:ListHostedZonesByName action
Found errors
To try to solve that, I added rolePolicyStatements
to my CodeBuildStep
rolePolicyStatements: [
new iam.PolicyStatement({
actions: ["route53:ListHostedZonesByName"],
resources: ["*"],
effect: iam.Effect.ALLOW
})
]
which might make more sense in the context of the whole file (at the bottom of this question). That had no effect. I'm not sure if the policy statement is wrong or I'm adding it to the wrong role.
After adding that rolePolicyStatements
, I run cdk deploy
which showed me this output:
> cdk deploy
✨ Synthesis time: 33.43s
This deployment will make potentially sensitive changes according to your current security approval level (--require-approval broadening).
Please confirm you intend to make the following modifications:
IAM Statement Changes
┌───┬──────────┬────────┬───────────────────────────────┬───────────────────────────────────────────────────────────┬───────────┐
│ │ Resource │ Effect │ Action │ Principal │ Condition │
├───┼──────────┼────────┼───────────────────────────────┼───────────────────────────────────────────────────────────┼───────────┤
│ + │ * │ Allow │ route53:ListHostedZonesByName │ AWS:${Pipeline/Pipeline/Build/Synth/CdkBuildProject/Role} │ │
└───┴──────────┴────────┴───────────────────────────────┴───────────────────────────────────────────────────────────┴───────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)
After deployment finishes, there's a role that I can see in the AWS console that has:
{
"Action": "route53:ListHostedZonesByName",
"Resource": "*",
"Effect": "Allow"
}
The ARN of the role is arn:aws:iam::...:role/Pipeline-PipelineBuildSynthCdkBuildProje-1H5AV7C28FZ3S
. I'm not 100% if the permissions are being granted to the right thing.
This is my whole CDK pipeline:
import * as path from "path";
import {Construct} from "constructs"
import * as pipelines from "aws-cdk-lib/pipelines"
import * as cdk from "aws-cdk-lib"
import * as s3 from "aws-cdk-lib/aws-s3"
import * as s3d from "aws-cdk-lib/aws-s3-deployment"
import * as iam from "aws-cdk-lib/aws-iam"
import * as route53 from "aws-cdk-lib/aws-route53";
export class MainStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StageProps) {
super(scope, id, props)
const bucket = new s3.Bucket(this, "Bucket", {
websiteIndexDocument: "index.html",
websiteErrorDocument: "error.html",
publicReadAccess: true,
})
const dnsZone = route53.HostedZone.fromLookup(this, "DNS zone", {domainName: "example.com"})
new route53.CnameRecord(this, "cname", {
zone: dnsZone,
recordName: "staging",
domainName: bucket.bucketWebsiteDomainName
})
new s3d.BucketDeployment(this, "WebsiteDeployment", {
sources: [s3d.Source.asset(path.join(process.cwd(), "../build"))],
destinationBucket: bucket
})
}
}
export class DeployStage extends cdk.Stage {
public readonly mainStack: MainStack
constructor(scope: Construct, id: string, props?: cdk.StageProps) {
super(scope, id, props)
this.mainStack = new MainStack(this, "MainStack", {env: props?.env})
}
}
export interface PipelineStackProps extends cdk.StackProps {
readonly githubRepo: string
readonly repoBranch: string
readonly repoConnectionArn: string
}
export class PipelineStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: PipelineStackProps) {
super(scope, id, props)
const pipeline = new pipelines.CodePipeline(this, id, {
pipelineName: id,
synth: new pipelines.CodeBuildStep("Synth", {
input: pipelines.CodePipelineSource.connection(props.githubRepo, props.repoBranch, {connectionArn: props.repoConnectionArn}),
installCommands: [
"npm install -g aws-cdk"
],
commands: [
// First build the React app.
"npm ci",
"npm run build",
// Now build the CF stack.
"cd infra",
"npm ci",
"npx cdk synth"
],
primaryOutputDirectory: "infra/cdk.out",
rolePolicyStatements: [
new iam.PolicyStatement({
actions: ["route53:ListHostedZonesByName"],
resources: ["*"],
effect: iam.Effect.ALLOW
})
]
},
),
})
const deploy = new DeployStage(this, "Staging", {env: props?.env})
const deployStage = pipeline.addStage(deploy)
}
}
You cannot depend on CDK pipeline to fix itself if the synth stage is failing, since the Pipeline CloudFormation Stack is changed in the SelfMutate stage which uses the output of the synth stage. You will need to do one of the following options to fix your pipeline:
Run cdk synth
and cdk deploy PipelineStack
locally (or anywhere outside the pipeline, where you have the required AWS IAM permissions). Edit: You will need to temporarily set selfMutatation to false
for this to work (Reference)
Temporarily remove route53.HostedZone.fromLookup
and route53.CnameRecord
from your MainStack while still keeping the rolePolicyStatements
change. Commit and push your code, let CodePipeline run once, making sure that the Pipeline self mutates and the IAM role has the required additional permissions. Add back the route53 constructs, commit, push again and check whether your code works with the new changes.