diff --git a/post-scan-actions/aws-python-publish-malware-to-sns/Makefile b/post-scan-actions/aws-python-publish-malware-to-sns/Makefile new file mode 100644 index 00000000..9a33956a --- /dev/null +++ b/post-scan-actions/aws-python-publish-malware-to-sns/Makefile @@ -0,0 +1,62 @@ +.PHONY: zip + +MKDIR := mkdir +ZIP := zip + +zip: + $(MKDIR) -p zip + $(ZIP) zip/send-malware-to-sns.zip handler.py + +create-target-sns-topic: + aws sns create-topic --name $(TARGET_SNS_TOPIC_NAME) + +create-publish-malware-policy: + aws iam create-policy \ + --policy-name $(PUBLISH_MALWARE_POLICY_NAME) \ + --policy-document "{\"Version\":\"2012-10-17\",\"Statement\":[{\"Sid\":\"VisualEditor0\",\"Effect\":\"Allow\",\"Action\":[\"sns:Publish\",\"account:GetAlternateContact\"],\"Resource\":\"*\"}]}" + +create-publish-malware-role: + aws iam create-role \ + --role-name $(PUBLISH_MALWARE_ROLE_NAME) \ + --assume-role-policy-document "{\"Version\": \"2012-10-17\",\"Statement\": [{\"Action\": \"sts:AssumeRole\",\"Effect\": \"Allow\",\"Principal\": {\"Service\": \"lambda.amazonaws.com\"}}]}" + +attach-policy: + aws iam attach-role-policy \ + --role-name $(PUBLISH_MALWARE_ROLE_NAME) \ + --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + + aws iam attach-role-policy \ + --role-name $(PUBLISH_MALWARE_ROLE_NAME) \ + --policy-arn $(PUBLISH_MALWARE_POLICY_ARN) + +create-function: zip + aws lambda create-function \ + --function-name $(PUBLISH_MALWARE_LAMBDA_NAME) \ + --role $(PUBLISH_MALWARE_ROLE_ARN) \ + --runtime python3.9 \ + --timeout 30 \ + --memory-size 128 \ + --handler handler.lambda_handler \ + --environment Variables=\{TARGET_SNS_ARN=$(TARGET_SNS_TOPIC_ARN)\} \ + --zip-file fileb://zip/send-malware-to-sns.zip + +subscribe-to-sns-topic: + aws sns subscribe \ + --topic-arn $(SCAN_RESULT_TOPIC_ARN) \ + --protocol lambda \ + --notification-endpoint $(PUBLISH_MALWARE_LAMBDA_ARN) \ + --region $(REGION) + +add-lambda-permission: + aws lambda add-permission \ + --function-name $(PUBLISH_MALWARE_LAMBDA_NAME) \ + --region $(REGION) \ + --statement-id sns \ + --action lambda:InvokeFunction \ + --principal sns.amazonaws.com \ + --source-arn $(SCAN_RESULT_TOPIC_ARN) + +update-function-code: zip + aws lambda update-function-code \ + --function-name $(FUNCTION_NAME) \ + --zip-file fileb://zip/send-malware-to-sns.zip diff --git a/post-scan-actions/aws-python-publish-malware-to-sns/README.md b/post-scan-actions/aws-python-publish-malware-to-sns/README.md new file mode 100644 index 00000000..2dbd19c2 --- /dev/null +++ b/post-scan-actions/aws-python-publish-malware-to-sns/README.md @@ -0,0 +1,122 @@ +# Cloud One File Storage Security Post Scan Action - Publish Malware to SNS Topic + +After a scan occurs, this example Lambda function publish the malware information to specific SNS topic. + +## Prerequisites + +### Find the 'ScanResultTopicARN' SNS topic ARN + +- In the AWS console, go to **Services > CloudFormation** > your all-in-one stack > **Outputs** or **Services > CloudFormation** > your storage stack > **Outputs**. +- Scroll down to locate the **ScanResultTopicARN** Key. +- Copy the **ScanResultTopic** ARN to a temporary location. Example: `arn:aws:sns:us-east-1:123445678901:FileStorageSecurity-All-In-One-Stack-StorageStack-1IDPU1PZ2W5RN-ScanResultTopic-N8DD2JH1GRKF` + +## Installation + +### From SAM CLI + +```bash +sam deploy \ +--stack-name \ +--capabilities CAPABILITY_AUTO_EXPAND CAPABILITY_NAMED_IAM \ +--region \ +--s3-bucket \ +--template-file template.yml \ +--parameter-overrides \ +ParameterKey=TargetSnsTopicArn,ParameterValue= \ +ParameterKey=ScanResultTopicARN,ParameterValue= +``` + +- where: + - `` is the stack name for this plugin. + - `` is the building AWS region. + - `` is the template put in. + - `` is the malware information published. + - `` is the SNS topic ARN from the storage stack. + +### From Makefile + +In a shell program, enter the following GNU Make command, using backslashes (`\`) to separate lines + +1. **Create Malware Target SNS Topic** + + ```bash + TARGET_SNS_TOPIC_NAME= \ + make create-target-sns-topic + ``` + +- where: + - `` is the name for the new SNS topic which cloud be published the walmare information. + +2. **Create Required Policy** + + ```bash + PUBLISH_MALWARE_POLICY_NAME= \ + make create-publish-malware-policy + ``` + +- where + - `` is the name for the new policy. + +3. **Create Required Role** + + ```bash + PUBLISH_MALWARE_POLICY_NAME= \ + make create-publish-malware-role + ``` + +- where + - `` is the name for the new role. + +4. **Attach Policy** + + ```bash + PUBLISH_MALWARE_ROLE_NAME= \ + PUBLISH_MALWARE_POLICY_ARN= \ + make create-publish-malware-role + ``` + +- where + - `` is the policy ARN created from step 2. + - `` is the role name created from step 3. + +5. **Create Lambda Function** + + ```bash + PUBLISH_MALWARE_LAMBDA_NAME= \ + PUBLISH_MALWARE_ROLE_ARN= \ + TARGET_SNS_TOPIC_ARN= \ + make create-function + ``` + +- where + - `` is the SNS topic ARN created from step 1. + - `` is the role ARN created from step 3. + - `` is the name for new lambda function. + +6. **SNS Subscrition** + + ```bash + SCAN_RESULT_TOPIC_ARN= \ + PUBLISH_MALWARE_LAMBDA_ARN= \ + REGION= \ + make subscribe-to-sns-topic + ``` + +- where + - `` is the lambda function ARN created from step 5. + - `` is the SNS topic ARN from the storage stack. + - `` is the building AWS region. + +7. **Add Lambda Permission** + + ```bash + PUBLISH_MALWARE_LAMBDA_NAME= \ + REGION= \ + SCAN_RESULT_TOPIC_ARN= \ + make add-lambda-permission + ``` + +- where + - `` is the lambda function and created from step 5. + - `` is the building AWS region. + - `` is the SNS topic ARN from the storage stack. diff --git a/post-scan-actions/aws-python-publish-malware-to-sns/handler.py b/post-scan-actions/aws-python-publish-malware-to-sns/handler.py new file mode 100644 index 00000000..dab8c965 --- /dev/null +++ b/post-scan-actions/aws-python-publish-malware-to-sns/handler.py @@ -0,0 +1,86 @@ +import os +import json +from datetime import datetime +from urllib.parse import urlparse + +import boto3 + + +def get_bucket_name(file_url): + + url = urlparse(file_url) + return url.hostname.split('.')[0] + + +def get_iso_str(ts): + + dt = datetime.fromtimestamp(ts) + return dt.isoformat() + + +def fetch_account_name(): + + account_client = boto3.client('account') + response = account_client.get_alternate_contact( + AlternateContactType='SECURITY' + ) + + return response['AlternateContact']['Name'] + + +def divide_to_chunks(iterable_collection, size): + + for i in range(0, len(iterable_collection), size): + yield iterable_collection[i:i + size] + + +def lambda_handler(event, context): + + target_sns_arn = os.environ['TARGET_SNS_ARN'] + + scan_results = [] + account_name = fetch_account_name() + + for record in event['Records']: + + print('record', record) + + # Message details from SNS event + message = json.loads(record['Sns']['Message']) + findings = message['scanning_result'].get('Findings') + + if not findings: + continue + + finding = findings[0] + + scan_results.append({ + 'bucket_name': get_bucket_name(message['file_url']), + 'account_name': account_name, + 'malware': finding['malware'], + 'malware_type': finding['type'], + 'time': get_iso_str(message['timestamp']) + }) + + print('scan_results', scan_results) + + sns_client = boto3.client('sns') + fails = [] + request_entries = [ + { + 'Message': json.dumps(scan_result), + 'Id': str(i) + } + for i, scan_result in enumerate(scan_results) + ] + + for entries_chunk in divide_to_chunks(request_entries, 10): + response = sns_client.publish_batch( + TopicArn=target_sns_arn, + PublishBatchRequestEntries=entries_chunk, + ) + + fails += response['Failed'] + + if fails: + print(fails) diff --git a/post-scan-actions/aws-python-publish-malware-to-sns/template.yml b/post-scan-actions/aws-python-publish-malware-to-sns/template.yml new file mode 100644 index 00000000..0af2d112 --- /dev/null +++ b/post-scan-actions/aws-python-publish-malware-to-sns/template.yml @@ -0,0 +1,86 @@ +AWSTemplateFormatVersion: 2010-09-09 +Transform: AWS::Serverless-2016-10-31 +Description: According to the scan result from the scanner, publish the malware information to the target SNS topic. + +Parameters: + ScanResultTopicARN: + Type: String + Description: The ARN of the scan result SNS topic in storage stack. + TargetSnsTopicArn: + Type: String + Description: The ARN for the SNS topic to which malware information publish. Will create a new SNS Topic if this value is empty. + Default: '' + +Conditions: + NeedCreateNewSnsTopic: !Equals + - !Ref TargetSnsTopicArn + - '' + +Resources: + TargetSnsTopic: + Type: AWS::SNS::Topic + Condition: NeedCreateNewSnsTopic + Properties: + DisplayName: Malware notification + PublishMalwarePolicy: + Type: AWS::IAM::ManagedPolicy + Properties: + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - sns:Publish + - account:GetAlternateContact + Resource: '*' + PublishMalwareRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Principal: + Service: + - lambda.amazonaws.com + Action: + - sts:AssumeRole + ManagedPolicyArns: + - !Ref PublishMalwarePolicy + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + PublishMalwareLambda: + Type: AWS::Serverless::Function + Properties: + CodeUri: ./ + Handler: handler.lambda_handler + Runtime: python3.9 + Timeout: 30 + Role: !GetAtt + - PublishMalwareRole + - Arn + Environment: + Variables: + TARGET_SNS_ARN: !If [ NeedCreateNewSnsTopic, !Ref TargetSnsTopic, !Ref TargetSnsTopicArn ] + MemorySize: 128 + TopicSubscription: + Type: AWS::SNS::Subscription + Properties: + Protocol: lambda + Endpoint: !GetAtt PublishMalwareLambda.Arn + TopicArn: !Ref ScanResultTopicARN + DeliveryPolicy: + healthyRetryPolicy: + numRetries: 6 + minDelayTarget: 5 + maxDelayTarget: 60 + numMinDelayRetries: 2 + numMaxDelayRetries: 2 + numNoDelayRetries: 2 + backoffFunction: exponential + InvokedLambdaPermission: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !GetAtt PublishMalwareLambda.Arn + Action: lambda:InvokeFunction + Principal: sns.amazonaws.com + SourceArn: !Ref ScanResultTopicARN