Allowing an AWS account to delegate DNS subdomains to another account in two simple CDK stacks
Published: 2023-07-12 10:35:59

## The Problem
I had an issue this week. I was initializing AWS accounts for our developers and they required a subdomain per account. Think user.blah.com. 

I wanted the developer account to be able to manage all records inside this subdomain, so they could create api.user.blah.com, staging.user.blah.com and so on. And if you manage your own subdomain, you can also generate SSL certificates for them as well. 

![Route 53 Logo](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/etp2x1tzd7ojk55hs763.png)


To achieve this with AWS, you simply need to create the hosted zone for the subdomain in Route53, find out which AWS nameservers it was allocated, and then add a 'NS' record for your subdomain using those nameservers in the DNS zone for the parent domain, ie blah.com. 

Now, it sounds simple, but I don't want to do this every time I onboard a new developer. I want it to happen automatically when I deploy the developers initialization stack. Fortunately, it's possible, and not very difficult to implement! 

## The Solution

The solution here requires you to be using [AWS Organizations](https://aws.amazon.com/organizations/) to create AWS accounts for your developers. 

### The Parent Account

Firstly, we create a new CDK project and start editing our lib/stack.ts file. 

I use a parameter I can pass in to my CDK stack to define the domain that will be created in Route53, eg. "blah.com". Lets capture that with CDK first:

```javascript
    // Get the domain from the user. 
    const domain = new cdk.CfnParameter(this, "domain", {
      type: "String",
      description: "The domain to be created. "
    }).value.toString();
```

Next we're going to create the Route53 Hosted Zone:
```javascript
    // Create the Route53 zone
    const hostedZone = new r53.PublicHostedZone(this, 'zone', {
       zoneName: domain,
    });
```

Now, this is where the magic happens. We're going to create a role that allows every other account which is part of your AWS organization to be able to add a NS record to the hosted zone we just created. Then, we grant the delegation access to the role. Be sure to change the o-xxxxxxx's to your Organization ID, of course.

```javascript
    // Create the role
    const role = new Role(this, 'RootZoneOrganizationRole', {
      assumedBy: new OrganizationPrincipal('o-xxxxxxxxxx'),
      roleName: 'HostedZoneDelegationRole',
    });

    // Grant the delegation
    hostedZone.grantDelegation(role);
```

CDK is doing a lot of heavy lifting under the hood here, it creates a role with correct permissions and a trust relationship that allows all the other accounts to do what we need it to do. Anyway, that's all we need for our parent account CDK stack! Save it and deploy it, probably something like this:

```bash
$ cdk deploy --parameters domain=user.blah.com --profile parent
```

### The Child Account

Now we're going to create a stack for the child account, do your cdk init and so on to create another new stack project and get ready to add some code. 

Lets capture the child account domain, in this case it we'll expect user.blah.com

```javascript
    // Get the subdomain from the user
    const domain = new cdk.CfnParameter(this, "domain", {
      type: "String",
      description: "The subdomain to be created. "
    }).value.toString();
```

Create the hosted zone in Route53

```javascript
    // Create the Route53 zone
    const hostedZone = new r53.PublicHostedZone(this, 'zone', {
       zoneName: domain,
    });
```

Generate the delegation role ARN, specifying the parent account which hosts the blah.com domain

```javascript
    const delegationRoleArn = Stack.of(this).formatArn({
      account: '1234567890',
      region: '',
      resource: 'role',
      resourceName: 'HostedZoneDelegationRole',
      service: 'iam',
    });
```

Create the delegation role from this ARN

```javascript
    const delegationRole = iam.Role.fromRoleArn(this, 'DelegationRole', delegationRoleArn);
```

And now we can add the zone as a delegation record to the parent account Route53:

```javascript
    new r53.CrossAccountZoneDelegationRecord(this, 'DelegationRecord', {
      delegationRole,
      delegatedZone: hostedZone,
      parentHostedZoneName: 'blah.com',
    });
```

and that's it! that's all you need! any records added to the hostedZone from this point on will be completely valid, because the parent account now recognizes the child account's route53 configuration as the authority for that subdomain. 

Huge shoutout to AWS Community Builder [Matt Morgan](https://awscommunity.social/@serverlesscultist) who has created an awesome [AWS Organizations for devs](https://github.com/aws-community-projects/aws-organization-for-devs) GitHub repository where you can learn about deploying full AWS Organizations!