AWSTemplateFormatVersion: 2010-09-09 Description: AWS Control Tower customization that adds in New Relic integration to your landing zone.(qs-1rsuloqd4) Metadata: QuickStartDocumentation: EntrypointName: "Parameters for deploying New Relic AWS Control Tower integration" AWS::CloudFormation::Interface: ParameterGroups: - Label: default: "New Relic configuration" Parameters: - NewRelicAccountNumber - NewRelicAccessKey - NerdGraphEndpoint - NewRelicLicenseKey - NewRelicDatacenter - Label: default: Delivery Stream configuration Parameters: - CloudWatchMetricsStreamingTemplateURL - CloudWatchMetricStreamName - FirehoseStreamName - S3BackupBucketName - Label: default: AWS Config details Parameters: - CreateConfigService - S3ConfigBucketName - Label: default: "Deployment configuration" Parameters: - LaunchAccountList - StackSetName - StackSetUrl - Label: default: "AWS Quick Start configuration" Parameters: - QSS3BucketName - QSS3KeyPrefix ParameterLabels: NewRelicLicenseKey: default: >- The license key associated with the account you wish to export metrics to NewRelicDatacenter: default: >- Identification of the New Relic datacenter your metrics are exported to CloudWatchMetricsStreamingTemplateURL: default: Template URL of Cloudwatch Metrics Streaming Resources CloudWatchMetricStreamName: default: Name of new CloudWatch Metric Stream FirehoseStreamName: default: Name of new Kinesis Firehose Delivery Stream S3BackupBucketName: default: Name of new S3 Bucket - Destination for failed events CreateConfigService: default: Enable and configure AWS Config to track changes in resources S3ConfigBucketName: default: Name of new S3 Bucket - Destination for delivery channel configuration NewRelicAccountNumber: default: New Relic account ID NewRelicAccessKey: default: New Relic NerdGraph User key NerdGraphEndpoint: default: New Relic NerdGraph API endpoint LaunchAccountList: default: Existing AWS account ID list StackSetName: default: StackSet name. StackSetUrl: default: StackSet template URL. QSS3BucketName: default: Quick Start S3 bucket name. QSS3KeyPrefix: default: Quick Start S3 key prefix. Parameters: NewRelicLicenseKey: Type: String NoEcho: true Description: The license key associated with the account you wish to export metrics to AllowedPattern: '^[A-Za-z0-9]{40}$' ConstraintDescription: New Relic ingestKey is 40-character Hexadecimal String NewRelicDatacenter: Type: String Description: New Relic datacenter your metrics are exported to Default: US AllowedValues: - US - EU CloudWatchMetricsStreamingTemplateURL: Type: String Default: >- https://aws-quickstart.s3.amazonaws.com/quickstart-ct-newrelic-one/templates/MetricStreams_CloudFormation.yml Description: S3 Url of cloudwatch metric stream template CloudWatchMetricStreamName: Type: String Default: CT-NewRelic-CloudWatchMetricStream Description: >- Name of new CloudWatch Metric Stream(must be unique per AWS account in the same AWS Region) FirehoseStreamName: Type: String Default: CT-NewRelic-FirehoseStream Description: >- Name of new Kinesis Firehose Delivery Stream (must be unique per AWS account in the same AWS Region) S3BackupBucketName: Type: String Default: quickstart-ct-newrelic-one-default Description: >- Name of new S3 Bucket Destination for failed events (must be globally unique across all AWS accounts in all AWS Regions within a partition) CreateConfigService: Type: String Description: >- Enable and configure AWS Config to track resource changes. This allows you a complete monitoring New Relic experience Default: 'true' AllowedValues: - 'true' - 'false' S3ConfigBucketName: Type: String Default: quickstart-ct-newrelic-one Description: >- Name of new S3 Bucket Destination for delivery channel configuration (must be globally unique across all AWS accounts in all AWS Regions within a partition) NewRelicAccountNumber: Type: String NoEcho: true Description: New Relic account ID. See https://docs.newrelic.com/docs/accounts/accounts-billing/account-setup/account-id/ AllowedPattern: '[0-9]+' ConstraintDescription: New Relic account ID contains only numbers LaunchAccountList: Type: String Description: Comma separated string of existing (enrolled with Control Tower) AWS account IDs that you wish to monitor with New Relic. See https://docs.aws.amazon.com/general/latest/gr/acct-identifiers.html AllowedPattern: '^$|^(([0-9]){12},)*(([0-9]){12})$' ConstraintDescription: LaunchAccountList must be either empty or a comma separated string of AWS account IDs (12 digit number) with no spaces NewRelicAccessKey: Type: String NoEcho: true AllowedPattern: '^([A-Z0-9-]){32}$' ConstraintDescription: New Relic User key is 32 characters long, and contains only numbers and letters Description: New Relic NerdGraph User key. See https://docs.newrelic.com/docs/apis/intro-apis/new-relic-api-keys/#user-api-key NerdGraphEndpoint: Type: String Description: New Relic NerdGraph endpoint URL. Use default unless your New Relic account uses an EU data center. See https://docs.newrelic.com/docs/apis/nerdgraph/get-started/introduction-new-relic-nerdgraph/#authentication Default: 'https://api.newrelic.com/graphql' AllowedValues: - 'https://api.newrelic.com/graphql' - 'https://api.eu.newrelic.com/graphiql' StackSetName: Type: String Description: New Relic integration StackSet name Default: NewRelic-Integration StackSetUrl: Type: String Default: https://aws-quickstart.s3.amazonaws.com/quickstart-ct-newrelic-one/templates/newrelic-stack-set.yml Description: New Relic integration StackSet template URL QSS3BucketName: Type: String Default: aws-quickstart Description: "S3 bucket for Quick Start assets. Use this if you want to customize your deployment. The bucket name can include numbers, lowercase letters, uppercase letters, and hyphens, but it cannot start or end with hyphens (-)." QSS3KeyPrefix: Type: String Default: quickstart-ct-newrelic-one/ Description: "S3 key prefix to simulate a directory for Quick Start assets. Use this if you want to customize your deployment. The prefix can include numbers, lowercase letters, uppercase letters, hyphens (-), and forward slashes (/). For more information, see https://docs.aws.amazon.com/AmazonS3/latest/dev/UsingMetadata.html." Resources: LambdaZipsBucket: Type: AWS::S3::Bucket CopyZips: Type: Custom::CopyZips Properties: ServiceToken: !GetAtt 'CopyZipsFunction.Arn' DestBucket: !Ref 'LambdaZipsBucket' SourceBucket: !Ref 'QSS3BucketName' Prefix: !Ref 'QSS3KeyPrefix' Objects: - 'functions/packages/onboarding/NewRelicCTOnboarding.zip' - 'functions/packages/register/NewRelicCTRegister.zip' - 'functions/packages/stackset/NewRelicCTStackSet.zip' CopyZipsRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' Path: / Policies: - PolicyName: lambda-copier PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - s3:GetObject - s3:GetObjectTagging Resource: - !Sub 'arn:${AWS::Partition}:s3:::${QSS3BucketName}/${QSS3KeyPrefix}*' - Effect: Allow Action: - s3:PutObject - s3:DeleteObject - s3:PutObjectTagging Resource: - !Sub 'arn:${AWS::Partition}:s3:::${LambdaZipsBucket}/${QSS3KeyPrefix}*' CopyZipsFunction: Type: AWS::Lambda::Function Properties: Description: Copies objects from the S3 bucket to a new location. Handler: index.handler Runtime: python3.7 Role: !GetAtt 'CopyZipsRole.Arn' Timeout: 240 Code: ZipFile: | import json import logging import threading import boto3 import cfnresponse def copy_objects(source_bucket, dest_bucket, prefix, objects): s3 = boto3.client('s3') for o in objects: key = prefix + o copy_source = { 'Bucket': source_bucket, 'Key': key } print('copy_source: %s' % copy_source) print('dest_bucket = %s'%dest_bucket) print('key = %s' %key) s3.copy_object(CopySource=copy_source, Bucket=dest_bucket, Key=key) def delete_objects(bucket, prefix, objects): s3 = boto3.client('s3') objects = {'Objects': [{'Key': prefix + o} for o in objects]} s3.delete_objects(Bucket=bucket, Delete=objects) def timeout(event, context): logging.error('Execution is about to time out, sending failure response to CloudFormation') cfnresponse.send(event, context, cfnresponse.FAILED, {}, None) def handler(event, context): # make sure we send a failure to CloudFormation if the function # is going to timeout timer = threading.Timer((context.get_remaining_time_in_millis() / 1000.00) - 0.5, timeout, args=[event, context]) timer.start() print('Received event: %s' % json.dumps(event)) status = cfnresponse.SUCCESS try: source_bucket = event['ResourceProperties']['SourceBucket'] dest_bucket = event['ResourceProperties']['DestBucket'] prefix = event['ResourceProperties']['Prefix'] objects = event['ResourceProperties']['Objects'] if event['RequestType'] == 'Delete': delete_objects(dest_bucket, prefix, objects) else: copy_objects(source_bucket, dest_bucket, prefix, objects) except Exception as e: logging.error('Exception: %s' % e, exc_info=True) status = cfnresponse.FAILED finally: timer.cancel() cfnresponse.send(event, context, status, {}, None) NewRelicOnboardingFunction: Type: AWS::Lambda::Function DependsOn: CopyZips Properties: Code: S3Bucket: !Ref LambdaZipsBucket S3Key: !Join ['', [!Ref 'QSS3KeyPrefix', 'functions/packages/onboarding/NewRelicCTOnboarding.zip']] Handler: onboarding.lambda_handler Runtime: python3.7 Timeout: 120 Environment: Variables: stackSetName: !Ref StackSetName newRelicAccId: !Ref NewRelicAccountNumber seedAccounts: !Ref LaunchAccountList stackSetUrl: !Ref StackSetUrl newRelicSecret: !Ref NewRelicCredentials newRelicStackSNS: !Ref NewRelicStackSNS S3ConfigBucketName: !Ref S3ConfigBucketName CreateConfigService: !Ref CreateConfigService S3BackupBucketName: !Ref S3BackupBucketName FirehoseStreamName: !Ref FirehoseStreamName CloudWatchMetricStreamName: !Ref CloudWatchMetricStreamName CloudWatchMetricsStreamingTemplateURL: !Ref CloudWatchMetricsStreamingTemplateURL NewRelicDatacenter: !Ref NewRelicDatacenter NewRelicLicenseKey: !Ref NewRelicLicenseKey Role: !GetAtt NewRelicOnboardingFunctionRole.Arn NewRelicOnboardingFunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: Onboarding_Operations PolicyDocument: Version: 2012-10-17 Statement: - Sid: StackSetInstanceCreate Effect: Allow Action: - cloudformation:CreateStackInstances - cloudformation:ListStackInstances Resource: !Join ['', ['arn:aws:cloudformation:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':stackset/', !Ref StackSetName, '*' ]] - Sid: StackSetInstanceDelete Effect: Allow Action: - cloudformation:DeleteStackSet - cloudformation:DeleteStackInstances - cloudformation:DescribeStackSetOperation Resource: !Join ['', ['arn:aws:cloudformation:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':stackset/', !Ref StackSetName, '*' ]] - Sid: StackSetCreate Effect: Allow Action: - cloudformation:CreateStackSet - cloudformation:DescribeStackSet Resource: !Join ['', ['arn:aws:cloudformation:', '*', ':', '*', ':stackset/NewRelic-*' ]] - Sid: S3Ops Effect: Allow Action: - s3:ListBucket - s3:GetObject Resource: - !Join ['',['arn:aws:s3:::', !Ref QSS3BucketName, '/', !Ref QSS3KeyPrefix, '*']] - Sid: SNSOps Effect: Allow Action: - sns:Publish Resource: !Ref NewRelicStackSNS - Sid: PassRole Effect: Allow Action: - iam:PassRole Resource: !Join [':', ['arn:aws:iam:', !Ref 'AWS::AccountId', 'role/service-role/AWSControlTowerStackSetRole' ]] ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' NewRelicCredentials: Type: AWS::SecretsManager::Secret Properties: Description: NewRelic API Credentials Name: NewRelicAPICredential SecretString: Fn::Join: - '' - - '{"AccessKey":"' - Ref: NewRelicAccessKey - '"}' NewRelicFirstLaunch: Type: AWS::CloudFormation::CustomResource DependsOn: - NewRelicStackSNSSubscription - NewRelicRegisterSNSSubscription Properties: ServiceToken: !GetAtt NewRelicOnboardingFunction.Arn NewRelicStackSNS: Type: AWS::SNS::Topic NewRelicStackSNSLambdaPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt NewRelicStackSetFunction.Arn Principal: sns.amazonaws.com SourceArn: !Ref NewRelicStackSNS NewRelicStackSNSSubscription: Type: AWS::SNS::Subscription Properties: Endpoint: !GetAtt NewRelicStackSetFunction.Arn Protocol: lambda TopicArn: !Ref NewRelicStackSNS NewRelicRegisterSNS: Type: AWS::SNS::Topic NewRelicRegisterSNSLambdaPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt NewRelicRegisterFunction.Arn Principal: sns.amazonaws.com SourceArn: !Ref NewRelicRegisterSNS NewRelicRegisterSNSSubscription: Type: AWS::SNS::Subscription Properties: Endpoint: !GetAtt NewRelicRegisterFunction.Arn Protocol: lambda TopicArn: !Ref NewRelicRegisterSNS NewRelicDLQ: Type: AWS::SQS::Queue Properties: MessageRetentionPeriod: 1209600 NewRelicStackSetFunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: Register_Operations PolicyDocument: Version: 2012-10-17 Statement: - Sid: StackSetInstanceOperations Effect: Allow Action: - cloudformation:CreateStackInstances - cloudformation:ListStackInstances - cloudformation:ListStackSetOperations - cloudformation:DescribeStackSetOperation - cloudformation:DeleteStackInstances Resource: !Join ['', ['arn:aws:cloudformation:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':stackset/', !Ref StackSetName, '*' ]] - Sid: StackSetOperations Effect: Allow Action: - cloudformation:DescribeStackSet Resource: !Join ['', ['arn:aws:cloudformation:', '*', ':', '*', ':stackset/NewRelic-*' ]] - Sid: SNSOps Effect: Allow Action: - sns:Publish Resource: - !Ref NewRelicStackSNS - !Ref NewRelicRegisterSNS ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' NewRelicStackSetFunction: Type: AWS::Lambda::Function DependsOn: CopyZips Properties: Code: S3Bucket: !Ref LambdaZipsBucket S3Key: !Join ['', [!Ref 'QSS3KeyPrefix', 'functions/packages/stackset/NewRelicCTStackSet.zip']] Handler: stackset.lambda_handler Runtime: python3.7 Timeout: 120 ReservedConcurrentExecutions: 1 Environment: Variables: stackSetName: !Ref StackSetName newRelicAccId: !Ref NewRelicAccountNumber stackSetUrl: !Ref StackSetUrl newRelicSecret: !Ref NewRelicCredentials newRelicStackSNS: !Ref NewRelicStackSNS newRelicRegisterSNS: !Ref NewRelicRegisterSNS Role: !GetAtt NewRelicStackSetFunctionRole.Arn NewRelicRegisterFunctionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: Register_Operations PolicyDocument: Version: 2012-10-17 Statement: - Sid: StackSetInstanceOperations Effect: Allow Action: - cloudformation:ListStackInstances - cloudformation:ListStackSetOperations - cloudformation:ListStackSetOperationResults - cloudformation:DescribeStackSetOperation Resource: !Join ['', ['arn:aws:cloudformation:', !Ref 'AWS::Region', ':', !Ref 'AWS::AccountId', ':stackset/', !Ref StackSetName, '*' ]] - Sid: StackSetOperations Effect: Allow Action: - cloudformation:DescribeStackSet Resource: !Join ['', ['arn:aws:cloudformation:', '*', ':', '*', ':stackset/NewRelic-*' ]] - Sid: SQSOps Effect: Allow Action: - sqs:SendMessage - sqs:DeleteMessage - sqs:ReceiveMessage - sqs:GetQueueAttributes Resource: - !GetAtt NewRelicDLQ.Arn - Sid: SNSOps Effect: Allow Action: - sns:Publish Resource: - !Ref NewRelicRegisterSNS - Sid: SecretRead Effect: Allow Action: - secretsmanager:GetSecretValue Resource: Ref: NewRelicCredentials ManagedPolicyArns: - !Sub 'arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole' NewRelicRegisterFunction: Type: AWS::Lambda::Function DependsOn: CopyZips Properties: Code: S3Bucket: !Ref LambdaZipsBucket S3Key: !Join ['', [!Ref 'QSS3KeyPrefix', 'functions/packages/register/NewRelicCTRegister.zip']] Handler: register.lambda_handler Runtime: python3.7 Timeout: 120 ReservedConcurrentExecutions: 5 Environment: Variables: stackSetName: !Ref StackSetName newRelicAccId: !Ref NewRelicAccountNumber stackSetUrl: !Ref StackSetUrl newRelicSecret: !Ref NewRelicCredentials newRelicRegisterSNS: !Ref NewRelicRegisterSNS newRelicDLQ: !Ref NewRelicDLQ nerdGraphEndPoint: !Ref NerdGraphEndpoint Role: !GetAtt NewRelicRegisterFunctionRole.Arn NewRelicControlTowerEvents: Type: AWS::Events::Rule Properties: Description: Captures AWS Control Tower LifeCycle events and invokes additional functions. EventPattern: detail: eventName: - CreateManagedAccount - UpdateManagedAccount eventSource: - controltower.amazonaws.com detail-type: - AWS Service Event via CloudTrail source: - aws.controltower Name: NewRelicControlTowerEvents State: ENABLED Targets: - Arn: !GetAtt "NewRelicStackSetFunction.Arn" Id: IDNewRelicControlTowerEvents NewRelicControlTowerLifeCyclePermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt "NewRelicStackSetFunction.Arn" Principal: events.amazonaws.com SourceArn: !GetAtt "NewRelicControlTowerEvents.Arn"