# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. # Permission is hereby granted, free of charge, to any person obtaining a copy of this # software and associated documentation files (the "Software"), to deal in the Software # without restriction, including without limitation the rights to use, copy, modify, # merge, publish, distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A # PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Description: > Sample stack illustrating security response automation concepts. This stack is intended for demonstration purposes only. You will be billed for the AWS resources used if you create a stack from this template. AWSTemplateFormatVersion: '2010-09-09' Parameters: SecurityContactEmail: Description: Email address to receive notifications. Must be a valid email address. Type: String AllowedPattern: ^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+\/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$ Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: Configuration Parameters: - SecurityContactEmail ParameterLabels: AdminEmail: default: Notification email (REQUIRED) Resources: CloudTrailStartLoggingLambdaCWE: Type: AWS::Lambda::Function Properties: Description: "Security Hub IAMUser-CloudTrailLoggingDisabled Response Function" Handler : "index.handler" MemorySize: 1024 Timeout: 300 Role: !GetAtt CloudTrailStartLoggingIAMRole.Arn Runtime : "python3.7" Environment: Variables: SNSTOPIC: !Ref CloudTrailStartLoggingSNSTopic Code: ZipFile: | # Description: Lambda function that restarts CloudTrail if logging and sends a notification in response to CloudTrail StopLogging. # import boto3 import logging import os import botocore.session from botocore.exceptions import ClientError session = botocore.session.get_session() logger=logging.getLogger(__name__) logger.setLevel(logging.INFO) snsARN = os.environ['SNSTOPIC'] def get_cloudtrail_status(trailname): client = boto3.client('cloudtrail') response = client.get_trail_status(Name=trailname) if response['ResponseMetadata']['HTTPStatusCode'] == 200: response = response['IsLogging'] logger.info("Status of CloudTrail logging for %s - %s" % (trailname, response)) else: logger.error("Error gettingCloudTrail logging status for %s - %s" % (trailname, response)) return response def enable_cloudtrail(trailname): client = boto3.client('cloudtrail') response = client.start_logging(Name=trailname) if response['ResponseMetadata']['HTTPStatusCode'] == 200: logger.info("Response on enable CloudTrail logging for %s - %s" % (trailname, response)) else: logger.error("Error enabling CloudTrail logging for %s - %s" % (trailname, response)) return response def notify_admin(topic, description): snsclient = boto3.client('sns') response = snsclient.publish( TargetArn = topic, Message = "Event description: \"%s\" " %description, Subject = 'CloudTrail Logging Alert' ) if response['ResponseMetadata']['HTTPStatusCode'] == 200: logger.info("SNS notification sent successfully - %s" %response) else: logger.error("Error sending SNS notification - %s" %response) return response # Lambda entry point def handler(event, context): logger.setLevel(logging.INFO) logger.info("Starting automatic CloudTrail remediation response") description = event['detail'] logger.debug("Event is-- %s" %event) logger.debug("snsARN is-- %s" %snsARN) try: response = None if 'name' in event['detail']['requestParameters']: trailARN = event['detail']['requestParameters']['name'] response = get_cloudtrail_status(trailARN) logger.debug("trailARN is--- %s" %trailARN) if response == False: response = enable_cloudtrail(trailARN) if response['ResponseMetadata']['HTTPStatusCode'] == 200: message = "CloudTrail logging restarted automatically for trail - " + trailARN + "\n \n Event:" + str(description) notify_admin(snsARN, message) logger.info("Completed automatic CloudTrail remediation response for %s - %s" % (trailARN, response)) elif response == True: message = "CloudTrail logging is already enabled for - " + trailARN + "\n \n Event:" + str(description) notify_admin(snsARN, message) logger.info("CloudTrail logging is already enabled for %s." %trailARN) else: message = "Event:" + str(description) notify_admin(snsARN, message) logger.info("Event: %s, Response: %s" % (event, response)) except ClientError as e: message = "%s \n \n %s" % (e, event) logger.error("%s, %s" % (e, event)) notify_admin(snsARN, message) CloudTrailStartLoggingLambdaSH: Type: AWS::Lambda::Function Properties: Description: "Security Hub IAMUser-CloudTrailLoggingDisabled Response Function" Handler : "index.handler" MemorySize: 1024 Timeout: 300 Role: !GetAtt CloudTrailStartLoggingIAMRole.Arn Runtime : "python3.7" Environment: Variables: SNSTOPIC: !Ref CloudTrailStartLoggingSNSTopic Code: ZipFile: | # Description: Lambda function that restarts CloudTrail if logging and sends a notification in response to Security Hub Security Hub IAMUser-CloudTrailLoggingDisabled. # import boto3 import logging import os import botocore.session from botocore.exceptions import ClientError session = botocore.session.get_session() logger=logging.getLogger(__name__) logger.setLevel(logging.INFO) snsARN = os.environ['SNSTOPIC'] def get_cloudtrail_status(trailname): client = boto3.client('cloudtrail') response = client.get_trail_status(Name=trailname) if response['ResponseMetadata']['HTTPStatusCode'] == 200: response = response['IsLogging'] logger.info("Status of CloudTrail logging for %s - %s" % (trailname, response)) else: logger.error("Error gettingCloudTrail logging status for %s - %s" % (trailname, response)) return response def enable_cloudtrail(trailname): client = boto3.client('cloudtrail') response = client.start_logging(Name=trailname) if response['ResponseMetadata']['HTTPStatusCode'] == 200: logger.info("Response on enable CloudTrail logging for %s - %s" % (trailname, response)) else: logger.error("Error enabling CloudTrail logging for %s - %s" % (trailname, response)) return response def notify_admin(topic, description): snsclient = boto3.client('sns') response = snsclient.publish( TargetArn = topic, Message = "CloudTrail logging state change detected. Event description: \"%s\" " %description, Subject = 'CloudTrail Logging Alert' ) if response['ResponseMetadata']['HTTPStatusCode'] == 200: logger.info("SNS notification sent successfully - %s" %response) else: logger.error("Error sending SNS notification - %s" %response) return response # Lambda entry point def handler(event, context): logger.setLevel(logging.INFO) logger.info("Starting automatic CloudTrail remediation response") trailARN = event['detail']['findings'][0]['ProductFields']['action/awsApiCallAction/affectedResources/AWS::CloudTrail::Trail'] description = event['detail']['findings'][0]['Description'] region = event['detail']['findings'][0]['Resources'][0]['Region'] logger.debug("Event is-- %s" %event) logger.debug("trailARN is--- %s" %trailARN) logger.debug("snsARN is-- %s" %snsARN) try: response = enable_cloudtrail(trailARN) status = get_cloudtrail_status(trailARN) if response['ResponseMetadata']['HTTPStatusCode'] == 200: message = "CloudTrail logging status for trail - " + trailARN + ": " + str(status) + "\n \n" + str(description) + "\n \n" + \ "Review additional information in Security Hub - https://console.aws.amazon.com/securityhub/home?region=" + region + "#/findings" notify_admin(snsARN, message) logger.info("Completed automatic CloudTrail remediation response for %s - %s" % (trailARN, response)) else: logger.error("Something went wrong - %s, %s" % (trailARN, event)) except ClientError as e: message = "%s \n \n %s" % (e, event) logger.error("%s, %s" % (e, event)) notify_admin(snsARN, message) # Lambda Execution role with needed IAM permissions CloudTrailStartLoggingIAMRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: "2012-10-17" Statement: - # allow Lambda service to use this role Effect: "Allow" Principal: Service: - "lambda.amazonaws.com" Action: "sts:AssumeRole" Path: "/" Policies: - PolicyName: inline PolicyDocument: Version: '2012-10-17' Statement: # can start logging on any trail - Effect: Allow Action: - cloudtrail:StartLogging - cloudtrail:GetTrailStatus Resource: "*" # can send a message to one SNS topic - Effect: Allow Action: - sns:Publish Resource: !Ref CloudTrailStartLoggingSNSTopic # can write logs in CloudWatchLogs - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: "arn:aws:logs:*:*:*" # EventBridge event forwards Security Hub finding to Lambda CloudTrailStartLoggingEventRuleforSH: Type: "AWS::Events::Rule" Properties: Description: "CloudTrail - start logging if trail stopped" # pattern matches the nested JSON format of a specific Security Hub finding EventPattern: source: - aws.securityhub detail-type: - "Security Hub Findings - Imported" detail: findings: Types: - "TTPs/Defense Evasion/Stealth:IAMUser-CloudTrailLoggingDisabled" State: "ENABLED" Targets: - # invokes the Lambda function if pattern is matched Arn: !GetAtt CloudTrailStartLoggingLambdaSH.Arn Id: "SecurityResponseAutomation-CloudTrailLoggingRemediationSH" # EventBridge event forwards Security Hub finding to Lambda CloudTrailStartLoggingEventRuleforCWE: Type: "AWS::Events::Rule" Properties: Description: "CloudTrail - start logging if trail stopped" # pattern matches the nested JSON format of a specific Security Hub finding EventPattern: source: - aws.cloudtrail detail-type: - "AWS API Call via CloudTrail" detail: eventSource: - cloudtrail.amazonaws.com eventName: - StopLogging - DeleteTrail - UpdateTrail - RemoveTags - AddTags - PutEventSelectors State: "ENABLED" Targets: - # invokes the Lambda function if pattern is matched Arn: !GetAtt CloudTrailStartLoggingLambdaCWE.Arn Id: "SecurityResponseAutomation-CloudTrailLoggingRemediationCWE" # Gives EventBridge service permissions to invoke the Lambda function CloudTrailStartLoggingInvokePermissionsCWE: Type: "AWS::Lambda::Permission" Properties: FunctionName: !Ref "CloudTrailStartLoggingLambdaCWE" Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" CloudTrailStartLoggingInvokePermissionsSH: Type: "AWS::Lambda::Permission" Properties: FunctionName: !Ref "CloudTrailStartLoggingLambdaSH" Action: "lambda:InvokeFunction" Principal: "events.amazonaws.com" # defines the SNS topic with email address as the only subscriber CloudTrailStartLoggingSNSTopic: Type: "AWS::SNS::Topic" Properties: Subscription: - Endpoint: !Ref SecurityContactEmail Protocol: "email" Outputs: LambdaFunctionCWE: Description: Lambda Function that restarts CloudTrail logging for CloudWatch Event. Value: !Sub https://console.aws.amazon.com/lambda/home?region=${AWS::Region}#/functions/${CloudTrailStartLoggingLambdaCWE} EventBridgeRuleCWE: Description: EventBridge rule that invokes the lambda function for CloudWatch Event. Value: !Sub https://console.aws.amazon.com/events/home?region=${AWS::Region}#/eventbus/default/rules/${CloudTrailStartLoggingEventRuleforCWE} LambdaFunctionSH: Description: Lambda Function that restarts CloudTrail logging for Security Hub. Value: !Sub https://console.aws.amazon.com/lambda/home?region=${AWS::Region}#/functions/${CloudTrailStartLoggingLambdaSH} EventBridgeRuleSH: Description: EventBridge rule that invokes the lambda function for Security Hub. Value: !Sub https://console.aws.amazon.com/events/home?region=${AWS::Region}#/eventbus/default/rules/${CloudTrailStartLoggingEventRuleforSH}