constructor()

in lib/tenant/oidc-provider-pipeline-stack.ts [23:252]


  constructor(app: App, id: string, props: PipelineStackProps) {
    super(app, id, props);

    const pipelineName = 'Hybrid-SaaS-Identity_OidcProvider_CI-CD_pipeline';

    const code = codecommit.Repository.fromRepositoryName(this, 'ImportedRepo',
      props.repoName);

    const cdkBuild = new codebuild.PipelineProject(this, 'CdkBuild', {
      buildSpec: codebuild.BuildSpec.fromObject({
        version: '0.2',
        phases: {
          install: {
            commands: ['chmod +x ./scripts/bootstrap/postinstall.sh', 'npm install --unsafe-perm', 'yum install -y jq'],
          },
          build: {
            commands: [
              'echo About to retrieve pipeline execution id',
              "pipelineexecutionid=$(aws codepipeline get-pipeline-state --name ${CODEBUILD_INITIATOR#codepipeline/} --query 'stageStates[?actionStates[?latestExecution.externalExecutionId==`'${CODEBUILD_BUILD_ID}'`]].latestExecution.pipelineExecutionId' --output text)",
              'echo pipelineexecutionid is ${pipelineexecutionid}',
              'dynamodbtablename=$(aws ssm get-parameter --name "/mysaasapp/${pipelineexecutionid}/dynamodbtablename" --output text --query Parameter.Value || "oidc-provider")',
              'echo dynamodbtablename is ${dynamodbtablename}',
              'loglevel=$(aws ssm get-parameter --name "/mysaasapp/${pipelineexecutionid}/loglevel" --output text --query Parameter.Value || "ERROR")',
              'echo loglevel is ${loglevel}',
              'tenantuuid=$(aws ssm get-parameter --name "/mysaasapp/${pipelineexecutionid}/tenantuuid" --output text --query Parameter.Value)',
              'echo tenantuuid is ${tenantuuid}',
              'vpcid=$(aws ssm get-parameter --name "/mysaasapp/${pipelineexecutionid}/vpcid" --output text --query Parameter.Value || true)',
              'echo vpcid is ${vpcid}',
              'subnet1=$(aws ssm get-parameter --name "/mysaasapp/${pipelineexecutionid}/subnet1" --output text --query Parameter.Value || true)',
              'echo subnet1 is ${subnet1}',
              'subnet2=$(aws ssm get-parameter --name "/mysaasapp/${pipelineexecutionid}/subnet2" --output text --query Parameter.Value || true)',
              'echo subnet2 is ${subnet2}',
              'securitygroup1=$(aws ssm get-parameter --name "/mysaasapp/${pipelineexecutionid}/securityGroup1" --output text --query Parameter.Value || true)',
              'echo securitygroup1 is ${securitygroup1}',
              'securitygroup2=$(aws ssm get-parameter --name "/mysaasapp/${pipelineexecutionid}/securityGroup2" --output text --query Parameter.Value || true)',
              'echo securitygroup2 is ${securitygroup2}',
              'npm run build',
              'if [[ ${vpcid} == "" ]]; then echo "Going to synth a non VPC oidc-provider" && npm run cdk synth HSI--Pipeline--OidcProvider -- -a "npx ts-node bin/oidcproviderpipeline.ts" -c tenantuuid=${tenantuuid} -c codecommitrepo=${codecommitrepo} -c dynamodbTableName=${dynamodbtablename} -c logLevel=${loglevel} -c pipelineexecutionid=${pipelineexecutionid} -o dist; fi',
              'if [[ ${vpcid} != "" ]]; then echo "Going to synth a VPC oidc-provider" && npm run cdk synth HSI--Pipeline--OidcProvider -- -a "npx ts-node bin/oidcproviderpipeline.ts" -c tenantuuid=${tenantuuid} -c subnet1=${subnet1} -c subnet2=${subnet2} -c securityGroup1=${securitygroup1} -c securityGroup2=${securitygroup2} -c dynamodbTableName=${dynamodbtablename} -c vpcid=${vpcid} -c logLevel=${loglevel} -c pipelineexecutionid=${pipelineexecutionid} -o dist; fi',
              'ls -lR dist',
            ],
          },
        },
        artifacts: {
          'base-directory': 'dist',
          files: [
            'OidcProviderStack.template.json',
          ],
        },
        env: {
          'git-credential-helper': 'yes',
          'parameter-store': {
            codecommitrepo: '/mysaasapp/codecommitrepo',

          },
          'exported-variables': ['tenantuuid'],
        },
      }),
      environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_3,
        computeType: codebuild.ComputeType.MEDIUM,
        privileged: true,
      },
      environmentVariables:
      {
        CDK_DEFAULT_ACCOUNT: {
          type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
          value: this.account,
        },
        CDK_DEFAULT_REGION: {
          type: codebuild.BuildEnvironmentVariableType.PLAINTEXT,
          value: this.region,
        },
      },
    });
    cdkBuild.addToRolePolicy(new iam.PolicyStatement({
      resources: [`arn:aws:ssm:${this.region}:${this.account}:parameter/mysaasapp/*`],
      actions: ['ssm:GetParameter*'],
    }));

    cdkBuild.addToRolePolicy(new iam.PolicyStatement({
      resources: [`arn:aws:codepipeline:${this.region}:${this.account}:${pipelineName}`],
      actions: ['codepipeline:GetPipelineState'],
    }));

    const lambdaBuild = new codebuild.PipelineProject(this, 'LambdaBuild', {
      buildSpec: codebuild.BuildSpec.fromObject({
        version: '0.2',
        phases: {
          install: {
            commands: [
              'cd resources/oidc-provider',
              'npm install',
            ],
          },
        },
        artifacts: {
          'base-directory': 'resources/oidc-provider',
          files: [
            '**/*',
          ],
        },
      }),
      environment: {
        buildImage: codebuild.LinuxBuildImage.STANDARD_2_0,
      },
    });

    const sourceOutput = new codepipeline.Artifact();
    const cdkBuildOutput = new codepipeline.Artifact('CdkBuildOutput');
    const lambdaBuildOutput = new codepipeline.Artifact('LambdaBuildOutput');
    const finishOidcProviderPipelineFn = new nodejslambda.NodejsFunction(this, 'FinishOidcProviderPipelineFunction', {
      entry: `${path.join(path.resolve(__dirname, '..', '..'), 'resources', 'finish_oidc_provider_pipeline_lambda')}/handler.js`,
      handler: 'handler',
      timeout: Duration.seconds(900), // +acm validation wait of 530 seconds
      memorySize: 3008,
    });
    // Tenant Federation Lambda Policy to insert/read tenant specific ssm parameters
    finishOidcProviderPipelineFn.addToRolePolicy(new iam.PolicyStatement({
      resources: [`arn:aws:ssm:${this.region}:${this.account}:parameter/mysaasapp/*`],
      actions: ['ssm:GetParameter*', 'ssm:PutParameter*'],
    }));
    // Tenant Federation Lambda Policy to start oidcprovider codepipeline
    finishOidcProviderPipelineFn.addToRolePolicy(new iam.PolicyStatement({
      resources: [`arn:aws:states:${this.region}:${this.account}:stateMachine:*TenantFederationStateMachine*`],
      actions: ['states:SendTaskSuccess'],
    }));

    finishOidcProviderPipelineFn.addToRolePolicy(new iam.PolicyStatement({
      resources: [`arn:aws:codepipeline:${this.region}:${this.account}:${pipelineName}`],
      actions: ['codepipeline:PutJob*'],
    }));

    const sourceAction = new codepipelineActions.CodeCommitSourceAction({
      actionName: 'CodeCommit_Source',
      repository: code,
      branch: 'main',
      output: sourceOutput,
      trigger: CodeCommitTrigger.NONE,
    });
    const lambdaBuildAction = new codepipelineActions.CodeBuildAction({
      actionName: 'Lambda_Build',
      project: lambdaBuild,
      input: sourceOutput,
      outputs: [lambdaBuildOutput],
    });
    const cdkBuildAction = new codepipelineActions.CodeBuildAction({
      actionName: 'CDK_Build',
      variablesNamespace: 'cdkbuild',
      project: cdkBuild,
      input: sourceOutput,
      outputs: [cdkBuildOutput],
    });
    const oidcProviderPipelineDeployActionRole = new iam.Role(this, 'oidcProviderPipelineDeployActionRole', { assumedBy: new iam.ServicePrincipal('cloudformation.amazonaws.com') });
    oidcProviderPipelineDeployActionRole.addToPolicy(new iam.PolicyStatement({
      resources: ['*'],
      actions: ['*'],
    }));
    const oidcProviderPipelineActionRole = new iam.Role(this, 'oidcProviderPipelineActionRole', { assumedBy: new iam.AccountPrincipal(this.account) });
    oidcProviderPipelineActionRole.addToPolicy(new iam.PolicyStatement({
      resources: ['*'],
      actions: ['*'],
    }));
    const oidcProviderDeployAction = new codepipelineActions.CloudFormationCreateUpdateStackAction({
      actionName: 'Oidc_Provider_CFN_Deploy',
      role: oidcProviderPipelineActionRole,
      deploymentRole: oidcProviderPipelineDeployActionRole,
      templatePath: cdkBuildOutput.atPath('OidcProviderStack.template.json'),
      stackName: `OidcProviderDeploymentStack-${cdkBuildAction.variable('tenantuuid')}`,
      adminPermissions: true,
      parameterOverrides: {
        ...props.lambdaCode.assign(lambdaBuildOutput.s3Location),
      },
      extraInputs: [lambdaBuildOutput],
    });
    const comopletionLambdaInvokeAction = new codepipelineActions.LambdaInvokeAction({
      actionName: 'InvokeAction',
      userParameters: {
      // Parameters for the lambda function
        executionid: '#{codepipeline.PipelineExecutionId}',
      },
      lambda: finishOidcProviderPipelineFn,
    });
    const pipe = new codepipeline.Pipeline(this, 'Pipeline', {
      pipelineName,
      stages: [
        {
          stageName: 'Source',
          actions: [sourceAction],
        },
        {
          stageName: 'Build',
          actions: [lambdaBuildAction, cdkBuildAction],
        },
        {
          stageName: 'Deploy',
          actions: [oidcProviderDeployAction],
        },
        {
          stageName: 'InvokeLambda',
          actions: [comopletionLambdaInvokeAction],
        },
      ],
    });
    pipe.addToRolePolicy(new iam.PolicyStatement({
      resources: [`arn:aws:cloudformation:${this.region}:${this.account}:stack/OidcProviderDeploymentStack*`],
      actions: ['cloudformation:*'],
    }));
    oidcProviderDeployAction.addToDeploymentRolePolicy(new iam.PolicyStatement({
      resources: [`arn:aws:cloudformation:${this.region}:${this.account}:stack/OidcProviderDeploymentStack*`],
      actions: ['cloudformation:*'],
    }));
    pipe.artifactBucket.grantRead(oidcProviderPipelineDeployActionRole);
    comopletionLambdaInvokeAction.actionProperties.role?.attachInlinePolicy(new iam.Policy(this, 'somefoo', {
      statements: [
        new iam.PolicyStatement({
          resources: [`arn:aws:ssm:${this.region}:${this.account}:parameter/*`],
          actions: ['ssm:GetParameter*', 'ssm:PutParameter*'],
        }),
        new iam.PolicyStatement({
          resources: [`arn:aws:states:${this.region}:${this.account}:stateMachine:*TenantFederationStateMachine*`],
          actions: ['states:SendTaskSuccess', 'states:SendTaskFailure'],
        }),
        new iam.PolicyStatement({
          resources: [`arn:aws:codepipeline:${this.region}:${this.account}:${pipelineName}`],
          actions: ['codepipeline:PutJob*'],
        }),
      ],
    }));
  }