constructor()

in src/deployments/cdk/src/common/vpc.ts [177:741]


  constructor(scope: cdk.Construct, name: string, vpcProps: VpcProps) {
    super(scope, name);

    const props = { vpcProps };

    const {
      accountKey,
      accounts,
      vpcConfig,
      organizationalUnitName,
      limiter,
      vpcConfigs,
      accountStacks,
      acceleratorName,
      installerVersion,
      vpcOutput,
      vpcPools,
      subnetPools,
      existingAttachments,
    } = props.vpcProps;
    const vpcName = props.vpcProps.vpcConfig.name;
    const currentVpcPools: AssignedVpcCidrPool[] = [];
    const currentSubnetPools: AssignedSubnetCidrPool[] = [];
    if (['lookup', 'dynamic'].includes(vpcConfig['cidr-src'])) {
      currentVpcPools.push(
        ...getVpcCidrPools(vpcPools, accountKey, vpcConfig.region, vpcConfig.name, organizationalUnitName),
      );
      currentSubnetPools.push(
        ...getSubnetCidrPools({
          subnetPools,
          accountKey,
          region: vpcConfig.region,
          vpcName: vpcConfig.name,
          organizationalUnitName,
        }),
      );
    }
    this.ddbKmsKey = props.vpcProps.ddbKmsKey || '';
    this.name = props.vpcProps.vpcConfig.name;
    const vpcCidrs = props.vpcProps.vpcConfig.cidr;
    this.region = vpcConfig.region;
    // Retrive CIDR
    if (props.vpcProps.vpcConfig['cidr-src'] === 'dynamic') {
      this.cidrBlock = currentVpcPools.find(vpcPool => vpcPool.pool === vpcCidrs[0].pool)?.cidr!;
      if (!this.cidrBlock) {
        throw new Error(`No CIDR found for VPC : ${vpcConfig.name} in DynamoDB "cidr-vpc-assign"`);
      }
      if (vpcCidrs.length > 1) {
        this.cidr2Block.push(...currentVpcPools.filter(vpcPool => vpcPool.pool !== vpcCidrs[0].pool).map(c => c.cidr));
      }
    } else if (props.vpcProps.vpcConfig['cidr-src'] === 'lookup') {
      if (currentVpcPools.length === 0) {
        throw new Error(`No CIDR found for VPC : ${vpcConfig.name} in DDB`);
      }
      currentVpcPools.sort((a, b) => (a['vpc-assigned-id']! > b['vpc-assigned-id']! ? 1 : -1));
      this.cidrBlock = currentVpcPools[0].cidr;
      if (currentVpcPools.length > 1) {
        this.cidr2Block.push(...currentVpcPools.slice(1, currentVpcPools.length).map(c => c.cidr));
      }
    } else {
      if (!vpcCidrs) {
        throw new Error(`No CIDR found for VPC : ${vpcConfig.name} in Configuration`);
      }

      this.cidrBlock = vpcCidrs[0].value?.toCidrString()!;
      if (vpcCidrs.length > 1) {
        this.cidr2Block.push(...vpcCidrs.slice(1, vpcCidrs.length).map(c => c.value?.toCidrString()!));
      }
    }

    // Create Custom VPC using CFN construct as tags override option not available in default construct
    const vpcObj = new ec2.CfnVPC(this, vpcName, {
      cidrBlock: this.cidrBlock,
      enableDnsHostnames: true,
      enableDnsSupport: true,
      instanceTenancy: props.vpcProps.vpcConfig['dedicated-tenancy']
        ? ec2.DefaultInstanceTenancy.DEDICATED
        : ec2.DefaultInstanceTenancy.DEFAULT,
    });
    this.vpcId = vpcObj.ref;

    const extendVpc: ec2.CfnVPCCidrBlock[] = [];
    this.cidr2Block.forEach((additionalCidr, index) => {
      let id = `ExtendVPC-${index}`;
      if (index === 0) {
        id = 'ExtendVPC';
      }
      const extendVpcCidr = new ec2.CfnVPCCidrBlock(this, id, {
        cidrBlock: additionalCidr,
        vpcId: vpcObj.ref,
      });
      extendVpc.push(extendVpcCidr);
      this.additionalCidrBlocks.push(additionalCidr);
    });

    let igw;
    let igwAttach;
    if (props.vpcProps.vpcConfig.igw) {
      // Create IGW
      igw = new ec2.CfnInternetGateway(this, `${vpcName}_igw`);
      // Attach IGW to VPC
      igwAttach = new ec2.CfnVPCGatewayAttachment(this, `${props.vpcProps.vpcConfig.name}_attach_igw`, {
        vpcId: vpcObj.ref,
        internetGatewayId: igw.ref,
      });
    }

    let vgw;
    let vgwAttach;

    const vgwConfig = props.vpcProps.vpcConfig.vgw;
    if (vgwConfig) {
      const amazonSideAsn = config.VirtualPrivateGatewayConfig.is(vgwConfig) ? vgwConfig.asn : undefined;

      // Create VGW
      vgw = new ec2.CfnVPNGateway(this, `${props.vpcProps.vpcConfig.name}_vpg`, {
        type: 'ipsec.1',
        amazonSideAsn,
      });

      // Attach VGW to VPC
      vgwAttach = new ec2.CfnVPCGatewayAttachment(this, `${props.vpcProps.vpcConfig.name}_attach_vgw`, {
        vpcId: vpcObj.ref,
        vpnGatewayId: vgw.ref,
      });
    }

    const s3Routes: string[] = [];
    const dynamoRoutes: string[] = [];
    const routeTablesProps = props.vpcProps.vpcConfig['route-tables'];
    const tgwAttach = props.vpcProps.vpcConfig['tgw-attach'];
    const natRouteTables: string[] = [];
    if (routeTablesProps) {
      // Create Route Tables
      for (const routeTableProp of routeTablesProps) {
        if (routeTableProp.name === 'default') {
          continue;
        }

        const routeTableName = routeTableProp.name;
        const routeTable = new ec2.CfnRouteTable(this, routeTableName, {
          vpcId: vpcObj.ref,
        });

        this.routeTableNameToIdMap[routeTableName] = routeTable.ref;
      }
    }

    const subnetsConfig = props.vpcProps.vpcConfig.subnets || [];
    for (const subnetConfig of subnetsConfig) {
      const subnetName = subnetConfig.name;
      for (const subnetDefinition of subnetConfig.definitions.values()) {
        if (subnetDefinition.disabled) {
          continue;
        }
        let subnetCidr: string = '';
        if (['lookup', 'dynamic'].includes(vpcConfig['cidr-src'])) {
          const subnetCidrPool = currentSubnetPools.find(
            s =>
              s.az === subnetDefinition.az &&
              s['subnet-name'] === subnetConfig.name &&
              s['vpc-name'] === vpcConfig.name &&
              s.region === vpcConfig.region,
          );
          if (subnetCidrPool) {
            subnetCidr = subnetCidrPool.cidr;
          }
        } else {
          subnetCidr = subnetDefinition.cidr?.value?.toCidrString()!;
        }
        if (!subnetCidr) {
          console.warn(`Subnet with name "${subnetName}" and AZ "${subnetDefinition.az}" does not have a CIDR block`);
          continue;
        }

        const subnetId = `${subnetName}_${vpcName}_az${subnetDefinition.az}`;
        const subnet = new ec2.CfnSubnet(this, subnetId, {
          cidrBlock: subnetCidr,
          vpcId: vpcObj.ref,
          availabilityZone: `${this.region}${subnetDefinition.az}`,
        });
        for (const extensions of extendVpc) {
          subnet.addDependsOn(extensions);
        }
        this.azSubnets.push({
          subnet,
          subnetName,
          id: subnet.ref,
          name: subnetName,
          az: subnetDefinition.az,
          cidrBlock: subnetCidr,
        });

        // Attach Subnet to Route-Table
        const routeTableName = subnetDefinition['route-table'];
        if (routeTableName === 'default') {
          continue;
        }

        // Find the route table ID for the route table name
        const routeTableId = this.routeTableNameToIdMap[routeTableName];
        if (!routeTableId) {
          console.warn(`Cannot find route table with name "${routeTableName}"`);
          continue;
        }

        // Associate the route table with the subnet
        new ec2.CfnSubnetRouteTableAssociation(this, `RouteTable${subnetId}`, {
          routeTableId,
          subnetId: subnet.ref,
        });
      }

      // Check for NACL's
      if (subnetConfig.nacls) {
        console.log(`NACL's Defined in VPC "${vpcName}" in Subnet "${subnetName}"`);
        new Nacl(this, `NACL-${subnetName}`, {
          accountKey,
          subnetConfig,
          vpcConfig,
          vpcId: this.vpcId,
          subnets: this.azSubnets,
          vpcConfigs: vpcConfigs!,
          vpcPools,
          subnetPools,
        });
      }
    }

    let tgw: TransitGatewayOutput | undefined;
    let tgwAttachment: TransitGatewayAttachment | undefined;
    if (config.TransitGatewayAttachConfigType.is(tgwAttach)) {
      const tgwName = tgwAttach['associate-to-tgw'];

      // Find TGW in outputs
      tgw = TransitGatewayOutputFinder.tryFindOneByName({
        outputs: vpcProps.outputs,
        accountKey: tgwAttach.account,
        name: tgwName,
      });
      if (!tgw) {
        throw new Error(`Cannot find transit gateway with name "${tgwName}"`);
      } else {
        const attachSubnetsConfig = tgwAttach['attach-subnets'] || [];
        const associateConfig = tgwAttach['tgw-rt-associate'] || [];
        const propagateConfig = tgwAttach['tgw-rt-propagate'] || [];
        const blackhole = tgwAttach['blackhole-route'];
        const subnetIds: string[] = [];
        if (vpcOutput && vpcOutput.initialSubnets.length > 0) {
          subnetIds.push(
            ...attachSubnetsConfig.flatMap(
              subnet =>
                vpcOutput.initialSubnets
                  .filter(s => s.subnetName === subnet)
                  .map(sub => this.azSubnets.getAzSubnetIdForNameAndAz(sub.subnetName, sub.az)!) || [],
            ),
          );
        } else if (vpcOutput) {
          subnetIds.push(
            ...attachSubnetsConfig.flatMap(
              subnet =>
                vpcOutput.subnets
                  .filter(s => s.subnetName === subnet)
                  .map(sub => this.azSubnets.getAzSubnetIdForNameAndAz(sub.subnetName, sub.az)!) || [],
            ),
          );
        } else {
          subnetIds.push(
            ...attachSubnetsConfig.flatMap(subnet => this.azSubnets.getAzSubnetIdsForSubnetName(subnet) || []),
          );
        }
        if (subnetIds.length === 0) {
          // TODO Throw or warn?
          // throw new Error(`Cannot attach to TGW ${tgw.name}: no subnets found to attach to for VPC ${vpcConfig.name}`);
        }

        const tgwRouteAssociates = associateConfig.map(route => tgw!.tgwRouteTableNameToIdMap[route]);
        const tgwRoutePropagates = propagateConfig.map(route => tgw!.tgwRouteTableNameToIdMap[route]);

        // Attach VPC To TGW
        tgwAttachment = new TransitGatewayAttachment(this, 'TgwAttach', {
          name: `${vpcConfig.name}_${tgw.name}_att`,
          vpcId: this.vpcId,
          subnetIds,
          transitGatewayId: tgw.tgwId,
        });

        const currentSubnets = attachSubnetsConfig.flatMap(
          subnet => this.azSubnets.getAzSubnetIdsForSubnetName(subnet) || [],
        );

        const ec2OpsRole = IamRoleOutputFinder.tryFindOneByName({
          outputs: props.vpcProps.outputs,
          accountKey,
          roleKey: 'Ec2Operations',
        });
        if (ec2OpsRole) {
          const modifyTgwAttach = new ModifyTransitGatewayAttachment(this, 'ModifyTgwAttach', {
            roleArn: ec2OpsRole.roleArn,
            subnetIds: currentSubnets,
            transitGatewayAttachmentId: tgwAttachment.transitGatewayAttachmentId,
            ignoreWhileDeleteSubnets: subnetIds,
          });
          modifyTgwAttach.node.addDependency(tgwAttachment);
        }

        // TODO add VPC To TGW attachment output
        this.tgwAttachments.push({
          name: tgw.name,
          id: tgwAttachment.transitGatewayAttachmentId,
        });

        const ownerAccountId = getAccountId(accounts, tgwAttach.account);
        if (ownerAccountId) {
          // Add tags in the TGW owner account
          new AddTagsToResourcesOutput(this, 'TgwAttachTags', {
            dependencies: [tgwAttachment],
            produceResources: () => [
              {
                resourceId: tgwAttachment!.transitGatewayAttachmentId,
                resourceType: 'tgw-attachment',
                tags: tgwAttachment!.resource.tags.renderTags(),
                targetAccountIds: [ownerAccountId],
                region: cdk.Aws.REGION,
              },
            ],
          });
        }

        // in case TGW attachment is created for the same account, we create using the same stack
        // otherwise, we will store tgw attachment output and do it in next phase
        if (tgwAttach.account === accountKey) {
          new TransitGatewayRoute(this, 'TgwRoute', {
            tgwAttachmentId: tgwAttachment.transitGatewayAttachmentId,
            tgwRouteAssociates,
            tgwRoutePropagates,
            blackhole,
            cidr: this.cidrBlock,
          });
        } else {
          let constructIndex: string;
          let existingAttachment: TransitGatewayAttachmentOutput | undefined;
          existingAttachment = existingAttachments.find(
            att =>
              att.accountKey === tgwAttach.account &&
              att.region === this.region &&
              att.cidr === this.cidrBlock &&
              att.vpc === vpcName,
          );
          if (!existingAttachment) {
            existingAttachment = existingAttachments.find(
              att => att.accountKey === tgwAttach.account && att.region === this.region && att.cidr === this.cidrBlock,
            );
          }
          if (!existingAttachment) {
            // Generate hash
            constructIndex = hashSum({
              accountKey: tgwAttach.account,
              rgion: this.region,
              cidr: this.cidrBlock,
              vpc: vpcName,
            });
          } else {
            // This might cause failure if existing users already having multiple tgw cross account attachments in same account and region
            constructIndex =
              existingAttachment.constructIndex ||
              existingAttachments
                .findIndex(
                  att =>
                    att.accountKey === tgwAttach.account && att.region === this.region && att.cidr === this.cidrBlock,
                )
                .toString();
          }
          new CfnTransitGatewayAttachmentOutput(this, 'TgwAttachmentOutput', {
            accountKey: tgwAttach.account,
            region: this.region,
            tgwAttachmentId: tgwAttachment.transitGatewayAttachmentId,
            tgwRouteAssociates,
            tgwRoutePropagates,
            blackhole: blackhole ?? false,
            cidr: this.cidrBlock,
            vpc: vpcName,
            constructIndex,
          });
        }
      }
    }

    const natgwProps = vpcConfig.natgw;
    if (config.NatGatewayConfig.is(natgwProps)) {
      const subnetConfig = natgwProps.subnet;
      const natSubnets: AzSubnet[] = [];
      if (subnetConfig.az) {
        natSubnets.push(this.azSubnets.getAzSubnetForNameAndAz(subnetConfig.name, subnetConfig.az)!);
      } else {
        natSubnets.push(...this.azSubnets.getAzSubnetsForSubnetName(subnetConfig.name));
      }

      for (const natSubnet of natSubnets) {
        console.log(`Creating natgw for Subnet "${natSubnet.name}" az: "${natSubnet.az}"`);
        const natGWName = `NATGW_${natSubnet.name}_${natSubnet.az}_natgw`;
        const eip = new ec2.CfnEIP(this, `EIP_natgw_${natSubnet.az}`);
        const natgw = new ec2.CfnNatGateway(this, natGWName, {
          allocationId: eip.attrAllocationId,
          subnetId: natSubnet.id,
        });
        this.natgwNameToIdMap[`NATGW_${natSubnet.name}_az${natSubnet.az.toUpperCase()}`.toLowerCase()] = natgw.ref;
      }
    }

    const nfwProps = vpcConfig.nfw;
    if (config.AWSNetworkFirewallConfig.is(nfwProps)) {
      const subnetConfig = nfwProps.subnet;
      const nfwSubnets: AzSubnet[] = [];
      if (subnetConfig.az) {
        nfwSubnets.push(this.azSubnets.getAzSubnetForNameAndAz(subnetConfig.name, subnetConfig.az)!);
      } else {
        nfwSubnets.push(...this.azSubnets.getAzSubnetsForSubnetName(subnetConfig.name));
      }

      this.nfw = new Nfw(this, `${nfwProps['firewall-name']}`, {
        nfwPolicy: nfwProps.policyString,
        nfwPolicyConfig: nfwProps.policy || { name: 'Sample-Firewall-Policy', path: 'nfw/nfw-example-policy.json' },
        subnets: nfwSubnets,
        vpcId: this.vpcId,
        nfwName: nfwProps['firewall-name'] || `${vpcConfig.name}-nfw`,
        acceleratorPrefix: vpcProps.acceleratorPrefix || '',
        nfwFlowLogging: nfwProps['flow-dest'] || 'None',
        nfwAlertLogging: nfwProps['alert-dest'] || 'None',
        logBucket: vpcProps.logBucket,
      });
    }

    if (vpcConfig?.['alb-forwarding']) {
      console.log('Deploying ALB forwarding');
      const albipforward = new AlbIpForwarding(this, 'albIpForwarding', {
        vpcId: this.vpcId,
        ddbKmsKey: this.ddbKmsKey,
        acceleratorPrefix: vpcProps.acceleratorPrefix || '',
      });
      console.log('ALB forwarding enabled');
    } else {
      console.log('alb ip forwarding not enabled. Skipping.');
    }
    // Add Routes to Route Tables
    if (routeTablesProps) {
      for (const routeTableProp of routeTablesProps) {
        if (routeTableProp.name === 'default') {
          continue;
        }
        const routeTableName = routeTableProp.name;
        const routeTableObj = this.routeTableNameToIdMap[routeTableName];
        if (!routeTableProp.routes?.find(r => r.target === 'IGW')) {
          natRouteTables.push(routeTableProp.name);
        }

        // Add Routes to RouteTable
        for (const route of routeTableProp.routes ? routeTableProp.routes : []) {
          let dependsOn: cdk.CfnResource | undefined;
          let gatewayId: string | undefined;
          if (route.target === 'IGW') {
            gatewayId = igw?.ref;
            dependsOn = igwAttach;
          } else if (route.target === 'VGW') {
            gatewayId = vgw?.ref;
            dependsOn = vgwAttach;
          } else if (route.target.toLowerCase() === 's3') {
            s3Routes.push(routeTableObj);
            continue;
          } else if (route.target.toLowerCase() === 'dynamodb') {
            dynamoRoutes.push(routeTableObj);
            continue;
          } else if (route.target === 'TGW' && tgw && tgwAttachment) {
            let constructName = `${routeTableName}_${route.target}`;
            if (typeof route.destination !== 'string') {
              console.warn(`Route for TGW only supports cidr as destination`);
              continue;
            }
            if (route.destination !== '0.0.0.0/0') {
              constructName = `${routeTableName}_${route.target}_${route.destination}`;
            }
            const tgwRoute = new ec2.CfnRoute(this, constructName, {
              routeTableId: routeTableObj,
              destinationCidrBlock: route.destination,
              transitGatewayId: tgw.tgwId,
            });
            tgwRoute.addDependsOn(tgwAttachment.resource);
            continue;
          } else if (route.target.startsWith('NATGW_')) {
            if (typeof route.destination !== 'string') {
              console.warn(`Route for NATGW only supports cidr as destination`);
              continue;
            }
            let constructName = `${routeTableName}_natgw_route`;
            if (route.destination !== '0.0.0.0/0') {
              constructName = `${routeTableName}_natgw_${route.destination}_route`;
            }
            const routeParams: ec2.CfnRouteProps = {
              routeTableId: routeTableObj,
              destinationCidrBlock: route.destination,
              natGatewayId: this.natgwNameToIdMap[route.target.toLowerCase()],
            };
            new ec2.CfnRoute(this, constructName, routeParams);
            continue;
          } else {
            // Need to add for different Routes
            continue;
          }

          const params: ec2.CfnRouteProps = {
            routeTableId: routeTableObj,
            destinationCidrBlock: route.destination as string,
            gatewayId,
          };
          const cfnRoute = new ec2.CfnRoute(this, `${routeTableName}_${route.target}`, params);
          if (dependsOn) {
            cfnRoute.addDependsOn(dependsOn);
          }
        }
      }
    }

    // Create VPC Gateway End Point
    const gatewayEndpoints = props.vpcProps.vpcConfig['gateway-endpoints'] || [];
    for (const gwEndpointName of gatewayEndpoints) {
      const gwService = new ec2.GatewayVpcEndpointAwsService(gwEndpointName.toLowerCase());
      new ec2.CfnVPCEndpoint(this, `Endpoint_${gwEndpointName}`, {
        serviceName: gwService.name,
        vpcId: vpcObj.ref,
        routeTableIds: gwEndpointName.toLocaleLowerCase() === 's3' ? s3Routes : dynamoRoutes,
      });
    }

    // Create all security groups
    if (vpcConfig['security-groups']) {
      this.securityGroup = new SecurityGroup(this, `SecurityGroups-${vpcConfig.name}`, {
        securityGroups: vpcConfig['security-groups'],
        vpcName: vpcConfig.name,
        vpcId: this.vpcId,
        accountKey,
        vpcConfigs: vpcConfigs!,
        installerVersion,
        vpcPools,
        subnetPools,
      });
    }

    // Share VPC subnet
    new VpcSubnetSharing(this, 'Sharing', {
      accountStacks,
      accountKey,
      accounts,
      vpcConfig,
      organizationalUnitName,
      subnets: this.azSubnets,
      limiter,
      vpc: vpcObj,
    });

    const vpcSecurityGroup = new VpcDefaultSecurityGroup(this, 'VpcDefaultSecurityGroup', {
      vpcId: this.vpcId,
      acceleratorName,
    });
    vpcSecurityGroup.node.addDependency(vpcObj);
  }