constructor()

in source/infrastructure/lib/api.ts [77:369]


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

        const taskArn = Stack.of(this).formatArn({ service: 'ecs', resource: 'task', sep: '/', resourceName: '*' });
        const taskDefArn = Stack.of(this).formatArn({ service: 'ecs', resource: 'task-definition/' });

        const dltApiServicesLambdaRole = new Role(this, 'DLTAPIServicesLambdaRole', {
            assumedBy: new ServicePrincipal('lambda.amazonaws.com'),
            inlinePolicies: {
                'DLTAPIServicesLambdaPolicy': new PolicyDocument({
                    statements: [
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: ['ecs:ListTasks'],
                            resources: ['*']
                        }),
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: [
                                'ecs:RunTask',
                                'ecs:DescribeTasks'
                            ],
                            resources: [
                                taskArn,
                                taskDefArn
                            ]
                        }),
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: ['iam:PassRole'],
                            resources: [props.ecsTaskExecutionRoleArn]
                        }),
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: ['states:StartExecution'],
                            resources: [props.taskRunnerStepFunctionsArn]
                        }),
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: ['logs:DeleteMetricFilter'],
                            resources: [props.ecsCloudWatchLogGroup.logGroupArn]
                        }),
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: ['cloudwatch:DeleteDashboards'],
                            resources: [`arn:${Aws.PARTITION}:cloudwatch::${Aws.ACCOUNT_ID}:dashboard/EcsLoadTesting*`]
                        })
                    ]
                })
            }
        });
        dltApiServicesLambdaRole.attachInlinePolicy(props.cloudWatchLogsPolicy);
        dltApiServicesLambdaRole.attachInlinePolicy(props.dynamoDbPolicy);
        dltApiServicesLambdaRole.attachInlinePolicy(props.scenariosS3Policy);
        dltApiServicesLambdaRole.attachInlinePolicy(props.taskCancelerInvokePolicy);

        const ruleSchedArn = Stack.of(this).formatArn({ service: 'events', resource: 'rule', resourceName: '*Scheduled' });
        const ruleCreateArn = Stack.of(this).formatArn({ service: 'events', resource: 'rule', resourceName: '*Create' });
        const ruleListArn = Stack.of(this).formatArn({ service: 'events', resource: 'rule', resourceName: '*' });

        const lambdaApiEventsPolicy = new Policy(this, 'LambdaApiEventsPolicy', {
            statements: [
                new PolicyStatement({
                    effect: Effect.ALLOW,
                    actions: [
                        'events:PutTargets',
                        'events:PutRule',
                        'events:DeleteRule',
                        'events:RemoveTargets'
                    ],
                    resources: [
                        ruleSchedArn,
                        ruleCreateArn
                    ]
                }),
                new PolicyStatement({
                    effect: Effect.ALLOW,
                    actions: [
                        'events:ListRules'
                    ],
                    resources: [
                        ruleListArn
                    ]
                })
            ]
        });
        dltApiServicesLambdaRole.attachInlinePolicy(lambdaApiEventsPolicy);

        const apiLambdaRoleResource = dltApiServicesLambdaRole.node.defaultChild as CfnResource;
        apiLambdaRoleResource.addMetadata('cfn_nag', {
            rules_to_suppress: [{
                id: 'W11',
                reason: 'ecs:ListTasks does not support resource level permissions'
            }]
        });

        const dltApiServicesLambda = new LambdaFunction(this, 'DLTAPIServicesLambda', {
            description: 'API microservices for creating, updating, listing and deleting test scenarios',
            code: Code.fromBucket(props.sourceCodeBucket, `${props.sourceCodePrefix}/api-services.zip`),
            runtime: Runtime.NODEJS_14_X,
            handler: 'index.handler',
            timeout: Duration.seconds(120),
            environment: {
                SCENARIOS_BUCKET: props.scenariosBucketName,
                SCENARIOS_TABLE: props.scenariosTableName,
                TASK_CLUSTER: props.ecsCuster,
                STATE_MACHINE_ARN: props.taskRunnerStepFunctionsArn,
                SOLUTION_ID: props.solutionId,
                UUID: props.uuid,
                VERSION: props.solutionVersion,
                SEND_METRIC: props.sendAnonymousUsage,
                METRIC_URL: props.metricsUrl,
                ECS_LOG_GROUP: props.ecsCloudWatchLogGroup.logGroupName,
                TASK_CANCELER_ARN: props.tastCancelerArn
            },
            role: dltApiServicesLambdaRole
        });
        Tags.of(dltApiServicesLambda).add('SolutionId', props.solutionId);
        const apiLambdaResource = dltApiServicesLambda.node.defaultChild as CfnResource;
        apiLambdaResource.addMetadata('cfn_nag', {
            rules_to_suppress: [{
                id: 'W58',
                reason: 'CloudWatchLogsPolicy covers a permission to write CloudWatch logs.'
            }, {
                id: 'W89',
                reason: 'VPC not needed for lambda'
            }, {
                id: 'W92',
                reason: 'Does not run concurrent executions'
            }]
        });

        const lambdaApiPermissionPolicy = new Policy(this, 'LambdaApiPermissionPolicy', {
            statements: [
                new PolicyStatement({
                    effect: Effect.ALLOW,
                    actions: [
                        'lambda:AddPermission',
                        'lambda:RemovePermission'
                    ],
                    resources: [dltApiServicesLambda.functionArn]
                })
            ]
        });
        dltApiServicesLambdaRole.attachInlinePolicy(lambdaApiPermissionPolicy);

        const apiLogs = new LogGroup(this, 'APILogs', {
            retention: RetentionDays.ONE_YEAR,
            removalPolicy: RemovalPolicy.RETAIN
        });
        const apiLogsResource = apiLogs.node.defaultChild as CfnResource;
        apiLogsResource.addMetadata('cfn_nag', {
            rules_to_suppress: [{
                id: 'W84',
                reason: 'KMS encryption unnecessary for log group'
            }]
        });

        const logsArn = Stack.of(this).formatArn({ service: 'logs', resource: '*' })
        const apiLoggingRole = new Role(this, 'APILoggingRole', {
            assumedBy: new ServicePrincipal('apigateway.amazonaws.com'),
            inlinePolicies: {
                'apiLoggingPolicy': new PolicyDocument({
                    statements: [
                        new PolicyStatement({
                            effect: Effect.ALLOW,
                            actions: [
                                'logs:CreateLogGroup',
                                'logs:CreateLogStream',
                                'logs:DescribeLogGroups',
                                'logs:DescribeLogStreams',
                                'logs:PutLogEvents',
                                'logs:GetLogEvents',
                                'logs:FilterLogEvent',
                            ],
                            resources: [
                                logsArn
                            ]
                        })
                    ]
                })
            }
        });

        const api = new RestApi(this, 'DLTApi', {
            defaultCorsPreflightOptions: {
                allowOrigins: ['*'],
                allowHeaders: [
                    'Authorization',
                    'Content-Type',
                    'X-Amz-Date',
                    'X-Amz-Security-Token',
                    'X-Api-Key'
                ],
                allowMethods: [
                    'DELETE',
                    'GET',
                    'HEAD',
                    'OPTIONS',
                    'PATCH',
                    'POST',
                    'PUT'
                ],
                statusCode: 200
            },
            deploy: true,
            deployOptions: {
                accessLogDestination: new LogGroupLogDestination(apiLogs),
                accessLogFormat: AccessLogFormat.jsonWithStandardFields(),
                loggingLevel: MethodLoggingLevel.INFO,
                stageName: 'prod',
                tracingEnabled: true
            },
            description: `Distributed Load Testing API - version ${props.solutionVersion}`,
            endpointTypes: [EndpointType.EDGE]
        });

        this.apiId = api.restApiId;
        this.apiEndpointPath = api.url.slice(0, -1);

        const apiAccountConfig = new CfnAccount(this, 'ApiAccountConfig', {
            cloudWatchRoleArn: apiLoggingRole.roleArn
        });
        apiAccountConfig.addDependsOn(api.node.defaultChild as CfnResource);

        const apiAllRequestValidator = new RequestValidator(this, 'APIAllRequestValidator', {
            restApi: api,
            validateRequestBody: true,
            validateRequestParameters: true
        });

        const apiDeployment = api.node.findChild('Deployment') as Deployment;
        const apiDeploymentResource = apiDeployment.node.defaultChild as CfnResource;
        apiDeploymentResource.addMetadata('cfn_nag', {
            rules_to_suppress: [{
                id: 'W68',
                reason: 'The solution does not require the usage plan.'
            }]
        });

        const apiFindProdResource = api.node.findChild('DeploymentStage.prod') as Stage;
        const apiProdResource = apiFindProdResource.node.defaultChild as CfnResource;
        apiProdResource.addMetadata('cfn_nag', {
            rules_to_suppress: [{
                id: 'W64',
                reason: 'The solution does not require the usage plan.'
            }]
        });

        const allIntegration = new Integration({
            type: IntegrationType.AWS_PROXY,
            integrationHttpMethod: 'POST',
            options: {
                contentHandling: ContentHandling.CONVERT_TO_TEXT,
                integrationResponses: [{ statusCode: '200' }],
                passthroughBehavior: PassthroughBehavior.WHEN_NO_MATCH,
            },
            uri: `arn:${Aws.PARTITION}:apigateway:${Aws.REGION}:lambda:path/2015-03-31/functions/${dltApiServicesLambda.functionArn}/invocations`
        });
        const allMethodOptions: MethodOptions = {
            authorizationType: AuthorizationType.IAM,
            methodResponses: [{
                statusCode: '200',
                responseModels: {
                    'application/json': { modelId: 'Empty' }
                }
            }],
            requestValidator: apiAllRequestValidator
        };

        /** Test scenario API
         * /scenarios
         * /scenarios/{testId}
         * /tasks
         */
        const scenariosResource = api.root.addResource('scenarios');
        scenariosResource.addMethod('ANY', allIntegration, allMethodOptions);

        const testIds = scenariosResource.addResource('{testId}');
        testIds.addMethod('ANY', allIntegration, allMethodOptions);

        const tasksResource = api.root.addResource('tasks');
        tasksResource.addMethod('ANY', allIntegration, allMethodOptions);


        const invokeSourceArn = Stack.of(this).formatArn({ service: 'execute-api', resource: api.restApiId, resourceName: '*' });
        dltApiServicesLambda.addPermission('DLTApiInvokePermission', {
            action: 'lambda:InvokeFunction',
            principal: new ServicePrincipal('apigateway.amazonaws.com'),
            sourceArn: invokeSourceArn
        });

    }