constructor()

in packages/@aws-c2a/cdk-pipelines-step/lib/private/change-analysis-check.ts [72:202]


  constructor(scope: Construct, id: string, props: ChangeAnalysisCheckProps) {
    super(scope, id);

    Tags.of(props.codePipeline).add('CHANGE_ANALYSIS', 'ALLOW_APPROVE', {
      includeResourceTypes: ['AWS::CodePipeline::Pipeline'],
    });

    const { preApproveLambda, invokeLambda } = new PreApproveLambda(this, 'C2APreApproveLambda', {
      pipelineTag: 'CHANGE_ANALYSIS',
    });
    this.preApproveLambda = preApproveLambda;

    const { accessKeySecret, bucket, putObject, signObject} = new WebAppBucket(this, 'C2ABucket', {
      autoDeleteObjects: props.autoDeleteObjects,
    });
    this.bucket = bucket;

    // eslint-disable-next-line @typescript-eslint/no-require-imports,@typescript-eslint/no-var-requires
    const myVersion = require(path.resolve(
      __dirname,
      '..',
      '..',
      'package.json',
    )).version;

    const message = [
      'An upcoming change would violate configured rules settings in $PIPELINE_NAME.',
      'Review and approve the changes in CodePipeline to proceed with the deployment.',
      '',
      'Review the changes in CodeBuild:',
      '',
      '$LINK',
      '',
      'Approve the changes in CodePipeline (stage $STAGE_NAME, action $ACTION_NAME):',
      '',
      '$PIPELINE_LINK',
    ];

    const publishNotification =
      'aws sns publish' +
      ' --topic-arn $NOTIFICATION_ARN' +
      ' --subject "$NOTIFICATION_SUBJECT"' +
      ` --message "${message.join('\n')}"`;

    const diff =
      'aws-c2a diff' +
      ` --app "assembly-${props.codePipeline.stack.stackName}-$STAGE_NAME/"` +
      ' ${BROADENING_PERMISSIONS:+--broadening-permissions}' +
      ' ${RULE_SET:+--rules-path "$RULE_SET"}' +
      ' --fail';

    this.c2aDiffProject = new codebuild.Project(this, 'CDKChangeAnalysis', {
      environmentVariables: {
        DOWNLOAD_USER_KEY: {
          type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER,
          value: `${accessKeySecret.secretArn}:AWS_ACCESS_KEY_ID`,
        },
        DOWNLOAD_USER_SECRET: {
          type: codebuild.BuildEnvironmentVariableType.SECRETS_MANAGER,
          value: `${accessKeySecret.secretArn}:AWS_SECRET_ACCESS_KEY`,
        },
      },
      buildSpec: codebuild.BuildSpec.fromObject({
        version: 0.2,
        phases: {
          build: {
            commands: [
              'set -e',
              // We have to pull this down separately because the synth stage
              // value of the asset hash might be different than the deploy stage
              // hash value
              '[ -z "${RULE_SET}" ] || aws s3 cp s3://$BUCKET/$RULE_SET $RULE_SET',
              `npm install -g aws-c2a@${myVersion}`,
              // $CODEBUILD_INITIATOR will always be Code Pipeline and in the form of:
              // "codepipeline/example-pipeline-name-Xxx"
              'export PIPELINE_NAME="$(node -pe \'`${process.env.CODEBUILD_INITIATOR}`.split("/")[1]\')"',
              'payload="$(node -pe \'JSON.stringify({ "PipelineName": process.env.PIPELINE_NAME, "StageName": process.env.STAGE_NAME, "ActionName": process.env.ACTION_NAME })\' )"',
              // ARN: "arn:aws:codebuild:$region:$account_id:build/$project_name:$project_execution_id$"
              'ARN=$CODEBUILD_BUILD_ARN',
              'REGION="$(node -pe \'`${process.env.ARN}`.split(":")[3]\')"',
              'ACCOUNT_ID="$(node -pe \'`${process.env.ARN}`.split(":")[4]\')"',
              'PROJECT_NAME="$(node -pe \'`${process.env.ARN}`.split(":")[5].split("/")[1]\')"',
              'PROJECT_ID="$(node -pe \'`${process.env.ARN}`.split(":")[6]\')"',
              'export PIPELINE_LINK="https://$REGION.console.aws.amazon.com/codesuite/codepipeline/pipelines/$PIPELINE_NAME/view?region=$REGION"',
              // Run invoke only if cdk diff passes (returns exit code 0)
              // 0 -> true, 1 -> false
              ifElse({
                condition: diff,
                thenStatements: [
                  invokeLambda,
                  'export MESSAGE="No changes that violate rules detected."',
                ],
                elseStatements: [
                  'aws-c2a html --report report.json',
                  putObject,
                  `export LINK=$(${signObject})`,
                  `[ -z "\${NOTIFICATION_ARN}" ] || ${publishNotification}`,
                  'export MESSAGE="Deployment would make security-impacting changes. Click the link below to inspect them, then click Approve if all changes are expected."',
                ],
              }),
            ],
          },
        },
        env: {
          'exported-variables': [
            'LINK',
            'MESSAGE',
          ],
        },
      }),
    });

    // this is needed to check the status the stacks when doing `cdk diff`
    this.c2aDiffProject.addToRolePolicy(new iam.PolicyStatement({
      actions: ['sts:AssumeRole'],
      resources: ['*'],
      conditions: {
        'ForAnyValue:StringEquals': {
          'iam:ResourceTag/aws-cdk:bootstrap-role': ['deploy'],
        },
      },
    }));

    this.c2aDiffProject.addToRolePolicy(new iam.PolicyStatement({
      actions: ['cloudformation:GetTemplate', 'cloudformation:DescribeStackResources', 'cloudformation:DescribeStacks'],
      resources: ['*'],
    }));

    this.preApproveLambda.grantInvoke(this.c2aDiffProject);
    this.bucket.grantWrite(this.c2aDiffProject);
  }