AWSTemplateFormatVersion: 2010-09-09 Description: This template creates the SNS subscription required for the 'Streamline AWS Control Tower multi-account environments with PagerDuty Service Ownership' blog in the AWS Control Tower Audit account. (MPCT-tnhjwbbr) Metadata: 'AWS::CloudFormation::Interface': ParameterGroups: - Label: default: SNS Subscription Configuration Parameters: - AuditTopicName - PagerDutyHttpsEndpoint ParameterLabels: AuditTopicARN: default: 'Enter the ARN of the aws-controltower-AggregateSecurityNotifications topic' PagerDutyHttpsEndpoint: default: 'Enter your PagerDuty HTTPS Endpoint' Resources: AggregateSecurityNotificationsSubscription: Type: 'AWS::SNS::Subscription' Properties: TopicArn: !Sub arn:aws:sns:${AWS::Region}:${AWS::AccountId}:${AuditTopicName} Endpoint: !Ref PagerDutyHttpsEndpoint Protocol: https SNSSubscriptionFlipExecutionRole: Type: AWS::IAM::Role Metadata: cfn_nag: rules_to_suppress: - id: W11 reason: "Only all resources are supported for the SNS APIs allowed." Properties: AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: lambda.amazonaws.com Action: sts:AssumeRole Path: / Policies: - PolicyName: SNS-subscription PolicyDocument: Version: 2012-10-17 Statement: - Effect: "Allow" Action: - "sns:SetSubscriptionAttributes" - "sns:ListTopics" - "sns:GetSubscriptionAttributes" - "sns:ListSubscriptions" Resource: "*" ManagedPolicyArns: - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole EnableRawMessageForSNSTopicLambda: Type: AWS::Lambda::Function Properties: Code: ZipFile: | import json, boto3, time, logging, os from botocore.exceptions import ClientError from botocore.vendored import requests LOGGER = logging.getLogger() LOGGER.setLevel(logging.INFO) session = boto3.Session() def cfnresponse_send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False): ''' function to signal CloudFormation custom resource ''' responseUrl = event['ResponseURL'] responseBody = {} responseBody['Status'] = responseStatus responseBody['Reason'] = 'See the details in CloudWatch Log Stream: ' + context.log_stream_name responseBody['PhysicalResourceId'] = physicalResourceId or context.log_stream_name responseBody['StackId'] = event['StackId'] responseBody['RequestId'] = event['RequestId'] responseBody['LogicalResourceId'] = event['LogicalResourceId'] responseBody['NoEcho'] = noEcho responseBody['Data'] = responseData json_responseBody = json.dumps(responseBody) headers = { 'content-type' : '', 'content-length' : str(len(json_responseBody)) } try: response = requests.put(responseUrl, data=json_responseBody, headers=headers) LOGGER.info("CFN Response Status code: " + response.reason) except Exception as e: LOGGER.info("CFN Response Failed: " + str(e)) def flip_sns_raw_messaging(sns_sub_arn, action='true'): '''Flip the raw messaging for SNS Subscription''' sns = boto3.client('sns') output = False attr_name = 'RawMessageDelivery' try: LOGGER.info('Changing %s to: %s', attr_name, action) output = sns.set_subscription_attributes(SubscriptionArn=sns_sub_arn, AttributeName=attr_name, AttributeValue=action) except Exception as exe: LOGGER.error('Unable to flip raw message setting: %s', str(exe)) return output def lambda_handler(event, context): LOGGER.info(json.dumps(event, default=str)) subscription_arn = os.environ['sub_arn'] response_data = {} response_data["event"] = event result = None try: if event['RequestType'] == 'Delete': result = flip_sns_raw_messaging(subscription_arn, action='false') else: result = flip_sns_raw_messaging(subscription_arn, action='true') except Exception as exe: LOGGER.error('Unable to process the file: %s', str(exe)) if result: cfnresponse_send(event, context, 'SUCCESS', response_data, "CustomResourcePhysicalID") else: cfnresponse_send(event, context, 'FAILED', response_data, "CustomResourcePhysicalID") Handler: index.lambda_handler MemorySize: 128 Role: !GetAtt "SNSSubscriptionFlipExecutionRole.Arn" Runtime: python3.7 Timeout: 60 Environment: Variables: sub_arn: !Ref AggregateSecurityNotificationsSubscription PermissionToInvokeLambda: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !GetAtt "EnableRawMessageForSNSTopicLambda.Arn" Principal: cloudformation.amazonaws.com SourceAccount: !Ref "AWS::AccountId" DeployEnableRawMessageForSNSTopicLambda: Type: 'Custom::TriggerLambda' Properties: ServiceToken: !GetAtt "EnableRawMessageForSNSTopicLambda.Arn" Parameters: AuditTopicName: Type: String Description: SNS Topic in Audit Account that AWS Config delivers notifications to. Default: 'aws-controltower-AggregateSecurityNotifications' PagerDutyHttpsEndpoint: Type: String Description: https://events.pagerduty.com/x-ere/[YOUR_PAGERDUTY_INTEGRATION_KEY_HERE] Default: https://events.pagerduty.com/x-ere/[YOUR_PAGERDUTY_INTEGRATION_KEY_HERE] Outputs: SNSSubscriptionArn: Description: SubscriptionArn Value: !Ref AggregateSecurityNotificationsSubscription