export async function deploy()

in src/deployments/cdk/src/apps/phase-1.ts [88:604]


export async function deploy({ acceleratorConfig, accountStacks, accounts, context, limiter, outputs }: PhaseInput) {
  const assignedVpcCidrPools = await loadAssignedVpcCidrPool(context.vpcCidrPoolAssignedTable);
  const assignedSubnetCidrPools = await loadAssignedSubnetCidrPool(context.subnetCidrPoolAssignedTable);
  const masterAccountKey = acceleratorConfig.getMandatoryAccountKey('master');
  const iamConfigs = acceleratorConfig.getIamConfigs();
  const masterAccountId = getAccountId(accounts, masterAccountKey);
  if (!masterAccountId) {
    throw new Error(`Cannot find mandatory primary account ${masterAccountKey}`);
  }
  const { acceleratorName, installerVersion, defaultRegion, acceleratorExecutionRoleName } = context;
  // Find the central bucket in the outputs
  const centralBucket = CentralBucketOutput.getBucket({
    accountStacks,
    config: acceleratorConfig,
    outputs,
  });
  console.log(centralBucket.bucketName);
  const logBucket = LogBucketOutput.getBucket({
    accountStacks,
    config: acceleratorConfig,
    outputs,
  });

  // Find the account buckets in the outputs
  const accountBuckets = await defaults.step2({
    accounts,
    accountStacks,
    centralLogBucket: logBucket,
    config: acceleratorConfig,
  });

  /**
   * Creates IAM Role in source Account and provide assume permissions to target acceleratorExecutionRole
   * @param roleName : Role Name for peering connection from source to target
   * @param sourceAccount : Source Account Key, Role will be created in this
   * @param accountKey : Target Account Key, Access will be provided to this account
   */
  const createIamRoleForPCXAcceptence = (roleName: string, sourceAccount: string) => {
    const accountStack = accountStacks.tryGetOrCreateAccountStack(sourceAccount, defaultRegion);
    if (!accountStack) {
      console.warn(`Cannot find account stack ${sourceAccount}`);
      return;
    }
    const existing = accountStack.node.tryFindChild('PeeringRole');
    if (existing) {
      return;
    }
    const targetAccounts = acceleratorConfig
      .getVpcConfigs()
      .filter(rsv => PeeringConnectionConfig.is(rsv.vpcConfig.pcx) && rsv.vpcConfig.pcx.source === sourceAccount);
    const targetAccountKeys = Array.from(new Set(targetAccounts.map(rsv => rsv.accountKey)));
    const peeringRole = new iam.Role(accountStack, 'PeeringRole', {
      roleName,
      assumedBy: new iam.CompositePrincipal(
        ...targetAccountKeys.map(
          targetAccountKey =>
            new iam.ArnPrincipal(
              `arn:aws:iam::${getAccountId(accounts, targetAccountKey)}:role/${acceleratorExecutionRoleName}`,
            ),
        ),
      ),
    });

    peeringRole.addToPrincipalPolicy(
      new iam.PolicyStatement({
        resources: ['*'],
        actions: ['ec2:AcceptVpcPeeringConnection'],
      }),
    );

    createIamRoleOutput(accountStack, peeringRole, 'PeeringConnectionAcceptRole');
  };

  // Auxiliary method to create a VPC in the account with given account key
  const createVpc = (accountKey: string, props: VpcProps): Vpc | undefined => {
    const { vpcConfig, vpcOutput } = props;

    const accountStack = accountStacks.tryGetOrCreateAccountStack(accountKey, vpcConfig.region);
    if (!accountStack) {
      console.warn(`Cannot find account stack ${accountKey}`);
      return;
    }
    const vpcStackPrettyName = pascalCase(props.vpcConfig.name);

    const vpcStack = new VpcStack(accountStack, `VpcStack${vpcStackPrettyName}`, props);
    const vpc = vpcStack.vpc;

    const subnets = vpc.azSubnets.subnets.map(s => ({
      subnetId: s.subnet.ref,
      subnetName: s.subnetName,
      az: s.az,
      cidrBlock: s.cidrBlock,
    }));
    const initialSubnets: VpcSubnetOutput[] = [];
    if (vpcOutput && vpcOutput.initialSubnets.length === 0) {
      initialSubnets.push(...vpcOutput.subnets);
    } else if (vpcOutput) {
      initialSubnets.push(...vpcOutput.initialSubnets);
    } else {
      initialSubnets.push(...subnets);
    }
    const nfwOutputs = vpc.nfw?.nfwOutput;

    // Store the VPC output so that subsequent phases can access the output
    new vpcDeployment.CfnVpcOutput(vpc, `VpcOutput`, {
      accountKey,
      region: props.vpcConfig.region,
      vpcId: vpc.vpcId,
      vpcName: props.vpcConfig.name,
      cidrBlock: vpc.cidrBlock,
      additionalCidrBlocks: vpc.additionalCidrBlocks,
      subnets,
      routeTables: vpc.routeTableNameToIdMap,
      securityGroups: Object.entries(vpc.securityGroup?.securityGroupNameMapping || {}).map(
        ([name, securityGroup]) => ({
          securityGroupId: securityGroup.ref,
          securityGroupName: name,
        }),
      ),
      tgwAttachments: vpc.tgwAVpcAttachments,
      initialSubnets,
      nfw: nfwOutputs || [],
    });

    return vpcStack.vpc;
  };

  const subscriptionCheckDone: string[] = [];
  const dnsLogGroupsAccountAndRegion: { [accoutKey: string]: boolean } = {};
  const existingAttachments = TransitGatewayAttachmentOutputFinder.findAll({
    outputs,
  });
  // Create all the VPCs for accounts and organizational units
  for (const { ouKey, accountKey, vpcConfig, deployments } of acceleratorConfig.getVpcConfigs()) {
    let createPolicy = false;
    if (!limiter.create(accountKey, Limit.VpcPerRegion, vpcConfig.region)) {
      console.log(
        `Skipping VPC "${vpcConfig.name}" deployment. Reached maximum VPCs per region for account "${accountKey}" and region "${vpcConfig.region}`,
      );
      continue;
    }

    console.debug(
      `Deploying VPC "${vpcConfig.name}" in account "${accountKey}"${
        ouKey ? ` and organizational unit "${ouKey}"` : ''
      }`,
    );

    const vpcOutput = VpcOutputFinder.tryFindOneByAccountAndRegionAndName({
      outputs,
      accountKey,
      region: vpcConfig.region,
      vpcName: vpcConfig.name,
    });

    if (vpcConfig.nfw) {
      // Try to get policy document for nfw
      const sts = new STS();
      const masterAcctCredentials = await sts.getCredentialsForAccountAndRole(
        masterAccountId,
        context.acceleratorExecutionRoleName,
      );

      const s3 = new S3(masterAcctCredentials);
      vpcConfig.nfw.policyString = await s3.getObjectBodyAsString({
        Bucket: centralBucket.bucketName,
        Key: vpcConfig.nfw.policy?.path || 'nfw/nfw-example-policy.json',
      });
    } else {
      console.log('No NFW policy path found skipping');
    }
    const ebsKmsKey = EbsKmsOutputFinder.findOneByName({ accountKey, region: vpcConfig.region, outputs });
    const vpc = createVpc(accountKey, {
      accountKey,
      accountStacks,
      limiter,
      accounts,
      vpcConfig,
      tgwDeployments: deployments?.tgw,
      organizationalUnitName: ouKey,
      vpcConfigs: acceleratorConfig.getVpcConfigs(),
      outputs,
      acceleratorName,
      installerVersion,
      vpcOutput,
      vpcPools: assignedVpcCidrPools,
      subnetPools: assignedSubnetCidrPools,
      existingAttachments,
      ddbKmsKey: ebsKmsKey?.encryptionKeyArn,
      acceleratorPrefix: context.acceleratorPrefix,
      logBucket,
    });

    const pcxConfig = vpcConfig.pcx;
    if (PeeringConnectionConfig.is(pcxConfig)) {
      const sourceVpcConfig = acceleratorConfig
        .getVpcConfigs()
        .find(x => x.accountKey === pcxConfig.source && x.vpcConfig.name === pcxConfig['source-vpc']);
      if (!sourceVpcConfig) {
        console.warn(`Cannot find PCX source VPC ${pcxConfig['source-vpc']} in account ${pcxConfig.source}`);
      } else {
        // Create Accepter Role for Peering Connection **WITHOUT** random suffix
        const pcxAcceptRole = IamRoleOutputFinder.tryFindOneByName({
          outputs,
          accountKey: pcxConfig.source,
          roleKey: 'PeeringConnectionAcceptRole',
        });
        let roleName = createRoleName(`VPC-PCX-${pascalCase(accountKey)}To${pascalCase(pcxConfig.source)}`, 0);
        if (pcxAcceptRole) {
          roleName = pcxAcceptRole.roleName;
        }
        createIamRoleForPCXAcceptence(roleName, pcxConfig.source);
      }
    }

    // Validate subscription for Firewall images only once per account
    // TODO Add region to check
    // TODO Check if VPC or deployments exists
    if (!subscriptionCheckDone.includes(`${accountKey}-region-${vpcConfig.region}`)) {
      console.log(`Checking Subscription for ${accountKey}`);
      await firewallSubscription.validate({
        accountKey,
        deployments: deployments!,
        vpc: vpc!,
        accountStacks,
      });
      subscriptionCheckDone.push(`${accountKey}-region-${vpcConfig.region}`);
    }

    if (vpc) {
      // Creates resolver query logging and associate to the VPC
      await vpcDeployment.step4({
        accountKey,
        accountStacks,
        acceleratorPrefix: context.acceleratorPrefix,
        outputs,
        vpcConfig,
        vpcId: vpc.id,
      });
    }
    // Create DNS Query Logging Log Group
    if (vpcConfig.zones && vpcConfig.zones.public.length > 0) {
      if (!dnsLogGroupsAccountAndRegion[accountKey]) {
        createPolicy = true;
        dnsLogGroupsAccountAndRegion[accountKey] = true;
      }
      await centralEndpoints.createDnsQueryLogGroup({
        acceleratorPrefix: context.acceleratorPrefix,
        accountKey,
        accountStacks,
        outputs,
        vpcConfig,
        createPolicy,
      });
    }
  }

  // Create the firewall
  await firewallCluster.step2({
    accountStacks,
    config: acceleratorConfig,
    outputs,
  });

  const getIamPoliciesDefinition = async (): Promise<{ [policyName: string]: string } | undefined> => {
    const iamPoliciesDef: { [policyName: string]: string } = {};

    const sts = new STS();
    const masterAcctCredentials = await sts.getCredentialsForAccountAndRole(
      masterAccountId,
      context.acceleratorExecutionRoleName,
    );

    // TODO Remove call to S3 here somehow
    const iamPolicyS3 = new S3(masterAcctCredentials);

    const iamPolicyArtifactOutput: IamPolicyArtifactsOutput[] = getStackJsonOutput(outputs, {
      accountKey: masterAccountKey,
      outputType: 'IamPolicyArtifactsOutput',
    });

    if (iamPolicyArtifactOutput.length === 0) {
      console.warn(`Cannot find output with Iam Policy reference artifacts`);
      return;
    }

    const iamPoliciesBucketName = iamPolicyArtifactOutput[0].bucketName;
    const iamPoliciesBucketPrefix = iamPolicyArtifactOutput[0].keyPrefix + '/';

    for (const { iam: iamConfig } of iamConfigs) {
      if (IamConfigType.is(iamConfig)) {
        const iamPolicies = iamConfig?.policies;
        for (const iamPolicy of iamPolicies || []) {
          if (IamPolicyConfigType.is(iamPolicy)) {
            const iamPolicyName = iamPolicy['policy-name'];
            const iamPolicyFileName = iamPolicy.policy;
            const iamPolicyKey = `${iamPoliciesBucketPrefix}${iamPolicyFileName}`;
            try {
              const policyContent = await iamPolicyS3.getObjectBodyAsString({
                Bucket: iamPoliciesBucketName,
                Key: iamPolicyKey,
              });
              iamPoliciesDef[iamPolicyName] = policyContent;
            } catch (e) {
              console.warn(`Cannot load IAM policy s3://${iamPoliciesBucketName}/${iamPolicyKey}`);
              throw e;
            }
          }
        }
      }
    }

    return iamPoliciesDef;
  };

  const getIamTrustPoliciesDefinition = async (): Promise<{ [policyName: string]: string } | undefined> => {
    const iamPoliciesDef: { [policyName: string]: string } = {};

    const sts = new STS();
    const masterAcctCredentials = await sts.getCredentialsForAccountAndRole(
      masterAccountId,
      context.acceleratorExecutionRoleName,
    );

    // TODO Remove call to S3 here somehow
    const iamPolicyS3 = new S3(masterAcctCredentials);

    const iamPolicyArtifactOutput: IamPolicyArtifactsOutput[] = getStackJsonOutput(outputs, {
      accountKey: masterAccountKey,
      outputType: 'IamPolicyArtifactsOutput',
    });

    if (iamPolicyArtifactOutput.length === 0) {
      console.warn(`Cannot find output with Iam Policy reference artifacts`);
      return;
    }

    const iamPoliciesBucketName = iamPolicyArtifactOutput[0].bucketName;
    const iamPoliciesBucketPrefix = iamPolicyArtifactOutput[0].keyPrefix + '/';

    for (const { iam: iamConfig } of iamConfigs) {
      if (IamConfigType.is(iamConfig)) {
        for (const role of iamConfig.roles || []) {
          if (role['trust-policy']) {
            const iamPolicyKey = `${iamPoliciesBucketPrefix}${role['trust-policy']}`;
            try {
              console.log(iamPoliciesBucketName, iamPolicyKey);
              const policyContent = await iamPolicyS3.getObjectBodyAsString({
                Bucket: iamPoliciesBucketName,
                Key: iamPolicyKey,
              });
              role['trust-policy'] = JSON.parse(JSON.stringify(policyContent));
            } catch (e) {
              console.warn(`Cannot load IAM policy s3://${iamPoliciesBucketName}/${iamPolicyKey}`);
              throw e;
            }
          }
        }
      }
    }
  };
  await getIamTrustPoliciesDefinition();
  const iamPoliciesDefinition = await getIamPoliciesDefinition();

  const createIamAssets = async (accountKey: string, iamConfig?: IamConfig): Promise<void> => {
    const accountStack = accountStacks.tryGetOrCreateAccountStack(accountKey);
    if (!accountStack) {
      console.warn(`Cannot find account stack ${accountKey}`);
      return;
    }

    const userPasswords: { [userId: string]: cdk.SecretValue } = {};

    const users = iamConfig?.users || [];
    const userIds = users.flatMap(u => u['user-ids']);
    for (const userId of userIds) {
      userPasswords[userId] = getIamUserPasswordSecretValue({
        acceleratorPrefix: context.acceleratorPrefix,
        accountKey,
        userId,
        secretAccountId: masterAccountId,
      });
    }

    if (iamPoliciesDefinition) {
      new IamAssets(accountStack, `IAM Assets-${pascalCase(accountKey)}`, {
        accountKey,
        iamConfig,
        iamPoliciesDefinition,
        accounts,
        userPasswords,
        logBucket,
      });
    }
  };

  const accountIamConfigs: { [accountKey: string]: IamConfig } = {};
  for (const { accountKey, iam: iamConfig } of iamConfigs) {
    if (accountIamConfigs[accountKey]) {
      if (accountIamConfigs[accountKey].policies) {
        accountIamConfigs[accountKey].policies?.push(...(iamConfig.policies || []));
      } else {
        accountIamConfigs[accountKey].policies = iamConfig.policies;
      }

      if (accountIamConfigs[accountKey].roles) {
        accountIamConfigs[accountKey].roles?.push(...(iamConfig.roles || []));
      } else {
        accountIamConfigs[accountKey].roles = iamConfig.roles;
      }

      if (accountIamConfigs[accountKey].users) {
        accountIamConfigs[accountKey].users?.push(...(iamConfig.users || []));
      } else {
        accountIamConfigs[accountKey].users = iamConfig.users;
      }
    } else {
      accountIamConfigs[accountKey] = iamConfig;
    }
  }

  // creating assets for default account settings
  for (const [accountKey, iamConfig] of Object.entries(accountIamConfigs)) {
    await createIamAssets(accountKey, iamConfig);
  }

  // Budget creation step 2
  await budget.step2({
    accountStacks,
    config: acceleratorConfig,
  });

  await certificates.step1({
    accountStacks,
    centralBucket,
    config: acceleratorConfig,
  });

  // SSM config step 1
  await ssm.step1({
    accountStacks,
    bucketName: logBucket.bucketName,
    config: acceleratorConfig,
    accounts,
    accountBuckets,
    outputs,
  });

  // Cost and usage reports step 1
  await reports.step1({
    accountBuckets,
    accountStacks,
    config: acceleratorConfig,
  });

  // Macie step 1
  await macie.step1({
    accountStacks,
    accounts,
    config: acceleratorConfig,
    outputs,
  });

  await macie.enableMaciePolicy({
    accountBuckets,
    accountStacks,
    accounts,
    config: acceleratorConfig,
    outputs,
  });

  // GuardDuty step 1
  // to use step1 need this to be fixed: https://t.corp.amazon.com/P36821200/overview
  await guardDutyDeployment.step1({
    accountStacks,
    config: acceleratorConfig,
    accounts,
    outputs,
  });

  // Central Services step 1
  await cwlCentralLoggingToS3.step1({
    accountStacks,
    accounts,
    logBucket,
    outputs,
    config: acceleratorConfig,
  });

  await vpcDeployment.step1({
    accountBuckets,
    accountStacks,
    config: acceleratorConfig,
    accounts,
  });

  await transitGateway.createPeeringAttachment({
    accountStacks,
    accounts,
    config: acceleratorConfig,
    outputs,
  });

  /**
   * DisAssociate HostedZone to VPC
   * - On Adding of InterfaceEndpoint in local VPC whose use-central-endpoint: true and Endpoint also exists in Central VPC
   */

  await centralEndpoints.step5({
    accountStacks,
    accounts,
    config: acceleratorConfig,
    outputs,
    executionRole: context.acceleratorPipelineRoleName,
    assumeRole: context.acceleratorExecutionRoleName,
  });
}