Search code examples
aws-cloudformationaws-cdkamazon-vpc

How can I map the subnet and its routing table to the firewall endpoint in the same availability zone using CDK?


I am trying to update my subnet routing table to point to the firewall endpoint in the same AZ using CDK.

I have the firewall created using CfnFirewall and I can fetch all the firewall endpoints with attrEndpointIds property. attrEndpointIds returns a list-encoded token and contains the endpoints for all the AZs.

https://docs.aws.amazon.com/cdk/api/v1/docs/@aws-cdk_aws-networkfirewall.CfnFirewall.html#attrendpointids

The unique IDs of the firewall endpoints for all of the subnets that you attached to the firewall.

The subnets are not listed in any particular order. For example: ["us-west-2c:vpce-111122223333", "us-west-2a:vpce-987654321098", "us-west-2b:vpce-012345678901"] .

I currently have the following code

        // for each firewall subnet, update its route table to firewall endpoint in the same AZ
        for (let i = 0; i < this.vpc.availabilityZones.length; i++) {
            const azWithFirewallVpcEndpointId = cdk.Fn.split(':', cdk.Fn.select(i, firewall.attrEndpointIds), 2);
            const az = azWithFirewallVpcEndpointId[0];
            const firewallVpcEndpointId = azWithFirewallVpcEndpointId[1];

            const firewallSubnetInAz = this.vpc.selectSubnets({
                subnetGroupName: FIREWALL_SUBNET_GROUP_NAME,
                availabilityZones: [az],
                onePerAz: true, // there should be 1 and only 1 subnet with the group name in each AZ
            }).subnets[0];
            (firewallSubnetInAz as ec2.Subnet).addRoute('firewall-route-' + i, {
                routerId: firewallVpcEndpointId,
                routerType: ec2.RouterType.VPC_ENDPOINT,
            });
            
            // const firewallSubnets = this.vpc.selectSubnets({
            //     subnetGroupName: FIREWALL_SUBNET_GROUP_NAME,
            // }).subnets;
            // for (let k = 0; k < firewallSubnets.length; k++) {
            //     if (cdk.Token.compareStrings(az, firewallSubnets[i].availabilityZone)) {
            //         (firewallSubnets[i] as ec2.Subnet).addRoute('firewall-route-' + i, {
            //             routerId: firewallVpcEndpointId,
            //             routerType: ec2.RouterType.VPC_ENDPOINT,
            //         });
            //     }
            // }
        }

I am struggling to update each subnet's routing table with its corresponding firewall endpoint in the same AZ. firewallSubnetInAz from vpc.selectSubnets() is undefined. I am getting the following error. Cannot read property 'addRoute' of undefined.

How can I map the subnet to the firewall endpoint in the same AZ using CDK?


Solution

  • very late to answer my question but i figured it out with some voodoo magic. Thanks to this blog post that helped me alot to sort AZ in order.

    /** this is actually some voodoo magic here...
     * we select firewall subnet from vpc, assuming (hoping...) vpc.availabilityZones are in order of AZs
     * this means we require `firewall.attrEndpointIds` to be in order.
     * however, `firewall.attrEndpointIds` is not in order for some inexplicable reasons.
     * https://github.com/aws-cloudformation/aws-cloudformation-resource-providers-networkfirewall/issues/15
     * `firewall.attrEndpointIds` is a List-encoded tokens - https://docs.aws.amazon.com/cdk/v2/guide/tokens.html#tokens_list
     * in order to sort `firewall.attrEndpointIds` in order of AZs, we need to use a combination of splits and joins.
     * AWS AZs are ordered lexicographically starting at `a`, and "assuming" vpc.availabilityZones are returned in order of AZs
     * to sort `firewall.attrEndpointIds` as a List-encoded tokens, the following steps are taken:
     * 1. join `firewall.attrEndpointIds` as a string token with `,` as delimiter.
     *   -> "us-west-2c:vpce-111122223333,us-west-2a:vpce-987654321098,us-west-2b:vpce-012345678901"
     * 2. split the resulting string token `azWithFirewallVpcEndpointIds` with `a:` for the first AZ (`b:` and `c:` subsequently).
     *   -> [ "us-west-2c:vpce-111122223333,us-west-2", "vpce-987654321098,us-west-2b:vpce-012345678901" ]
     * 3. select 2nd element as it contains the endpoint ID, along with some extra garbage and store it into var `temp`
     *   -> "vpce-987654321098,us-west-2b:vpce-012345678901"
     * 4. split `temp` by `,` as that was the joined list delimiter
     *   -> [ "vpce-987654321098", "us-west-2b:vpce-012345678901" ]
     * 5. select 1st element as it contains the endpoint ID without any extra garbage now
     *   -> "vpce-987654321098"
     * https://carriagereturn.nl/aws/cloudformation/firewall/endpoint/routing/2022/05/22/firewall-endpoints.html
     */
    getFirewallEndpointsInOrder(firewall: fw.CfnFirewall): string[] {
        const firewallVpcEndpointIds: string[] = [];
        const azWithFirewallVpcEndpointIds = cdk.Fn.join(',', firewall.attrEndpointIds);
        const aZs = ['a', 'b', 'c', 'd', 'e', 'f']; // max amount of AZs in AWS (us-east-1)
        for (let i = 0; i < this.vpc.availabilityZones.length; i++) {
            const temp = cdk.Fn.split(`${aZs[i]}:`, azWithFirewallVpcEndpointIds, 2)[1];
            const firewallVpcEndpointId = cdk.Fn.split(',', temp, 2)[0];
            firewallVpcEndpointIds.push(firewallVpcEndpointId);
        }
        return firewallVpcEndpointIds;
    }
    

    once firewallVpcEndpointIds are sorted by AZ, we can use the following code to map the subnet with its corresponding VPC endpoint in the same AZ.

    updateFirewallSubnetRouteTableWithFirewallVpcEndpoint(sortedVpcEndpointIds) {
      for (let i = 0; i < this.vpc.availabilityZones.length; i++) {
          const firewallSubnetInAz = this.vpc.selectSubnets({
              subnetGroupName: FIREWALL_SUBNET_GROUP_NAME,
              availabilityZones: [this.vpc.availabilityZones[i]], // assuming availabilityZones are in order?
              onePerAz: true, // there should be 1 and only 1 subnet with the group name in each AZ
          }).subnets[0];
    
          (firewallSubnetInAz as ec2.Subnet).addRoute('firewall-route-' + i, {
              routerId: sortedVpcEndpointIds[i],
              routerType: ec2.RouterType.VPC_ENDPOINT,
          });
      }
    }