Search code examples
aws-cloudformationamazon-iamamazon-route53aws-cdkaws-codebuild

assumed-role is not authorized to perform: route53:ListHostZonesByDomain; Adding a Route53 Policy to a CodePipeline CodeBuildAction's Assumed Rule


My goal is to create a website at subdomain.mydomain.com pointing to a CloudFront CDN distributing a Lambda running Express that's rendering an S3 website. I'm using AWS CDK to do this.

I have an error that says

[Error at /plants-domain] User: arn:aws:sts::413025517373:assumed-role/plants-pipeline-BuildCDKRole0DCEDB8F-1BHVX6Z6H5X0H/AWSCodeBuild-39a582bf-8b89-447e-a6b4-b7f7f13c9db1 is not authorized to perform: route53:ListHostedZonesByName

It means:

  • [Error at /plants-domain] - error in the stack called plants-domain
  • User: arn:aws:sts::1234567890:assumed-role/plants-pipeline-BuildCDKRole0DCEDB8F-1BHVX6Z6H5X0H/AWSCodeBuild-39a582bf-8b89-447e-a6b4-b7f7f13c9db is the ARN of the Assumed Role associated with my object in the plants-pipeline executing route53.HostedZone.fromLookup() (but which object is it??)
  • is not authorized to perform: route53:ListHostedZonesByName the Assumed Role needs additional Route53 permissions

I believe this policy will permit the object in question to lookup the Hosted Zone:

const listHostZonesByNamePolicy = new IAM.PolicyStatement({
  actions: ['route53:ListHostedZonesByName'],
  resources: ['*'],
  effect: IAM.Effect.ALLOW,
});

The code using Route53.HostedZone.fromLookup() is in the first stack domain.ts. My other stack consumes the domain.ts template using CodePipelineAction.CloudFormationCreateUpdateStackAction (see below)

domain.ts


// The addition of this zone lookup broke CDK
const zone = route53.HostedZone.fromLookup(this, 'baseZone', {
  domainName: 'domain.com',
});

// Distribution I'd like to point my subdomain.domain.com to
const distribution = new CloudFront.CloudFrontWebDistribution(this, 'website-cdn', {
// more stuff goes here
});


// Create the subdomain aRecord pointing to my distribution
const aRecord = new route53.ARecord(this, 'aliasRecord', {
  zone: zone,
  recordName: 'subdomain',
  target: route53.RecordTarget.fromAlias(new targets.CloudFrontTarget(distribution)),
});

pipeline.ts


const pipeline = new CodePipeline.Pipeline(this, 'Pipeline', {
  pipelineName: props.name,
  restartExecutionOnUpdate: false,
});

// My solution to the missing AssumedRole synth error: Create a Role, add the missing Policy to it (and the Pipeline, just in case)
const buildRole = new IAM.Role(this, 'BuildRole', {
  assumedBy: new IAM.ServicePrincipal('codebuild.amazonaws.com'),
  path: '/',
});

const listHostZonesByNamePolicy = new IAM.PolicyStatement({
  actions: ['route53:ListHostedZonesByName'],
  resources: ['*'],
  effect: IAM.Effect.ALLOW,
});


buildRole.addToPrincipalPolicy(listHostZonesByNamePolicy);

pipeline.addStage({
  // This is the action that fails, when it calls `cdk synth`
  stageName: 'Build',
  actions: [
    new CodePipelineAction.CodeBuildAction({
      actionName: 'CDK',
      project: new CodeBuild.PipelineProject(this, 'BuildCDK', {
        projectName: 'CDK',
        buildSpec: CodeBuild.BuildSpec.fromSourceFilename('./aws/buildspecs/cdk.yml'),
        role: buildRole, // this didn't work
      }),
      input: outputSources,
      outputs: [outputCDK],
      runOrder: 10,
      role: buildRole, // this didn't work
    }),
    new CodePipelineAction.CodeBuildAction({
       actionName: 'Assets',
      // other stuff
    }),
    new CodePipelineAction.CodeBuildAction({
      actionName: 'Render',
      // other stuff
    }),
  ]
})

pipeline.addStage({
  stageName: 'Deploy',
  actions: [
    // This is the action calling the compiled domain stack template
    new CodePipelineAction.CloudFormationCreateUpdateStackAction({
      actionName: 'Domain',
      templatePath: outputCDK.atPath(`${props.name}-domain.template.json`),
      stackName: `${props.name}-domain`,
      adminPermissions: true,
      runOrder: 50,
      role: buildRole, // this didn't work
    }),
    // other actions
  ]
});

With the above configuration, unfortunately, I still receive the same error:

[Error at /plants-domain] User: arn:aws:sts::413025517373:assumed-role/plants-pipeline-BuildCDKRole0DCEDB8F-1BHVX6Z6H5X0H/AWSCodeBuild-957b18fb-909d-4e22-94f0-9aa6281ddb2d is not authorized to perform: route53:ListHostedZonesByName

With the Assumed Role ARN, is it possible to track down the object missing permissions? Is there another way to solve my IAM/AssumedUser role problem?


Solution

  • Here is the answer from the official doco: https://docs.aws.amazon.com/cdk/api/latest/docs/pipelines-readme.html#context-lookups

    TLDR: pipeline by default cannot do lookups -> 2 options:

    • synth on dev machine (make sure a dev has permissions)
    • add policy for lookups

    new CodePipeline(this, 'Pipeline', {
      synth: new CodeBuildStep('Synth', {
        input: // ...input...
        commands: [
          // Commands to load cdk.context.json from somewhere here
          '...',
          'npm ci',
          'npm run build',
          'npx cdk synth',
          // Commands to store cdk.context.json back here
          '...',
        ],
        rolePolicyStatements: [
          new iam.PolicyStatement({
            actions: ['sts:AssumeRole'],
            resources: ['*'],
            conditions: {
              StringEquals: {
                'iam:ResourceTag/aws-cdk:bootstrap-role': 'lookup',
              },
            },
          }),
        ],
      }),
    });