constructor()

in source/backend/lib/cdk-photosearch-web-stack.ts [37:178]


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

    const wafIPSet = new waf.CfnIPSet(this, 'WafIPSet', {
      name: 'whiteList',
      ipSetDescriptors: [
        {
          type: 'IPV4',
          value: props.webConsoleAllowedIpAddressCidr,
        },
      ],
    });

    const wafActivatedRule = new waf.CfnRule(this, 'WafRule', {
      metricName: 'whiteListRule',
      name: 'whiteListRule',
      predicates: [
        {
          dataId: wafIPSet.ref,
          negated: false,
          type: 'IPMatch',
        },
      ],
    });

    const webAcl = new waf.CfnWebACL(this, 'WebAcl', {
      name: 'WebAcl',
      metricName: 'WebAcl',
      defaultAction: {
        type: 'BLOCK',
      },
      rules: [
        {
          action: { type: 'ALLOW' },
          priority: 1,
          ruleId: wafActivatedRule.ref,
        },
      ],
    });

    const { cloudFrontWebDistribution, s3Bucket } = new cloudfrontS3.CloudFrontToS3(this, 'cloudfront-s3', {
      insertHttpSecurityHeaders: false,
      cloudFrontDistributionProps: {
        webAclId: webAcl.ref,
        errorResponses: [
          {
            httpStatus: 403,
            responseHttpStatus: 200,
            responsePagePath: '/',
            ttl: cdk.Duration.minutes(5),
          },
        ],
      },
    });

    if (!s3Bucket) throw new Error('failed to create s3 bucket for frontend assets.');

    const srcBucket = s3.Bucket.fromBucketName(this, 'SourceBucket', this.getAssetSourceBucketName());
    const lambdaRole = new iam.Role(this, 'PhotoSearchLambdaRoleFrontendEventHandler', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
    });
    lambdaRole.addManagedPolicy({
      managedPolicyArn: 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole',
    });
    s3Bucket.grantReadWrite(lambdaRole);
    s3Bucket.grantDelete(lambdaRole);
    srcBucket.grantRead(lambdaRole);
    const cfnLambdafunctionDefPolicy = lambdaRole.node
      .tryFindChild('DefaultPolicy')
      ?.node.findChild('Resource') as iam.CfnPolicy;
    cfnLambdafunctionDefPolicy.cfnOptions.metadata = {
      cfn_nag: {
        rules_to_suppress: [
          {
            id: 'W12',
            reason: 'Lambda needs the minimum required permissions to create frontend assets in S3.',
          },
        ],
      },
    };

    const frontendEventHandler = new NodejsFunction(this, 'frontend-event-handler', {
      entry: 'lambda/custom-resources/frontend-event-handler/index.ts',
      role: lambdaRole,
      handler: 'handler',
      runtime: lambda.Runtime.NODEJS_14_X,
      timeout: cdk.Duration.minutes(1),
      memorySize: 256,
      tracing: lambda.Tracing.ACTIVE,
      environment: {
        BUCKET_NAME: srcBucket.bucketName,
        SOLUTION_NAME: '%%SOLUTION_NAME%%',
        VERSION: '%%VERSION%%',
      },
    });
    const provider = new Provider(this, 'provider', {
      onEventHandler: frontendEventHandler,
    });
    const eventHandlerFunction: lambda.CfnFunction = frontendEventHandler.node.findChild(
      'Resource'
    ) as lambda.CfnFunction;
    const providerFunction: lambda.CfnFunction = provider.node
      .findChild('framework-onEvent')
      .node.findChild('Resource') as lambda.CfnFunction;
    const customResourceRules = {
      cfn_nag: {
        rules_to_suppress: [
          {
            id: 'W58',
            reason:
              'Lambda functions has the required permission to write CloudWatch Logs. It uses custom policy instead of arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole with tighter permissions.',
          },
          {
            id: 'W89',
            reason: 'This is not a rule for the general case, just for specific use cases/industries',
          },
          {
            id: 'W92',
            reason: 'Reserved capacity depends on how much users use this system.',
          },
        ],
      },
    };
    providerFunction.cfnOptions.metadata = eventHandlerFunction.cfnOptions.metadata = customResourceRules;

    new cdk.CustomResource(this, 'cdk-event-handler', {
      serviceToken: provider.serviceToken,
      properties: {
        FrontendBucketName: s3Bucket.bucketName,
        Region: cdk.Stack.of(this).region,
        AppApiId: props.apiId,
        IdentityPoolId: props.identityPoolId,
      },
    });

    this.distributionDomainName = cloudFrontWebDistribution.distributionDomainName;

    new cdk.CfnOutput(this, 'frontendBucketName', {
      description: 'Name of the bucket for frontend',
      value: s3Bucket.bucketName,
    });
  }