constructor()

in source/lib/network-firewall-automation-solution-stack.ts [43:1222]


  constructor(scope: cdk.Construct, id: string, props: NetworkFirewallAutomationStackProps) {
    super(scope, id, props);

    /**
     * Parameters - Values to pass to your template at runtime
     */

    const cidrBlock = new cdk.CfnParameter(this, 'cidrBlock', {
      type: 'String',
      default: '192.168.1.0/26',
      description: 'CIDR Block for VPC. Must be /26 or larger CIDR block.',
      allowedPattern: '^(?:[0-9]{1,3}\.){3}[0-9]{1,3}[\/]([0-9]?[0-6]?|[1][7-9])$'
    })

    const logRetentionPeriod = new cdk.CfnParameter(this, "LogRetentionPeriod", {
      type: "Number",
      description: "Log retention period in days.",
      allowedValues: ["1", "3", "5", "7", "14", "30", "60", "90", "120", "150", "180", "365", "400", "545", "731", "1827", "3653"],
      default: 90
    });

    const existingTransitGatewayId = new cdk.CfnParameter(this, "ExistingTransitGateway", {
      description: 'Existing AWS Transit Gateway id.',
      type: 'String',
      default: ""
    })

    const transitGatewayRTIdForAssociation = new cdk.CfnParameter(this, "TransitGatewayRouteTableIdForAssociation", {
      description: 'Existing AWS Transit Gateway route table id. Example:' +
        ' Firewall Route Table. Format: tgw-rtb-0a1b2c3d',
      type: 'String',
      default: ""
    })

    const transitGatewayRTIdForDefaultRoute = new cdk.CfnParameter(this, "TransitGatewayRTIdForDefaultRoute", {
      description: 'Existing AWS Transit Gateway route table id.' +
        ' Example: Spoke VPC Route Table. Format: tgw-rtb-4e5f6g7h',
      type: 'String',
      default: ""
    })

    const logType = new cdk.CfnParameter(this, "logType", {
      type: "String",
      description: 'The type of log to send. Alert logs report traffic that' +
        ' matches a StatefulRule with an action setting that sends an alert' +
        ' log message. Flow logs are standard network traffic flow logs.',
      allowedValues: ['ALERT', 'FLOW', 'EnableBoth'],
      default: 'FLOW',
    })

    const logDestinationType = new cdk.CfnParameter(this, "logDestinationType", {
      type: "String",
      description: 'The type of storage destination to send these logs to.' +
        ' You can send logs to an Amazon S3 bucket ' +
        'or a CloudWatch log group.',
      allowedValues: ['S3', 'CloudWatchLogs', 'ConfigureManually'],
      default: 'CloudWatchLogs',
    })

    /**
     * Metadata - Objects that provide additional information about the
     * template.
     */

    this.templateOptions.metadata = {
      "AWS::CloudFormation::Interface": {
        ParameterGroups: [
          {
            Label: { default: "VPC Configuration" },
            Parameters: [cidrBlock.logicalId]
          },
          {
            Label: { default: "Transit Gateway Configuration" },
            Parameters: [
              existingTransitGatewayId.logicalId,
              transitGatewayRTIdForAssociation.logicalId,
              transitGatewayRTIdForDefaultRoute.logicalId
            ]
          },
          {
            Label: { default: "Firewall Logging Configuration" },
            Parameters: [
              logDestinationType.logicalId,
              logType.logicalId,
              logRetentionPeriod.logicalId
            ]
          }
        ],
        ParameterLabels: {
          [cidrBlock.logicalId]: {
            default: "Provide the CIDR block for the Inspection VPC",
          },
          [existingTransitGatewayId.logicalId]: {
            default: "Provide the existing AWS Transit Gateway ID you wish to" +
              " attach to the Inspection VPC",
          },
          [transitGatewayRTIdForAssociation.logicalId]: {
            default: "Provide AWS Transit Gateway Route Table to be" +
              " associated with the Inspection VPC TGW Attachment.",
          },
          [transitGatewayRTIdForDefaultRoute.logicalId]: {
            default: "Provide the AWS Transit Gateway Route Table to receive 0.0.0.0/0 route to the Inspection VPC TGW Attachment.",
          },
          [logType.logicalId]: {
            default: "Select the type of log to send to the defined log" +
              " destination.",
          },
          [logDestinationType.logicalId]: {
            default: "Select the type of log destination for the Network" +
              " Firewall",
          },
          [logRetentionPeriod.logicalId]: {
            default: "Select the log retention period for Network Firewall" +
              " Logs.",
          }
        },
      },
    };

    /**
     * Mappings - define fixed values
     */
    const mappings = new cdk.CfnMapping(this, 'SolutionMapping')
    mappings.setValue('Version', 'Latest', 'latest')
    mappings.setValue('Route', 'QuadZero', '0.0.0.0/0')
    mappings.setValue('Log', 'Level', 'info')
    mappings.setValue('CodeCommitRepo', 'Name', 'network-firewall-config-repo-')
    mappings.setValue('Metrics', 'URL', 'https://metrics.awssolutionsbuilder.com/generic')
    mappings.setValue('Solution', 'Identifier', 'SO0108')
    mappings.setValue('TransitGatewayAttachment', 'ApplianceMode', 'enable')

    const send = new cdk.CfnMapping(this, 'Send')
    send.setValue('AnonymousUsage', 'Data', 'Yes')
    send.setValue('ParameterKey', 'UniqueId', `/Solutions/${props.solutionName}/UUID`)


    /**
     * Conditions - control whether certain resources are created or whether
     * certain resource properties are assigned a value during stack
     * creation or update.
     */

    const isLoggingInS3 = new cdk.CfnCondition(this,
      "LoggingInS3",
      {
        expression: cdk.Fn.conditionEquals(logDestinationType.valueAsString, 'S3')
      })

    const isLoggingInCloudWatch = new cdk.CfnCondition(this,
      "LoggingInCloudWatch",
      {
        expression: cdk.Fn.conditionEquals(logDestinationType.valueAsString, 'CloudWatchLogs')
      })

    const isNotLoggingConfigureManually = new cdk.CfnCondition(this,
      "NotLoggingConfigureManually",
      {
        expression: cdk.Fn.conditionNot(cdk.Fn.conditionEquals(logDestinationType.valueAsString, 'ConfigureManually'))
      })

    /**
     * condition to determine if transit gateway id is provided or not if
     * provided use it to create transit gateway attachment else skip
     */

    const createTransitGatewayAttachment = new cdk.CfnCondition(this,
      "CreateTransitGatewayAttachment",
      {
        expression: cdk.Fn.conditionNot(cdk.Fn.conditionEquals(existingTransitGatewayId.valueAsString, ''))
      })

    /**
     * condition to determine if transit gateway route table id is provided or
     * not. if provided use it to create route table association else skip
     */
    const createTransitGatewayRTAssociation = new cdk.CfnCondition(this,
      "CreateTransitGatewayRTAssociation",
      {
        expression: cdk.Fn.conditionAnd(
          cdk.Fn.conditionNot(
            cdk.Fn.conditionEquals(
              transitGatewayRTIdForAssociation.valueAsString, '')), createTransitGatewayAttachment)
      })

    /**
     * condition to determine if transit gateway route table id is provided or
     * not. if provided use it to create route table propagation else skip
     */
    const createDefaultRouteFirewallRT = new cdk.CfnCondition(this,
      "CreateDefaultRouteFirewallRT",
      {
        expression: cdk.Fn.conditionAnd(
          cdk.Fn.conditionNot(
            cdk.Fn.conditionEquals(
              transitGatewayRTIdForDefaultRoute.valueAsString, '')), createTransitGatewayAttachment)
      })

    /**
     * Resources - Specifies the stack resources and their properties
     */

    this.templateOptions.templateFormatVersion = '2010-09-09';

    // Create a new VPC

    const vpc = new ec2.CfnVPC(this, 'VPC', {
      cidrBlock: cidrBlock.valueAsString,
    });

    //KMS Key for the VPC Flow logs and Firewall Logs
    const KMSKeyForNetworkFirewallLogDestinations = new kms.Key(this, "KMSKeyForNetworkFirewallLogDestinations", {
      description: "This key will be used for encrypting the vpc flow logs and firewall logs.",
      enableKeyRotation: true
    })

    //Permissions for network firewall service to be able use this key for publishing logs to S3.
    KMSKeyForNetworkFirewallLogDestinations.addToResourcePolicy(new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      resources: ["*"],
      principals: [new iam.ServicePrincipal("delivery.logs.amazonaws.com")],
      actions: ["kms:GenerateDataKey*"]
    }))
    //Permissions for network firewall service to be able use this key for publishing logs to cloudwatch.
    KMSKeyForNetworkFirewallLogDestinations.addToResourcePolicy(new iam.PolicyStatement({
      effect: iam.Effect.ALLOW,
      resources: ["*"],
      actions: [
        "kms:Encrypt*",
        "kms:Decrypt*",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:Describe*"
      ],
      principals: [
        new iam.ServicePrincipal(`logs.${cdk.Aws.REGION}.amazonaws.com`)
      ]
    }))

    // Create a new log group for Firewall logging
    const cloudWatchLogGroup = new logs.CfnLogGroup(this, 'CloudWatchLogGroup', {
      retentionInDays: logRetentionPeriod.valueAsNumber,
      kmsKeyId: KMSKeyForNetworkFirewallLogDestinations.keyArn
    })

    cloudWatchLogGroup.cfnOptions.condition = isLoggingInCloudWatch;

    const logsBucket = new s3.Bucket(this, 'Logs', {
      encryption: s3.BucketEncryption.KMS,
      encryptionKey: KMSKeyForNetworkFirewallLogDestinations,
      publicReadAccess: false,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
      lifecycleRules: [{
        expiration: cdk.Duration.days(logRetentionPeriod.valueAsNumber)
      }]
    });

    const cfnLogsBucket = logsBucket.node.defaultChild as s3.CfnBucket;
    cfnLogsBucket.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W35',
          reason: 'Logs bucket does not require logging configuration'
        }, {
          id: 'W51',
          reason: 'Logs bucket is private and does not require a bucket policy'
        }]
      }
    };
    cfnLogsBucket.cfnOptions.condition = isLoggingInS3;

    //Solution Logging Changes stop.


    vpc.applyRemovalPolicy(RemovalPolicy.RETAIN)
    vpc.tags.setTag('Name', `${cdk.Aws.STACK_NAME}-Inspection-VPC`)
    vpc.tags.setTag('created-by', `${props.solutionName}`)

    const cidrCount = 4
    const cidrBits = '4'
    const availabilityZoneA = {
      "Fn::Select": [
        "0",
        {
          "Fn::GetAZs": ""
        }
      ]
    }
    const availabilityZoneB = {
      "Fn::Select": [
        "1",
        {
          "Fn::GetAZs": ""
        }
      ]
    }

    // Create Firewall Subnet 1
    const NetworkFirewallSubnet1 = new ec2.CfnSubnet(this, "NetworkFirewallSubnet1", {
      vpcId: vpc.ref,
      cidrBlock: cdk.Fn.select(
        0,
        cdk.Fn.cidr(
          vpc.attrCidrBlock,
          cidrCount,
          cidrBits
        )
      )
    })
    NetworkFirewallSubnet1.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-FirewallSubnet1`)
    NetworkFirewallSubnet1.applyRemovalPolicy(RemovalPolicy.RETAIN)
    NetworkFirewallSubnet1.addPropertyOverride('AvailabilityZone', availabilityZoneA)


    // Create Firewall Subnet 2
    const NetworkFirewallSubnet2 = new ec2.CfnSubnet(this, "NetworkFirewallSubnet2", {
      vpcId: vpc.ref,
      cidrBlock: cdk.Fn.select(
        1,
        cdk.Fn.cidr(
          vpc.attrCidrBlock,
          cidrCount,
          cidrBits
        )
      )
    })

    NetworkFirewallSubnet2.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-FirewallSubnet2`)
    NetworkFirewallSubnet2.applyRemovalPolicy(RemovalPolicy.RETAIN)
    NetworkFirewallSubnet2.addPropertyOverride('AvailabilityZone', availabilityZoneB)

    //Subnet Route Tables.
    const firewallSubnetRouteTable = new ec2.CfnRouteTable(this, "FirewallSubnetRouteTable", {
      vpcId: vpc.ref
    })
    firewallSubnetRouteTable.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-FirewallSubnetRouteTable`)
    firewallSubnetRouteTable.applyRemovalPolicy(RemovalPolicy.RETAIN)

    //Subnet Route Table Associations.
    const NetworkFirewallSubnet1RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, "NetworkFirewallSubnet1RouteTableAssociation", {
      subnetId: NetworkFirewallSubnet1.ref,
      routeTableId: firewallSubnetRouteTable.ref
    })
    NetworkFirewallSubnet1RouteTableAssociation.applyRemovalPolicy(RemovalPolicy.RETAIN)

    const NetworkFirewallSubnet2RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, "NetworkFirewallSubnet2RouteTableAssociation", {
      subnetId: NetworkFirewallSubnet2.ref,
      routeTableId: firewallSubnetRouteTable.ref
    })
    NetworkFirewallSubnet2RouteTableAssociation.applyRemovalPolicy(RemovalPolicy.RETAIN)

    // Create Transit Gateway Subnet 1
    const vpcTGWSubnet1 = new ec2.CfnSubnet(this, "VPCTGWSubnet1", {
      vpcId: vpc.ref,
      cidrBlock: cdk.Fn.select(
        2,
        cdk.Fn.cidr(
          vpc.attrCidrBlock,
          cidrCount,
          cidrBits
        )
      )
    })
    vpcTGWSubnet1.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-VPCTGWSubnet1`)
    vpcTGWSubnet1.applyRemovalPolicy(RemovalPolicy.RETAIN)
    vpcTGWSubnet1.addPropertyOverride('AvailabilityZone', availabilityZoneA)

    // Create Transit Gateway Subnet 2
    const vpcTGWSubnet2 = new ec2.CfnSubnet(this, "VPCTGWSubnet2", {
      vpcId: vpc.ref,
      cidrBlock: cdk.Fn.select(
        3,
        cdk.Fn.cidr(
          vpc.attrCidrBlock,
          cidrCount,
          cidrBits
        )
      )
    })
    vpcTGWSubnet2.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-VPCTGWSubnet2`)
    vpcTGWSubnet2.applyRemovalPolicy(RemovalPolicy.RETAIN)
    vpcTGWSubnet2.addPropertyOverride('AvailabilityZone', availabilityZoneB)

    //Route Tables for VPC Transit Gateway subnets.
    const vpcTGWRouteTable1 = new ec2.CfnRouteTable(this, "VPCTGWRouteTable1", {
      vpcId: vpc.ref
    })
    vpcTGWRouteTable1.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-TGWSubnetRouteTable1`)
    vpcTGWRouteTable1.applyRemovalPolicy(RemovalPolicy.RETAIN)

    const vpcTGWRouteTable2 = new ec2.CfnRouteTable(this, "VPCTGWRouteTable2", {
      vpcId: vpc.ref
    })
    vpcTGWRouteTable2.tags.setTag("Name", `${cdk.Aws.STACK_NAME}-TGWSubnetRouteTable2`)
    vpcTGWRouteTable2.applyRemovalPolicy(RemovalPolicy.RETAIN)

    //Subnet Route Table Associations for Transit Gateway Subnets
    const vpcTGWSubnet1RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, "VPCTGWSubnet1RouteTableAssociation", {
      subnetId: vpcTGWSubnet1.ref,
      routeTableId: vpcTGWRouteTable1.ref
    })
    vpcTGWSubnet1RouteTableAssociation.applyRemovalPolicy(RemovalPolicy.RETAIN)

    const vpcTGWSubnet2RouteTableAssociation = new ec2.CfnSubnetRouteTableAssociation(this, "VPCTGWSubnet2RouteTableAssociation", {
      subnetId: vpcTGWSubnet2.ref,
      routeTableId: vpcTGWRouteTable2.ref,
    })
    vpcTGWSubnet2RouteTableAssociation.applyRemovalPolicy(RemovalPolicy.RETAIN)

    //VPC Flow Log
    const logGroup = new logs.CfnLogGroup(this, "LogGroupFlowLogs", {
      retentionInDays: logRetentionPeriod.valueAsNumber,
      logGroupName: cdk.Aws.STACK_NAME,
      kmsKeyId: KMSKeyForNetworkFirewallLogDestinations.keyArn
    })

    const flowLogRole = new iam.Role(this, "RoleFlowLogs", {
      assumedBy: new iam.ServicePrincipal("vpc-flow-logs.amazonaws.com")
    });

    const policyStatement = new iam.PolicyStatement({
      actions: [
        "logs:CreateLogStream",
        "logs:DescribeLogStreams",
        "logs:PutLogEvents",
        "logs:CreateLogGroup",
        "logs:DescribeLogGroups"],
      resources: [logGroup.attrArn]
    });
    policyStatement.effect = iam.Effect.ALLOW;
    flowLogRole.addToPolicy(policyStatement);


    new ec2.CfnFlowLog(this, "FlowLog", {
      deliverLogsPermissionArn: flowLogRole.roleArn,
      logGroupName: logGroup.logGroupName,
      resourceId: vpc.ref,
      resourceType: "VPC",
      trafficType: "ALL"
    });

    //Start: associate for an existing transit gateway if user provides one.


    //Transit gateway attachment.
    const vpcTGWAttachment = new ec2.CfnTransitGatewayAttachment(this, 'VPC_TGW_ATTACHMENT', {
      transitGatewayId: existingTransitGatewayId.valueAsString,
      vpcId: vpc.ref,
      subnetIds: [
        vpcTGWSubnet1.ref,
        vpcTGWSubnet2.ref
      ]
    })
    vpcTGWAttachment.cfnOptions.condition = createTransitGatewayAttachment
    vpcTGWAttachment.tags.setTag('Name', `${cdk.Aws.STACK_NAME}-Inspection-VPC-Attachment`)
    vpcTGWAttachment.applyRemovalPolicy(RemovalPolicy.RETAIN)
    vpcTGWAttachment.addDeletionOverride("UpdateReplacePolicy")

    //add the transit gateway id provided by the user to the firewall route
    // table created for transit gateway interaction.
    const defaultTransitGatewayRoute = new ec2.CfnRoute(this, 'TGWRoute', {
      routeTableId: firewallSubnetRouteTable.ref,
      destinationCidrBlock: mappings.findInMap('Route', 'QuadZero'),
      transitGatewayId: existingTransitGatewayId.valueAsString
    })
    defaultTransitGatewayRoute.cfnOptions.condition = createTransitGatewayAttachment
    defaultTransitGatewayRoute.addDependsOn(vpcTGWAttachment)


    //Transit Gateway association with the TGW route table id provided by the user.
    const tgwRouteTableAssociation = new ec2.CfnTransitGatewayRouteTableAssociation(this, 'VPCTGWRouteTableAssociation', {
      transitGatewayAttachmentId: vpcTGWAttachment.ref,
      transitGatewayRouteTableId: transitGatewayRTIdForAssociation.valueAsString
    })

    //createTransitGatewayRTAssociation
    tgwRouteTableAssociation.cfnOptions.condition = createTransitGatewayRTAssociation
    tgwRouteTableAssociation.addOverride("DeletionPolicy", "Retain")
    tgwRouteTableAssociation.addDeletionOverride("UpdateReplacePolicy")

    // Add default route to Instection VPC-TGW Attachment in the Spoke VPC
    // Route Transit Gateway Route Table
    const defaultRouteSpokeVPCTGWRouteTable = new ec2.CfnTransitGatewayRoute(this, 'DefaultRouteSpokeVPCTGWRouteTable', {
      transitGatewayRouteTableId: transitGatewayRTIdForDefaultRoute.valueAsString,
      destinationCidrBlock: mappings.findInMap('Route', 'QuadZero'),
      transitGatewayAttachmentId: vpcTGWAttachment.ref
    })
    defaultRouteSpokeVPCTGWRouteTable.cfnOptions.condition = createDefaultRouteFirewallRT
    defaultRouteSpokeVPCTGWRouteTable.addOverride("DeletionPolicy", "Retain")

    //End: Transit gateway changes.

    //CodeCommit Repo and Code Pipeline with default policy created.
    const codeCommitRepo = new codecommit.Repository(this, 'NetworkFirewallCodeRepository', {
      repositoryName: mappings.findInMap("CodeCommitRepo", "Name") + cdk.Aws.STACK_NAME,
      description: 'This repository is created by the AWS Network Firewall' +
        ' solution for AWS Transit Gateway, to store and trigger changes to' +
        ' the network firewall rules and configurations.'
    })

    const codeCommitRepo_cfn_ref = codeCommitRepo.node.defaultChild as codecommit.CfnRepository
    codeCommitRepo_cfn_ref.addOverride("Properties.Code.S3.Bucket", `${props.solutionBucket}-${this.region}`)
    codeCommitRepo_cfn_ref.addOverride("Properties.Code.S3.Key", `${props.solutionName}/${mappings.findInMap('Version', 'Latest')}/network-firewall-configuration.zip`)
    codeCommitRepo_cfn_ref.addOverride("DeletionPolicy", "Retain")
    codeCommitRepo_cfn_ref.addOverride("UpdateReplacePolicy", "Retain")

    const codeBuildStagesSourceCodeBucket = new s3.Bucket(this, 'CodeBuildStagesSourceCodeBucket', {
      publicReadAccess: false,
      blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL
    });

    const sourceOutputArtifact = new codepipeline.Artifact('SourceArtifact')
    const buildOutputArtifact = new codepipeline.Artifact('BuildArtifact')

    const subnetIds = NetworkFirewallSubnet1.ref + ',' + NetworkFirewallSubnet2.ref
    const codeBuildEnvVariables = {
      ['LOG_LEVEL']:
      {
        value: mappings.findInMap('Log', 'Level'),
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['VPC_ID']:
      {
        value: vpc.ref,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['SUBNET_IDS']:
      {
        value: subnetIds,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['LOG_TYPE']:
      {
        value: logType.valueAsString,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['LOG_DESTINATION_TYPE']:
      {
        value: logDestinationType.valueAsString,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['S3_LOG_BUCKET_NAME']:
      {
        value: cdk.Fn.conditionIf('LoggingInS3', logsBucket.bucketName, 'NotConfigured'),
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['CLOUDWATCH_LOG_GROUP_NAME']:
      {
        value: cdk.Fn.conditionIf('LoggingInCloudWatch', cloudWatchLogGroup.ref, 'NotConfigured'),
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['VPC_TGW_ATTACHMENT_AZ_1']:
      {
        value: cdk.Fn.getAtt(
          'NetworkFirewallSubnet1',
          'AvailabilityZone').toString(),
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['VPC_TGW_ATTACHMENT_AZ_2']:
      {
        value: cdk.Fn.getAtt(
          'NetworkFirewallSubnet2',
          'AvailabilityZone').toString(),
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_1']:
      {
        value: vpcTGWRouteTable1.ref,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['VPC_TGW_ATTACHMENT_ROUTE_TABLE_ID_2']:
      {
        value: vpcTGWRouteTable2.ref,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['CODE_BUILD_SOURCE_CODE_S3_KEY']: {
        value: `${props.solutionName}/${props.solutionVersion}`,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['STACK_ID']: {
        value: `${cdk.Aws.STACK_ID}`,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['SSM_PARAM_FOR_UUID']: {
        value: send.findInMap('ParameterKey', 'UniqueId'),
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['SEND_ANONYMOUS_METRICS']: {
        value: `${send.findInMap('AnonymousUsage', 'Data')}`,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['SOLUTION_ID']: {
        value: `${mappings.findInMap('Solution', 'Identifier')}`,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['METRICS_URL']: {
        value: `${mappings.findInMap('Metrics', 'URL')}`,
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['TRANSIT_GATEWAY_ATTACHMENT_ID']: {
        value: cdk.Fn.conditionIf(createTransitGatewayAttachment.logicalId, vpcTGWAttachment.ref, ''),
        type: BuildEnvironmentVariableType.PLAINTEXT
      },
      ['TRANSIT_GATEWAY_ATTACHMENT_APPLIANCE_MODE']: {
        value: mappings.findInMap('TransitGatewayAttachment', 'ApplianceMode'),
        type: BuildEnvironmentVariableType.PLAINTEXT
      }
    }

    // Code build project, code build role will be created by the construct.
    const buildProject = new PipelineProject(this, 'BuildProject', {
      buildSpec: BuildSpec.fromObject({
        version: '0.2',
        phases: {
          install: {
            'runtime-versions': {
              nodejs: '12'
            },
            commands: [`export current=$(pwd)`, `export sourceCodeKey=$CODE_BUILD_SOURCE_CODE_S3_KEY`]
          },
          pre_build: {
            commands: [
              `cd $current`,
              `pwd; ls -ltr`,
              `echo 'Download Network Firewall Solution Package'`,
              `aws s3 cp s3://${codeBuildStagesSourceCodeBucket.bucketName}/$sourceCodeKey/network-firewall-automation.zip $current || true`,
              `if [ -f $current/network-firewall-automation.zip ];then exit 0;else echo \"Copy file to s3 bucket\"; aws s3 cp s3://${props.solutionBucket}-${cdk.Aws.REGION}/$sourceCodeKey/network-firewall-automation.zip s3://${codeBuildStagesSourceCodeBucket.bucketName}/$sourceCodeKey/network-firewall-automation.zip; aws s3 cp s3://${codeBuildStagesSourceCodeBucket.bucketName}/$sourceCodeKey/network-firewall-automation.zip $current; fi;`,
              `unzip -o $current/network-firewall-automation.zip -d $current`,
              `pwd; ls -ltr`,
            ]
          },
          build: {
            commands: [
              `echo "Validating the firewall config"`,
              `node build.js`
            ]
          }
        },
        artifacts: {
          files: "**/*"
        }
      }),
      environment: {
        buildImage: LinuxBuildImage.STANDARD_4_0
      },
      environmentVariables: codeBuildEnvVariables
    })

    const buildStageIAMPolicy = new iam.Policy(this, 'buildStageIAMPolicy', {
      statements: [
        new iam.PolicyStatement({
          actions: [
            "network-firewall:CreateFirewallPolicy",
            "network-firewall:CreateRuleGroup"
          ],
          resources: [
            cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:stateful-rulegroup/*"),
            cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:firewall-policy/*"),
            cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:stateless-rulegroup/*")
          ],
          effect: iam.Effect.ALLOW
        }),
        new iam.PolicyStatement({
          actions: ["s3:GetObject"],
          resources: [cdk.Fn.sub("arn:${AWS::Partition}:s3:::${CodeBucketName}/${KeyName}/*", {
            CodeBucketName: `${props.solutionBucket}-${this.region}`,
            KeyName: `${props.solutionName}`
          }),
          `arn:${cdk.Aws.PARTITION}:s3:::${codeBuildStagesSourceCodeBucket.bucketName}/*`]
        }),
        new iam.PolicyStatement({
          actions: ["s3:PutObject"],
          resources: [
            `arn:${cdk.Aws.PARTITION}:s3:::${codeBuildStagesSourceCodeBucket.bucketName}/*`
          ],
          effect: iam.Effect.ALLOW
        }),
        new iam.PolicyStatement({
          actions: [
            "ssm:PutParameter",
            "ssm:GetParameter",
          ],
          effect: iam.Effect.ALLOW,
          resources: [
            cdk.Fn.sub("arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${ParameterKey}", {
              ParameterKey: `${send.findInMap('ParameterKey', 'UniqueId')}`
            })
          ]
        }),
      ]
    })

    buildProject.role?.attachInlinePolicy(buildStageIAMPolicy)

    //IAM Policy and Role to execute deploy stage

    const deployStageFirewallPolicy = new iam.Policy(this,
      'deployStageFirewallPolicy',
      {
        statements: [
          new iam.PolicyStatement({
            actions: [
              "network-firewall:CreateFirewall",
              "network-firewall:UpdateFirewallDeleteProtection",
              "network-firewall:DeleteRuleGroup",
              "network-firewall:DescribeLoggingConfiguration",
              "network-firewall:UpdateFirewallDescription",
              "network-firewall:CreateRuleGroup",
              "network-firewall:DescribeFirewall",
              "network-firewall:DeleteFirewallPolicy",
              "network-firewall:UpdateRuleGroup",
              "network-firewall:DescribeRuleGroup",
              "network-firewall:ListRuleGroups",
              "network-firewall:UpdateSubnetChangeProtection",
              "network-firewall:UpdateFirewallPolicyChangeProtection",
              "network-firewall:AssociateFirewallPolicy",
              "network-firewall:DescribeFirewallPolicy",
              "network-firewall:UpdateFirewallPolicy",
              "network-firewall:DescribeResourcePolicy",
              "network-firewall:CreateFirewallPolicy",
              "network-firewall:UpdateLoggingConfiguration",
              "network-firewall:TagResource"
            ],
            resources: [
              cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:stateful-rulegroup/*"),
              cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:firewall-policy/*"),
              cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:firewall/*"),
              cdk.Fn.sub("arn:${AWS::Partition}:network-firewall:${AWS::Region}:${AWS::AccountId}:stateless-rulegroup/*")
            ]
          }),
          new iam.PolicyStatement({
            actions: ["s3:GetObject"],
            resources: [cdk.Fn.sub("arn:${AWS::Partition}:s3:::${CodeBucketName}/${KeyName}/*", {
              CodeBucketName: `${props.solutionBucket}-${this.region}`,
              KeyName: `${props.solutionName}`
            }),
            `arn:${cdk.Aws.PARTITION}:s3:::${codeBuildStagesSourceCodeBucket.bucketName}/*`]
          }),
          new iam.PolicyStatement({
            actions: [
              "ec2:DescribeVpcs",
              "ec2:DescribeSubnets",
              "ec2:DescribeRouteTables"
            ],
            resources: ["*"]
          }),
          new iam.PolicyStatement({
            actions: [
              "ec2:CreateRoute",
              "ec2:DeleteRoute",
            ],
            effect: iam.Effect.ALLOW,
            resources: [
              `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:route-table/${vpcTGWRouteTable1.ref}`,
              `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:route-table/${vpcTGWRouteTable2.ref}`
            ]
          }),
          new iam.PolicyStatement({
            actions: ["iam:CreateServiceLinkedRole"],
            resources: [cdk.Fn.sub("arn:aws:iam::${AWS::AccountId}:role/aws-service-role/network-firewall.amazonaws.com/AWSServiceRoleForNetworkFirewall")]
          })
        ]
      })

    const deployStageFirewallPolicyResource = deployStageFirewallPolicy.node.findChild('Resource') as iam.CfnPolicy;

    deployStageFirewallPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [
          {
            id: 'W12',
            reason: 'Resource * is required for describe APIs'
          }]
      }
    };

    //add modify transit gateway attachement permission only if the transit gateway attachment is provided.
    const deployStageModifyTransitGatewayAttachmentPolicy = new iam.Policy(this, 'deployStageModifyTransitGatewayAttachmentPolicy', {
      statements: [
        new iam.PolicyStatement({
          actions: [
            "ec2:ModifyTransitGatewayVpcAttachment"
          ],
          effect: iam.Effect.ALLOW,
          resources: [
            `arn:${cdk.Aws.PARTITION}:ec2:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:transit-gateway-attachment/${vpcTGWAttachment.ref}`,
          ]
        })
      ]
    })
    const resourcePolicyModifyTGWAttachment = deployStageModifyTransitGatewayAttachmentPolicy.node.findChild('Resource') as iam.CfnPolicy;
    resourcePolicyModifyTGWAttachment.cfnOptions.condition = createTransitGatewayAttachment

    const deployStageFirewallLoggingPolicy = new iam.Policy(this,
      'deployStageFirewallLoggingPolicy',
      {
        statements: [
          new iam.PolicyStatement({
            actions: [
              "logs:CreateLogDelivery",
              "logs:GetLogDelivery",
              "logs:UpdateLogDelivery",
              "logs:DeleteLogDelivery",
              "logs:ListLogDeliveries"
            ],
            resources: ["*"] // Per IAM service must use All Resources
          })
        ]
      })

    const deployStageFirewallLoggingResource = deployStageFirewallLoggingPolicy.node.findChild('Resource') as iam.CfnPolicy;

    deployStageFirewallLoggingResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [
          {
            id: 'W12',
            reason: 'Resource * is required for these actions.'
          }]
      }
    };

    // skip creating the 'deployStageFirewallLoggingPolicy' IAM policy if
    // logging destination type is set to configure manually
    deployStageFirewallLoggingResource.cfnOptions.condition = isNotLoggingConfigureManually

    const deployStageFirewallLoggingS3Policy = new iam.Policy(this,
      'deployStageFirewallLoggingS3Policy',
      {
        statements: [
          new iam.PolicyStatement({
            actions: [
              "s3:PutBucketPolicy",
              "s3:GetBucketPolicy"
            ],
            resources: [logsBucket.bucketArn]
          })
        ]
      })

    const deployStageFirewallLoggingS3PolicyResource = deployStageFirewallLoggingS3Policy.node.findChild('Resource') as iam.CfnPolicy;

    // create the 'deployStageFirewallLoggingS3Policy' IAM policy only if
    // logging destination type is set to S3
    deployStageFirewallLoggingS3PolicyResource.cfnOptions.condition = isLoggingInS3

    const deployStageFirewallLoggingCWPolicy = new iam.Policy(this,
      'deployStageFirewallLoggingCWPolicy',
      {
        statements: [
          new iam.PolicyStatement({
            actions: [
              "logs:PutResourcePolicy",
              "logs:DescribeResourcePolicies"
            ],
            resources: ["*"] // Per IAM service must use All Resources
          }),
          new iam.PolicyStatement({
            actions: [
              "logs:DescribeLogGroups"
            ],
            resources: [
              cdk.Fn.sub("arn:${AWS::Partition}:logs:*:${AWS::AccountId}:log-group:*")
            ]
          })
        ]
      })

    const deployStageFirewallLoggingCWPolicyResource = deployStageFirewallLoggingCWPolicy.node.findChild('Resource') as iam.CfnPolicy;

    deployStageFirewallLoggingCWPolicyResource.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [
          {
            id: 'W12',
            reason: 'Resource * is required for describe APIs'
          }]
      }
    };

    // create the 'deployStageFirewallLoggingCWPolicy' IAM policy if
    // logging destination type is set to CloudWatch Logs
    deployStageFirewallLoggingCWPolicyResource.cfnOptions.condition = isLoggingInCloudWatch

    // Code deploy build action project, role will be created by the construct.

    const deployProject = new PipelineProject(this, 'DeployProject', {
      buildSpec: BuildSpec.fromObject({
        version: '0.2',
        phases: {
          install: {
            'runtime-versions': {
              nodejs: '12'
            },
            commands: [`export current=$(pwd)`, `export sourceCodeKey=$CODE_BUILD_SOURCE_CODE_S3_KEY`]
          },
          pre_build: {
            commands: [
              `cd $current`,
              `pwd; ls -ltr`,
              `echo 'Download Network Firewall Solution Package'`,
              `aws s3 cp s3://${codeBuildStagesSourceCodeBucket.bucketName}/$sourceCodeKey/network-firewall-automation.zip $current`,
              `unzip -o $current/network-firewall-automation.zip -d $current`,
              `pwd; ls -ltr`,
            ]
          },
          build: {
            commands: [
              `echo "Initiating Network Firewall Automation"`,
              `node index.js`
            ]
          },
          post_build: {
            commands: []
          }
        },
        artifacts: {
          files: "**/*"
        }
      }),
      environment: {
        buildImage: LinuxBuildImage.STANDARD_4_0
      },
      environmentVariables: codeBuildEnvVariables
    })

    // attach inline IAM policies with the default CodeBuild role.
    deployProject.role?.attachInlinePolicy(deployStageFirewallPolicy)
    deployProject.role?.attachInlinePolicy(deployStageFirewallLoggingPolicy)
    deployProject.role?.attachInlinePolicy(deployStageFirewallLoggingS3Policy)
    deployProject.role?.attachInlinePolicy(deployStageFirewallLoggingCWPolicy)
    deployProject.role?.attachInlinePolicy(deployStageModifyTransitGatewayAttachmentPolicy)


    const codePipeline = new codepipeline.Pipeline(this, `NetworkFirewallCodePipeline`, {
      stages: [
        {
          stageName: 'Source',
          actions: [
            new codepipeline_action.CodeCommitSourceAction({
              actionName: 'Source',
              repository: codeCommitRepo,
              branch: 'main',
              output: sourceOutputArtifact,
            })
          ]
        },
        {
          stageName: 'Validation',
          actions: [
            new codepipeline_action.CodeBuildAction({
              actionName: 'CodeBuild',
              input: sourceOutputArtifact,
              project: buildProject,
              outputs: [buildOutputArtifact]
            })
          ]
        },
        {
          stageName: 'Deployment',
          actions: [
            new codepipeline_action.CodeBuildAction({
              actionName: 'CodeDeploy',
              input: buildOutputArtifact,
              project: deployProject,
            })
          ]
        }]
    })

    //Adding bucket encryption
    const kmsKeyCfn_ref = codePipeline.artifactBucket.encryptionKey?.node.defaultChild as kms.CfnKey
    kmsKeyCfn_ref.addPropertyOverride('EnableKeyRotation', true)

    const stack = cdk.Stack.of(this);

    const codePipelineArtifactBucketKmsKeyAlias = stack.node.findChild("NetworkFirewallCodePipeline").node.findChild("ArtifactsBucketEncryptionKeyAlias").node.defaultChild as kms.CfnAlias
    codePipelineArtifactBucketKmsKeyAlias.addPropertyOverride("AliasName", {
      "Fn::Join": [
        "",
        [
          "alias/",
          {
            "Ref": "AWS::StackName"
          },
          "-artifactBucket-EncryptionKeyAlias"
        ]
      ]
    })

    const codeBuildStagesSourceCodeBucket_cfn_ref = codeBuildStagesSourceCodeBucket.node.defaultChild as s3.CfnBucket
    codeBuildStagesSourceCodeBucket_cfn_ref.bucketEncryption = {
      serverSideEncryptionConfiguration: [
        {
          serverSideEncryptionByDefault: {
            kmsMasterKeyId: codePipeline.artifactBucket.encryptionKey?.keyArn,
            sseAlgorithm: "aws:kms"
          }
        }
      ]
    }

    codeBuildStagesSourceCodeBucket_cfn_ref.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [{
          id: 'W35',
          reason: 'Source Code bucket bucket does not require logging configuration'
        }, {
          id: 'W51',
          reason: 'Source Code bucket is private and does not require a bucket policy'
        }]
      }
    };

    //S3 Bucket policy for the pipeline artifacts bucket
    const bucketPolicy = new s3.BucketPolicy(this, 'CodePipelineArtifactS3BucketPolicy', {
      bucket: codePipeline.artifactBucket,
      removalPolicy: RemovalPolicy.RETAIN
    })

    bucketPolicy.document.addStatements(
      new iam.PolicyStatement({
        effect: iam.Effect.ALLOW,
        actions: [
          's3:DeleteBucket'
        ],
        principals: [new iam.ServicePrincipal('cloudformation.amazonaws.com')],
        resources: [
          codePipeline.artifactBucket.bucketArn
        ]
      }),
      new iam.PolicyStatement({
        effect: iam.Effect.DENY,
        actions: [
          's3:GetObject'
        ],
        principals: [
          new iam.AnyPrincipal()
        ],
        resources: [
          `${codePipeline.artifactBucket.bucketArn}/*`,
          `${codePipeline.artifactBucket.bucketArn}`
        ],
        conditions: {
          Bool: {
            "aws:SecureTransport": false
          }
        }
      }));

    const bucketPolicyForlogsBucket = new s3.BucketPolicy(this, 'CloudWatchLogsForNetworkFirewallBucketPolicy', {
      bucket: logsBucket,
      removalPolicy: RemovalPolicy.RETAIN
    })

    bucketPolicyForlogsBucket.document.addStatements(
      new iam.PolicyStatement({
        effect: iam.Effect.DENY,
        actions: [
          's3:GetObject'
        ],
        principals: [
          new iam.AnyPrincipal()
        ],
        resources: [
          `${logsBucket.bucketArn}/*`,
          `${logsBucket.bucketArn}`
        ],
        conditions: {
          Bool: {
            "aws:SecureTransport": false
          }
        }
      }));

    const bucketPolicyForlogsBucket_cfn_ref = bucketPolicyForlogsBucket.node.defaultChild as s3.CfnBucketPolicy
    bucketPolicyForlogsBucket_cfn_ref.cfnOptions.condition = isLoggingInS3

    const bucketPolicyForSourceCodeBucket = new s3.BucketPolicy(this, 'CodeBuildStageSourceCodeBucketPolicy', {
      bucket: codeBuildStagesSourceCodeBucket,
      removalPolicy: RemovalPolicy.RETAIN
    });

    bucketPolicyForSourceCodeBucket.document.addStatements(
      new iam.PolicyStatement({
        effect: iam.Effect.DENY,
        actions: [
          's3:GetObject'
        ],
        principals: [
          new iam.AnyPrincipal()
        ],
        resources: [
          `${codeBuildStagesSourceCodeBucket.bucketArn}`,
          `${codeBuildStagesSourceCodeBucket.bucketArn}/*`
        ],
        conditions: {
          Bool: {
            "aws:SecureTransport": false
          }
        }
      }));

    //disable W35 for the artifact bucket as it only store the artifact files.
    const w35Rule = {
      rules_to_suppress: [{
        id: 'W35',
        reason: "This S3 bucket is used as the destination for 'NetworkFirewallCodePipelineArtifactsBucket'"
      }]
    }
    const s3ArtifactBucket_cfn_ref = codePipeline.artifactBucket.node.defaultChild as s3.CfnBucket
    s3ArtifactBucket_cfn_ref.cfnOptions.metadata = {
      cfn_nag: w35Rule
    }

    /**
     * Outputs - describes the values that are returned whenever you view
     * your stack's properties.
     */
    new cdk.CfnOutput(this, 'Inspection VPC ID', {
      value: vpc.ref,
      description: 'Inspection VPC ID to create Network Firewall.',
    })

    new cdk.CfnOutput(this, 'Firewall Subnet 1 ID', {
      value: NetworkFirewallSubnet1.ref,
      description: 'Subnet 1 associated with Network Firewall.',
    })

    new cdk.CfnOutput(this, 'Firewall Subnet 2 ID', {
      value: NetworkFirewallSubnet2.ref,
      description: 'Subnet 2 associated with Network Firewall.',
    })

    new cdk.CfnOutput(this, 'Transit Gateway Subnet 1 ID', {
      value: vpcTGWSubnet1.ref,
      description: 'Subnet 1 associated with Transit Gateway.',
    })

    new cdk.CfnOutput(this, 'Transit Gateway Subnet 2 ID', {
      value: vpcTGWSubnet2.ref,
      description: 'Subnet 1 associated with Transit Gateway.',
    })

    new cdk.CfnOutput(this, 'Network Firewall Availability Zone 1', {
      value: cdk.Fn.getAtt(
        'NetworkFirewallSubnet1',
        'AvailabilityZone').toString(),
      description: 'Availability Zone configured for Network Firewall subnet 1',
    })

    new cdk.CfnOutput(this, 'Network Firewall Availability Zone 2', {
      value: cdk.Fn.getAtt(
        'NetworkFirewallSubnet2',
        'AvailabilityZone').toString(),
      description: 'Availability Zone configured for Network Firewall subnet 2',
    })

    new cdk.CfnOutput(this, 'Artifact Bucket for CodePipeline', {
      value: codePipeline.artifactBucket.bucketName,
      description: 'Artifact bucket name configured for the CodePipeline.',
    })

    new cdk.CfnOutput(this, 'Code Build source code bucket', {
      value: codeBuildStagesSourceCodeBucket.bucketName,
      description: 'Code Build source code bucket',
    })

    new cdk.CfnOutput(this, 'S3 Bucket for Firewall Logs', {
      value: cdk.Fn.conditionIf('LoggingInS3', logsBucket.bucketName, 'NotConfigured').toString(),
      description: 'S3 Bucket used as the log destination for Firewall' +
        ' Logs.',
    })

    new cdk.CfnOutput(this, 'CloudWatch Log Group for Firewall Logs', {
      value: cdk.Fn.conditionIf('LoggingInCloudWatch', cloudWatchLogGroup.ref, 'NotConfigured').toString(),
      description: 'CloudWatch Log Group used as the log destination for Firewall' +
        ' Logs.',
    })

  }