constructor()

in source/lib/limit-monitor-stack.ts [36:880]


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

    new cdk.CfnParameter(this, 'SNSEmail', {
      description: 'The email address to subscribe for SNS limit alert messages, leave blank if SNS alerts not needed.',
      type: "String"
    });

    const accountList = new cdk.CfnParameter(this, 'AccountList', {
      description: 'List of comma-separated and double-quoted account numbers to monitor. If you leave this parameter blank, ' +
        'the solution will only monitor limits in the primary account. If you enter multiple secondary account IDs, you must also provide the primary account ID in this parameter.',
      type: "String",
      allowedPattern: '^"\\d{12}"(,"\\d{12}")*$|(^\\s*)$'
    });

    const snsEvents = new cdk.CfnParameter(this, 'SNSEvents', {
      description: 'List of alert levels to send email notifications. Must be double-quoted and comma separated. To disable email notifications, leave this blank.',
      type: "String",
      default: '"WARN","ERROR"'
    });

    const slackEvents = new cdk.CfnParameter(this, 'SlackEvents', {
      description: 'List of alert levels to send Slack notifications. Must be double-quoted and comma separated. To disable slack notifications, leave this blank.',
      type: "String",
      default: '"WARN","ERROR"'
    });

    new cdk.CfnParameter(this, 'SlackHookURL', {
      description: 'SSM parameter key for incoming Slack web hook URL. Leave blank if you do not wish to receive Slack notifications.',
      type: "String"
    });

    new cdk.CfnParameter(this, 'SlackChannel', {
      description: 'SSM parameter key for the Slack channel. Leave blank if you do not wish to receive Slack notifications.',
      type: "String"
    });

    const metricsMapping = new cdk.CfnMapping(this, "MetricsMap")
    metricsMapping.setValue("Send-Data", "SendAnonymousData", "Yes")

    const refreshRateMapping = new cdk.CfnMapping(this, "RefreshRate")
    refreshRateMapping.setValue("CronSchedule", "Default", "rate(1 day)")

    const sourceCodeMapping = new cdk.CfnMapping(this, "SourceCode")
    sourceCodeMapping.setValue("General", "S3Bucket", props.solutionBucket)
    sourceCodeMapping.setValue("General", "KeyPrefix", props.solutionName + '/' + props.solutionVersion)
    sourceCodeMapping.setValue("General", "TemplateBucket", props.solutionTemplateBucket)

    const eventsMapping = new cdk.CfnMapping(this, "EventsMap")
    eventsMapping.setValue("Checks", "Services", '"AutoScaling","CloudFormation","DynamoDB","EBS","EC2","ELB","IAM","Kinesis","RDS","Route53","SES","VPC"')

    new cdk.CfnCondition(this, 'SingleAccnt', {
      expression: cdk.Fn.conditionEquals('', accountList),
    });

    new cdk.CfnCondition(this, 'SNSTrue', {
      expression: cdk.Fn.conditionNot(cdk.Fn.conditionEquals('', snsEvents)),
    });

    const slackTrue = new cdk.CfnCondition(this, 'SlackTrue', {
      expression: cdk.Fn.conditionNot(cdk.Fn.conditionEquals('', slackEvents)),
    });

    new cdk.CfnCondition(this, 'AnonymousMetric', {
      expression: cdk.Fn.conditionEquals('Yes', cdk.Fn.findInMap('MetricsMap', 'Send-Data', 'SendAnonymousData')),
    });

    const slackNotifierPolicyName = 'Limit-Monitor-Policy-' + cdk.Aws.STACK_NAME + '-' + cdk.Aws.REGION

    const cwLogsPS = new iam.PolicyStatement({
      actions: ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
      effect: Effect.ALLOW,
      resources: [`arn:${cdk.Aws.PARTITION}:logs:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:log-group:/aws/lambda/*`],
      sid: 'default'
    })

    const slackNotifierLambdaRole = new iam.Role(this, 'SlackNotifierRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      path: '/',
      inlinePolicies: {
        [slackNotifierPolicyName]: new iam.PolicyDocument({
          statements: [
            cwLogsPS,
            new iam.PolicyStatement({
              actions: ['ssm:GetParameter'],
              effect: Effect.ALLOW,
              resources: [`arn:${cdk.Aws.PARTITION}:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:*`]
            })
          ]
        })
      }
    });

    const cfn_nag_w11 = {
      "cfn_nag": {
        "rules_to_suppress": [
          {
            "id": "W11",
            "reason": "Override the IAM role to allow support:* for logs:PutLogEvents resource on its permissions policy"
          }
        ]
      }
    }

    const slackNotifierLambdaRole_cfn_ref = slackNotifierLambdaRole.node.defaultChild as iam.CfnRole
    slackNotifierLambdaRole_cfn_ref.overrideLogicalId('SlackNotifierRole')
    slackNotifierLambdaRole_cfn_ref.addOverride('Condition', 'SlackTrue')
    slackNotifierLambdaRole_cfn_ref.cfnOptions.metadata = cfn_nag_w11

    const solutionSourceCodeBucket = s3.Bucket.fromBucketAttributes(this, 'SolutionSourceCodeBucket', {
      bucketName: cdk.Fn.findInMap('SourceCode', 'General', 'S3Bucket') + '-' + cdk.Aws.REGION
    });

    const slackNotifierLambdaSourceCodeS3Key = cdk.Fn.findInMap('SourceCode', "General", "KeyPrefix") + '/' + 'limtr-slack-service.zip'

    const taSlackEventsRuleToLambdaProps: EventsRuleToLambdaProps = {
      lambdaFunctionProps: {
        description: 'Serverless Limit Monitor - Lambda function to send notifications on slack',
        environment: {
          SLACK_HOOK: cdk.Fn.sub('SlackHookURL'),
          SLACK_CHANNEL: cdk.Fn.sub('SlackChannel'),
          LOG_LEVEL: 'INFO'
        },
        runtime: lambda.Runtime.NODEJS_12_X,
        code: lambda.Code.fromBucket(solutionSourceCodeBucket, slackNotifierLambdaSourceCodeS3Key),
        handler: 'index.handler',
        role: slackNotifierLambdaRole,
        timeout: cdk.Duration.seconds(300)
      },
      eventRuleProps: {
        description: 'Limit Monitor Solution - Rule for TA Slack events',
        schedule: events.Schedule.expression('rate(24 hours)'),
        enabled: true
      }
    };

    const taSlackEventRuleToLambdaConstruct = new EventsRuleToLambda(this, 'TASlackEventRule', taSlackEventsRuleToLambdaProps);

    const cfn_nag_w89_w92 = {
      cfn_nag: {
        rules_to_suppress: [
          {
            id: "W89",
            reason: "Not a valid use case to deploy in VPC",
          },
          {
            id: "W92",
            reason: "ReservedConcurrentExecutions not needed",
          }
        ],
      },
    };
    const lambdaSlackNotifier_cfn_ref = taSlackEventRuleToLambdaConstruct.lambdaFunction.node.defaultChild as lambda.CfnFunction
    lambdaSlackNotifier_cfn_ref.overrideLogicalId('SlackNotifier')
    lambdaSlackNotifier_cfn_ref.addOverride('Condition', 'SlackTrue')

    const lambdaSlackNotifier_cfn_permission = taSlackEventRuleToLambdaConstruct.lambdaFunction.permissionsNode.tryFindChild('LambdaInvokePermission') as lambda.CfnPermission
    if (lambdaSlackNotifier_cfn_permission != undefined) {
      lambdaSlackNotifier_cfn_permission.overrideLogicalId('SlackNotifierInvokePermission')
      lambdaSlackNotifier_cfn_permission.addOverride('Condition', 'SlackTrue')
    }
    lambdaSlackNotifier_cfn_ref.cfnOptions.metadata = cfn_nag_w89_w92

    const taslackrule_cfn_ref = taSlackEventRuleToLambdaConstruct.eventsRule.node.defaultChild as events.CfnRule
    taslackrule_cfn_ref.overrideLogicalId('TASlackRule')
    taslackrule_cfn_ref.addOverride('Condition', 'SlackTrue')
    taslackrule_cfn_ref.addOverride('Properties.Targets.0.Id', 'LimitMonitorSlackTarget')
    taslackrule_cfn_ref.addOverride('Properties.EventPattern', {
      "Fn::Join": [
        "",
        [
          "{\"account\":[",
          {
            "Fn::If": [
              "SingleAccnt",
              {
                "Fn::Join": [
                  "",
                  [
                    "\"",
                    {
                      "Ref": "AWS::AccountId"
                    },
                    "\""
                  ]
                ]
              },
              {
                "Ref": "AccountList"
              }
            ]
          },
          "],",
          "\"source\":[\"aws.trustedadvisor\", \"limit-monitor-solution\"],",
          "\"detail-type\":[\"Trusted Advisor Check Item Refresh Notification\", \"Limit Monitor Checks\"],",
          "\"detail\":{",
          "\"status\":[",
          {
            "Ref": "SlackEvents"
          },
          "],",
          "\"check-item-detail\":{",
          "\"Service\":[",
          {
            "Fn::FindInMap": [
              "EventsMap",
              "Checks",
              "Services"
            ]
          },
          "]",
          "}",
          "}",
          "}"
        ]
      ]
    })
    taslackrule_cfn_ref.addPropertyDeletionOverride('ScheduleExpression')

    /*
    * Create cloudformation resources for email notifications
    * [key policy, kms key, key alias, sns topic, cw event rule]
    */
    const LimitMonitorEncrypKeyPS = new iam.PolicyStatement({
      actions: [
        "kms:Encrypt",
        "kms:Decrypt"
      ],
      sid: 'default',
      resources: ['*'],
      effect: Effect.ALLOW,
      principals: [new ArnPrincipal(`arn:${this.partition}:iam::${this.account}:root`)]
    });

    const limitMonitorEncrypKeyPD = new iam.PolicyDocument()
    limitMonitorEncrypKeyPD.addStatements(LimitMonitorEncrypKeyPS)

    const limitMonitorEncryptionKey = new kms.Key(this, "LimitMonitorEncryptionKey", {
      description: 'Key for SNS and SQS',
      enabled: true,
      enableKeyRotation: true,
      policy: limitMonitorEncrypKeyPD
    })

    /*
    * Create cloudformation resources for TA SQS Event Rule to SQS using aws-events-rule-sqs construct pattern
    */
    const taSQSRuleEventsRuleToSqsProps: EventsRuleToSqsProps = {
      queueProps: {
        encryption: sqs.QueueEncryption.KMS,
        encryptionMasterKey: limitMonitorEncryptionKey,
        visibilityTimeout: cdk.Duration.seconds(60),
        retentionPeriod: cdk.Duration.seconds(86400) //1 day retention
      },
      deadLetterQueueProps: {
        encryption: sqs.QueueEncryption.KMS,
        encryptionMasterKey: limitMonitorEncryptionKey,
        retentionPeriod: cdk.Duration.seconds(604800)  //7 day retention
      },
      deployDeadLetterQueue: true,
      enableEncryptionWithCustomerManagedKey: false,
      maxReceiveCount: 3,
      eventRuleProps: {
        description: 'Limit Monitor Solution - Rule for TA SQS events',
        schedule: events.Schedule.expression('rate(24 hours)'),
        enabled: true
      }
    };

    const taSQSRuleEventsRuleToSqsConstruct = new EventsRuleToSqs(this, 'TASQSRule', taSQSRuleEventsRuleToSqsProps);

    const taevent_queue_cfn_ref = taSQSRuleEventsRuleToSqsConstruct.sqsQueue.node.defaultChild as sqs.CfnQueue
    taevent_queue_cfn_ref.overrideLogicalId('EventQueue')

    if (taSQSRuleEventsRuleToSqsConstruct.deadLetterQueue != undefined) {
      const taevent_deadletterqueue_cfn_ref = taSQSRuleEventsRuleToSqsConstruct.deadLetterQueue.queue.node.defaultChild as sqs.CfnQueue
      taevent_deadletterqueue_cfn_ref.overrideLogicalId('DeadLetterQueue')
    }

    const tasqsrule_cfn_ref = taSQSRuleEventsRuleToSqsConstruct.eventsRule.node.defaultChild as events.CfnRule
    tasqsrule_cfn_ref.overrideLogicalId('TASQSRule')
    tasqsrule_cfn_ref.addOverride('Properties.EventPattern', {
      "Fn::Join": [
        "",
        [
          "{\"account\":[",
          {
            "Fn::If": [
              "SingleAccnt",
              {
                "Fn::Join": [
                  "",
                  [
                    "\"",
                    {
                      "Ref": "AWS::AccountId"
                    },
                    "\""
                  ]
                ]
              },
              {
                "Ref": "AccountList"
              }
            ]
          },
          "],",
          "\"source\":[\"aws.trustedadvisor\", \"limit-monitor-solution\"],",
          "\"detail-type\":[\"Trusted Advisor Check Item Refresh Notification\", \"Limit Monitor Checks\"],",
          "\"detail\":{",
          "\"status\":[",
          "\"OK\",\"WARN\",\"ERROR\"",
          "],",
          "\"check-item-detail\":{",
          "\"Service\":[",
          {
            "Fn::FindInMap": [
              "EventsMap",
              "Checks",
              "Services"
            ]
          },
          "]",
          "}",
          "}",
          "}"
        ]
      ]
    })
    tasqsrule_cfn_ref.addPropertyDeletionOverride('ScheduleExpression')
    tasqsrule_cfn_ref.addOverride('Properties.Targets.0.Id', 'LimitMonitorSQSTarget')

    //Start QueuePollSchedule event rule to Limit Summarizer Lambda construct
    const limitSummarizerRoleSQSPS = new iam.PolicyStatement({
      actions: ["sqs:DeleteMessage", "sqs:ReceiveMessage"],
      effect: Effect.ALLOW,
      resources: [taSQSRuleEventsRuleToSqsConstruct.sqsQueue.queueArn]
    })

    const limitSummarizerRoleDynamoDBPS = new iam.PolicyStatement({
      actions: ["dynamodb:GetItem", "dynamodb:PutItem"],
      effect: Effect.ALLOW,
      resources: [`arn:${cdk.Aws.PARTITION}:dynamodb:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:table/*`]
    })

    const limitSummarizerRoleKMSPS = new iam.PolicyStatement({
      actions: ["kms:GenerateDataKey*", "kms:Decrypt", "kms:Encrypt"],
      resources: [limitMonitorEncryptionKey.keyArn],
      effect: Effect.ALLOW
    })

    const limitSummarizerPolicyName = 'Limit-Monitor-Policy-' + cdk.Aws.STACK_NAME + '-' + cdk.Aws.REGION

    const limitSummarizerLambdaRole = new iam.Role(this, 'LimitSummarizerRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      path: '/',
      inlinePolicies: {
        [limitSummarizerPolicyName]: new iam.PolicyDocument({
          statements: [
            cwLogsPS,
            limitSummarizerRoleSQSPS,
            limitSummarizerRoleDynamoDBPS,
            limitSummarizerRoleKMSPS
          ]
        })
      }
    });

    const limitSummarizerLambdaRole_cfn_ref = limitSummarizerLambdaRole.node.defaultChild as iam.CfnRole
    limitSummarizerLambdaRole_cfn_ref.overrideLogicalId('LimitSummarizerRole')
    limitSummarizerLambdaRole_cfn_ref.cfnOptions.metadata = cfn_nag_w11

    const limitSummarizerLambdaSourceCodeS3Key = cdk.Fn.join("/", [cdk.Fn.findInMap('SourceCode', 'General', 'KeyPrefix'), 'limtr-report-service.zip'])

    const sendAnonymousData = cdk.Fn.findInMap('MetricsMap', 'Send-Data', 'SendAnonymousData')

    const queuePollScheduleEventsRuleToLambdaProps: EventsRuleToLambdaProps = {
      lambdaFunctionProps: {
        description: 'Serverless Limit Monitor - Lambda function to summarize service limit usage',
        environment: {
          //LIMIT_REPORT_TBL: limitSummarizerLambdaToDynamoDb.dynamoTable.tableName,
          SQS_URL: taSQSRuleEventsRuleToSqsConstruct.sqsQueue.queueUrl,
          MAX_MESSAGES: '10', //100 messages can be read with each invocation, change as needed
          MAX_LOOPS: '10',
          ANONYMOUS_DATA: sendAnonymousData,
          SOLUTION: props.solutionId, //'SO0005'
          LOG_LEVEL: 'INFO'  //change to WARN, ERROR or DEBUG as needed
        },
        runtime: lambda.Runtime.NODEJS_12_X,
        code: lambda.Code.fromBucket(solutionSourceCodeBucket, limitSummarizerLambdaSourceCodeS3Key),
        handler: 'index.handler',
        role: limitSummarizerLambdaRole,
        timeout: cdk.Duration.seconds(300)
      },
      eventRuleProps: {
        description: 'Limit Monitor Solution - Schedule to poll SQS queue',
        schedule: events.Schedule.expression('rate(5 minutes)'),
        enabled: true
      }
    };

    const queuePollScheduleEventsRuleToLambdaConstruct = new EventsRuleToLambda(this, 'QueuePollSchedule', queuePollScheduleEventsRuleToLambdaProps);

    const lambdaLimitSummarizer_cfn_ref = queuePollScheduleEventsRuleToLambdaConstruct.lambdaFunction.node.defaultChild as lambda.CfnFunction
    lambdaLimitSummarizer_cfn_ref.cfnOptions.metadata = cfn_nag_w89_w92
    lambdaLimitSummarizer_cfn_ref.overrideLogicalId('LimitSummarizer')

    const lambdaLimitSummarizer_cfn_permission = queuePollScheduleEventsRuleToLambdaConstruct.lambdaFunction.permissionsNode.tryFindChild('LambdaInvokePermission') as lambda.CfnPermission
    if (lambdaLimitSummarizer_cfn_permission != undefined) {
      lambdaLimitSummarizer_cfn_permission.overrideLogicalId('SummarizerInvokePermission')
    }

    const queuepollschedulerule_cfn_ref = queuePollScheduleEventsRuleToLambdaConstruct.eventsRule.node.defaultChild as events.CfnRule
    queuepollschedulerule_cfn_ref.overrideLogicalId('QueuePollSchedule')

    // Start creating TARefresher (Trust Advisor Refresher) Cloudformation Resources
    // TA Refresher policy documents
    const taRefresherRoleSupportPS = new iam.PolicyStatement({
      effect: Effect.ALLOW,
      actions: ['support:*'],
      resources: ['*']
    })

    const taRefresherRoleServiceQuotasPS = new iam.PolicyStatement({
      effect: Effect.ALLOW,
      actions: ['servicequotas:GetAWSDefaultServiceQuota'],
      resources: ['*']
    })

    const taRefresherPolicyName = 'Limit-Monitor-Refresher-Policy-' + cdk.Aws.STACK_NAME

    // TA Refresher iam role
    const taRefresherLambdaRole = new iam.Role(this, 'TARefresherRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      path: '/',
      inlinePolicies: {
        [taRefresherPolicyName]: new iam.PolicyDocument({
          statements: [
            cwLogsPS,
            taRefresherRoleSupportPS,
            taRefresherRoleServiceQuotasPS
          ]
        })
      }
    });

    const cfn_nag_f3_w11 = {
      "cfn_nag": {
        "rules_to_suppress": [
          {
            "id": "F3",
            "reason": "Override the IAM role to allow support:* resource on its permissions policy"
          },
          {
            "id": "W11",
            "reason": "Override the IAM role to allow Resource:* for logs:PutLogEvents, resource on its permissions policy"
          }
        ]
      }
    }

    const taRefresherLambdaRole_cfn_ref = taRefresherLambdaRole.node.defaultChild as iam.CfnRole
    taRefresherLambdaRole_cfn_ref.overrideLogicalId('TARefresherRole')
    taRefresherLambdaRole_cfn_ref.cfnOptions.metadata = cfn_nag_f3_w11

    const taRefresherLambdaSourceCodeS3Key = cdk.Fn.join("/", [cdk.Fn.findInMap('SourceCode', 'General', 'KeyPrefix'), 'limtr-refresh-service.zip'])

    const refreshRateCronSchedule = cdk.Fn.findInMap('RefreshRate', 'CronSchedule', 'Default')

    // TA Refresher event rule to lambda construct
    const taRefresherEventsRuleToLambdaProps: EventsRuleToLambdaProps = {
      lambdaFunctionProps: {
        description: 'Serverless Limit Monitor - Lambda function to summarize service limits',
        environment: {
          AWS_SERVICES: cdk.Fn.findInMap('EventsMap', 'Checks', 'Services'),
          LOG_LEVEL: 'INFO'  //change to WARN, ERROR or DEBUG as needed
        },
        runtime: lambda.Runtime.NODEJS_12_X,
        code: lambda.Code.fromBucket(solutionSourceCodeBucket, taRefresherLambdaSourceCodeS3Key),
        handler: 'index.handler',
        role: taRefresherLambdaRole,
        timeout: cdk.Duration.seconds(300)
      },
      eventRuleProps: {
        description: 'Limit Monitor Solution - Schedule to refresh TA checks',
        schedule: events.Schedule.expression(refreshRateCronSchedule),
        enabled: true
      }
    };

    const taRefresherEventsRuleToLambdaConstruct = new EventsRuleToLambda(this, 'TARefreshSchedule', taRefresherEventsRuleToLambdaProps);

    const lambdaTaRefresher_cfn_ref = taRefresherEventsRuleToLambdaConstruct.lambdaFunction.node.defaultChild as lambda.CfnFunction
    lambdaTaRefresher_cfn_ref.cfnOptions.metadata = cfn_nag_w89_w92
    lambdaTaRefresher_cfn_ref.overrideLogicalId('TARefresher')

    const lambdaTaRefresher_cfn_permission = taRefresherEventsRuleToLambdaConstruct.lambdaFunction.permissionsNode.tryFindChild('LambdaInvokePermission') as lambda.CfnPermission
    if (lambdaTaRefresher_cfn_permission != undefined) {
      lambdaTaRefresher_cfn_permission.overrideLogicalId('TARefresherInvokePermission')
    }

    const taRefresherrule_cfn_ref = taRefresherEventsRuleToLambdaConstruct.eventsRule.node.defaultChild as events.CfnRule
    taRefresherrule_cfn_ref.overrideLogicalId('TARefreshSchedule')

    //Start aws-lambda-dynamoDB construct reference. 
    const limitSummarizerLambdaToDynamoDBProps: LambdaToDynamoDBProps = {
      existingLambdaObj: queuePollScheduleEventsRuleToLambdaConstruct.lambdaFunction,
      dynamoTableProps: {
        partitionKey: {
          name: 'MessageId',
          type: dynamodb.AttributeType.STRING
        },
        sortKey: {
          name: 'TimeStamp',
          type: dynamodb.AttributeType.STRING
        },
        billingMode: dynamodb.BillingMode.PROVISIONED,
        readCapacity: 2,
        writeCapacity: 2,
        timeToLiveAttribute: 'ExpiryTime'
      },
      tablePermissions: "ReadWrite"
    };

    const limitSummarizerLambdaToDynamoDb = new LambdaToDynamoDB(this, 'SummaryDDB', limitSummarizerLambdaToDynamoDBProps);

    const summaryDDB_cfn_ref = limitSummarizerLambdaToDynamoDb.dynamoTable.node.defaultChild as dynamodb.CfnTable
    summaryDDB_cfn_ref.overrideLogicalId('SummaryDDB')
    summaryDDB_cfn_ref.addPropertyOverride("SSESpecification", {
      "SSEEnabled": true
    })

    queuePollScheduleEventsRuleToLambdaConstruct.lambdaFunction.addEnvironment("LIMIT_REPORT_TBL", limitSummarizerLambdaToDynamoDb.dynamoTable.tableName)

    /*
    *Start LimtrHelper lambda function
    */
    const limtrHelperRoleEventsPS = new iam.PolicyStatement()
    limtrHelperRoleEventsPS.effect = iam.Effect.ALLOW
    limtrHelperRoleEventsPS.addActions("events:PutPermission", "events:RemovePermission")
    limtrHelperRoleEventsPS.addResources(`arn:${cdk.Aws.PARTITION}:events:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:event-bus/default`)

    const limtrHelperRoleSSMPS = new iam.PolicyStatement()
    limtrHelperRoleSSMPS.effect = iam.Effect.ALLOW
    limtrHelperRoleSSMPS.addActions("ssm:GetParameters", "ssm:PutParameter")
    limtrHelperRoleSSMPS.addResources(`arn:${cdk.Aws.PARTITION}:ssm:${cdk.Aws.REGION}:${cdk.Aws.ACCOUNT_ID}:parameter/*`)

    const limtrHelperPolicy = new iam.PolicyDocument();
    limtrHelperPolicy.addStatements(cwLogsPS)
    limtrHelperPolicy.addStatements(limtrHelperRoleEventsPS)
    limtrHelperPolicy.addStatements(limtrHelperRoleSSMPS)

    const limtrHelperPolicyPolicyName = 'Custom_Limtr_Helper_Permissions'

    const limtrHelperLambdaRole = new iam.Role(this, 'LimtrHelperRole', {
      assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
      path: '/',
      inlinePolicies: {
        [limtrHelperPolicyPolicyName]: limtrHelperPolicy
      }
    });

    const limtrHelperLambdaRole_cfn_ref = limtrHelperLambdaRole.node.defaultChild as iam.CfnRole
    limtrHelperLambdaRole_cfn_ref.overrideLogicalId('LimtrHelperRole')
    limtrHelperLambdaRole_cfn_ref.cfnOptions.metadata = cfn_nag_w11

    const LimtrHelperLambdaSourceCodeS3Key = cdk.Fn.join("/", [cdk.Fn.findInMap('SourceCode', 'General', 'KeyPrefix'), 'limtr-helper-service.zip'])

    const LimtrHelperFunction = new lambda.Function(this, 'LimtrHelperFunction', {
      description: 'This function generates UUID, establishes cross account trust on CloudWatch Event Bus and sends anonymous metric',
      environment: {
        LOG_LEVEL: 'INFO'
      },
      runtime: lambda.Runtime.NODEJS_12_X,
      code: lambda.Code.fromBucket(solutionSourceCodeBucket, LimtrHelperLambdaSourceCodeS3Key),
      handler: 'index.handler',
      role: limtrHelperLambdaRole,
      timeout: cdk.Duration.seconds(300)
    })

    const limtrhelperlambda_cfn_ref = LimtrHelperFunction.node.defaultChild as lambda.CfnFunction
    limtrhelperlambda_cfn_ref.cfnOptions.metadata = cfn_nag_w89_w92
    limtrhelperlambda_cfn_ref.overrideLogicalId('LimtrHelperFunction')



    const lm_encrypkey_cfn_ref = limitMonitorEncryptionKey.node.defaultChild as cdk.CfnResource
    lm_encrypkey_cfn_ref.overrideLogicalId('LimitMonitorEncryptionKey')

    const limitMonitorEncryptionKeyAlias = new kms.Alias(this, "LimitMonitorEncryptionKeyAlias", {
      aliasName: "alias/limit-monitor-encryption-key",
      targetKey: limitMonitorEncryptionKey
    })

    const lm_encrypkeyalias_cfn_ref = limitMonitorEncryptionKeyAlias.node.defaultChild as cdk.CfnResource
    lm_encrypkeyalias_cfn_ref.overrideLogicalId('LimitMonitorEncryptionKeyAlias')

    const snsTopic = new sns.Topic(this, 'SNSTopic', {
      masterKey: limitMonitorEncryptionKey
    })

    const sns_topic_cfn_ref = snsTopic.node.defaultChild as sns.CfnTopic
    sns_topic_cfn_ref.overrideLogicalId('SNSTopic')
    sns_topic_cfn_ref.addOverride('Condition', 'SNSTrue')
    sns_topic_cfn_ref.addOverride('Properties.Subscription', [{
      "Protocol": "email",
      "Endpoint": {
        "Fn::Sub": "${SNSEmail}"
      }
    }])


    /*
    * Use aws-events-rule-sns construct
    */
    const taSNSEventsRuleToSnsProps: EventsRuleToSnsProps = {
      existingTopicObj: snsTopic,
      enableEncryptionWithCustomerManagedKey: false,
      eventRuleProps: {
        description: 'Limit Monitor Solution - Rule for TA SNS events',
        enabled: true,
        schedule: events.Schedule.expression('rate(24 hours)'),
      },
      encryptionKey: limitMonitorEncryptionKey
    };

    const taSNSEventsRuleToSnsTopicConstruct = new EventsRuleToSns(this, 'TASNSRule', taSNSEventsRuleToSnsProps);

    const tasnsrule_cfn_ref = taSNSEventsRuleToSnsTopicConstruct.eventsRule.node.defaultChild as events.CfnRule
    tasnsrule_cfn_ref.overrideLogicalId('TASNSRule')
    tasnsrule_cfn_ref.addOverride('Condition', 'SlackTrue')
    //add additional details to the event rule target created by default by the construct
    tasnsrule_cfn_ref.addOverride('Properties.Targets.0.Id', 'LimitMonitorSNSTarget')
    tasnsrule_cfn_ref.addOverride('Properties.Targets.0.InputTransformer', {
      "InputPathsMap": {
        "limitdetails": "$.detail.check-item-detail",
        "time": "$.time",
        "account": "$.account"
      },
      "InputTemplate": "\"AWS-Account : <account> || Timestamp : <time> || Limit-Details : <limitdetails>\""
    })
    tasnsrule_cfn_ref.addOverride("Properties.EventPattern", {
      "Fn::Join": [
        "",
        [
          "{\"account\":[",
          {
            "Fn::If": [
              "SingleAccnt",
              {
                "Fn::Join": [
                  "",
                  [
                    "\"",
                    {
                      "Ref": "AWS::AccountId"
                    },
                    "\""
                  ]
                ]
              },
              {
                "Ref": "AccountList"
              }
            ]
          },
          "],",
          "\"source\":[\"aws.trustedadvisor\", \"limit-monitor-solution\"],",
          "\"detail-type\":[\"Trusted Advisor Check Item Refresh Notification\", \"Limit Monitor Checks\"],",
          "\"detail\":{",
          "\"status\":[",
          {
            "Ref": "SNSEvents"
          },
          "],",
          "\"check-item-detail\":{",
          "\"Service\":[",
          {
            "Fn::FindInMap": [
              "EventsMap",
              "Checks",
              "Services"
            ]
          },
          "]",
          "}",
          "}",
          "}"
        ]
      ]
    })
    tasnsrule_cfn_ref.addPropertyDeletionOverride('ScheduleExpression')

    /*
    * Custom resource: CreateUUID
    */
    const createUUID = new cdk.CustomResource(this, 'CreateUUID', {
      resourceType: 'Custom::UUID',
      serviceToken: LimtrHelperFunction.functionArn
    })

    queuePollScheduleEventsRuleToLambdaConstruct.lambdaFunction.addEnvironment('UUID', createUUID.getAttString('UUID'))
    /*
    * Custom resource: EstablishTrust
    */
    let customServiceEstablishTrust = new cdk.CustomResource(this, 'EstablishTrust', {
      resourceType: 'Custom::CrossAccntTrust',
      serviceToken: LimtrHelperFunction.functionArn
    })
    const custom_service_establishtrust_cfn_ref = customServiceEstablishTrust.node.defaultChild as cdk.CfnCustomResource
    custom_service_establishtrust_cfn_ref.addPropertyOverride('SUB_ACCOUNTS', accountList.valueAsString)

    /*
    * Custom resource: SSMParameter
    */
    let customServiceSSMParameter = new cdk.CustomResource(this, 'SSMParameter', {
      resourceType: 'Custom::SSMParameter',
      serviceToken: LimtrHelperFunction.functionArn
    })
    const custom_service_ssmparameter = customServiceSSMParameter.node.defaultChild as cdk.CfnCustomResource
    custom_service_ssmparameter.addOverride('Condition', 'SlackTrue')
    custom_service_ssmparameter.addPropertyOverride('SLACK_HOOK_KEY', cdk.Fn.sub('SlackHookURL'))
    custom_service_ssmparameter.addPropertyOverride('SLACK_CHANNEL_KEY', cdk.Fn.sub('SlackChannel'))

    /*
    * Custom resource: AccountAnonymousData
    */
    let customServiceAcctAnonymousData = new cdk.CustomResource(this, 'AccountAnonymousData', {
      resourceType: 'Custom::AnonymousData',
      serviceToken: LimtrHelperFunction.functionArn
    })
    const custom_service_acct_anonymous_Data = customServiceAcctAnonymousData.node.defaultChild as cdk.CfnCustomResource
    custom_service_acct_anonymous_Data.addOverride('Condition', 'AnonymousMetric'),
      custom_service_acct_anonymous_Data.addPropertyOverride('SOLUTION', 'SO0005')
    custom_service_acct_anonymous_Data.addPropertyOverride('UUID', createUUID.getAttString('UUID'))
    custom_service_acct_anonymous_Data.addPropertyOverride('SNS_EVENTS', [cdk.Fn.conditionIf('SNSTrue', 'true', 'false')])
    custom_service_acct_anonymous_Data.addPropertyOverride('SLACK_EVENTS', [cdk.Fn.conditionIf('SlackTrue', 'true', 'false')])
    custom_service_acct_anonymous_Data.addPropertyOverride('SUB_ACCOUNTS', accountList.valueAsString)
    custom_service_acct_anonymous_Data.addPropertyOverride('VERSION', props.solutionVersion)
    custom_service_acct_anonymous_Data.addPropertyOverride('TA_REFRESH_RATE', refreshRateCronSchedule)

    /*
    * Custom resource: DeploymentData
    */
    let customServiceDeploymentData = new cdk.CustomResource(this, 'DeploymentData', {
      resourceType: 'Custom::DeploymentData',
      serviceToken: LimtrHelperFunction.functionArn
    })
    const custom_service_deployment_data = customServiceDeploymentData.node.defaultChild as cdk.CfnCustomResource
    custom_service_deployment_data.addPropertyOverride('SOLUTION', 'SO0005')
    custom_service_deployment_data.addPropertyOverride('UUID', createUUID.getAttString('UUID'))
    custom_service_deployment_data.addPropertyOverride('VERSION', props.solutionVersion)
    custom_service_deployment_data.addPropertyOverride('ANONYMOUS_DATA', sendAnonymousData)

    /*
    * CFN Stack: invoke limit check cfn stack 
    */
    const templateS3Bucket = cdk.Fn.findInMap("SourceCode", "General", "TemplateBucket")
    const limitCheckStackS3Key = cdk.Fn.findInMap("SourceCode", "General", "KeyPrefix") + "/service-quotas-checks.template"
    new cdk.CfnStack(this, "limitCheckStack", {
      templateUrl: "https://s3.amazonaws.com/" + templateS3Bucket + "/" + limitCheckStackS3Key
    })

    /*
    * CFN Outputs
    */
    new CfnOutput(this, 'ServiceChecks', {
      value: cdk.Fn.findInMap('EventsMap', 'Checks', 'Services'),
      description: 'Service limits monitored in the account'
    })

    new CfnOutput(this, 'Accounts', {
      value: accountList.valueAsString,
      description: 'Accounts to be monitored for service limits'
    })

    new CfnOutput(this, 'SlackChannelKey', {
      condition: slackTrue,
      value: cdk.Fn.sub('SlackChannel'),
      description: 'SSM parameter for Slack Channel, change the value for your slack workspace'
    })

    new CfnOutput(this, 'SlackHookKey', {
      condition: slackTrue,
      value: cdk.Fn.sub('SlackHookURL'),
      description: 'SSM parameter for Slack Web Hook, change the value for your slack workspace'
    })

    new CfnOutput(this, 'UUID', {
      value: createUUID.getAttString('UUID'),
      description: 'UUID for the deployment'
    })

    const stack = cdk.Stack.of(this)

    stack.templateOptions.metadata = {
      "AWS::CloudFormation::Interface": {
        "ParameterGroups": [
          {
            "Label": {
              "default": "Account Configuration"
            },
            "Parameters": [
              "AccountList"
            ]
          },
          {
            "Label": {
              "default": "Notification Configuration"
            },
            "Parameters": [
              "SNSEvents",
              "SNSEmail",
              "SlackEvents",
              "SlackHookURL",
              "SlackChannel"
            ]
          }
        ],
        "ParameterLabels": {
          "SNSEmail": {
            "default": "Email Address"
          },
          "AccountList": {
            "default": "Account List"
          },
          "SNSEvents": {
            "default": "Email Notification Level"
          },
          "SlackEvents": {
            "default": "Slack Notification Level"
          },
          "SlackHookURL": {
            "default": "Slack Hook Url Key Name"
          },
          "SlackChannel": {
            "default": "Slack Channel Key Name"
          }
        }
      }
    }

    stack.templateOptions.templateFormatVersion = "2010-09-09"
  }
}