├── .gitignore ├── LICENSE ├── README.md ├── detect.py ├── guardduty_workflow.yml ├── images ├── aws-gd-remediation-arch.png └── workflow.png ├── remediation.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GuardDuty Remediation Workflow with Step Functions 2 | 3 | This serverless application creates an [AWS Step Functions](https://aws.amazon.com/step-functions/) state machine that uses AWS Lambda functions to publish alerts and remediate [Amazon GuardDuty](https://aws.amazon.com/guardduty/) findings. The below architecture showcases how a finding is processed through the workflow. 4 | 5 | ## Architecture 6 | 7 | ![Architecture](images/aws-gd-remediation-arch.png) 8 | 9 | ## Prerequisites 10 | 11 | Below are the necessary prerequisites: 12 | 13 | * [AWS Account](https://aws.amazon.com/premiumsupport/knowledge-center/create-and-activate-aws-account/) 14 | * [AWS CLI](https://aws.amazon.com/cli/) 15 | * [pip](https://pypi.org/project/pip/) 16 | 17 | ### Cloud9 Environment 18 | 19 | If you have trouble installing any of the prerequisites or dependencies, you can spin up an [AWS Cloud9](https://aws.amazon.com/cloud9/) environment, which is a cloud-based IDE that comes prepackaged with a number of essential packages. 20 | 21 | ## Install Dependencies 22 | 23 | After cloning the repo, change to the aws-guardduty-remediation-workflow and run the following to install the dependencies: 24 | 25 | ``` 26 | pip install -r requirements.txt -t ./ 27 | ``` 28 | 29 | ## Setup Environment 30 | 31 | Before you deploy the SAM template for your serverless application you need to setup a number of resources manually. 32 | 33 | ### Slack 34 | 35 | #### Create Slack Bot 36 | 37 | Go to your Slack client: 38 | 39 | 1. Click the top left corner drop down. 40 | 2. Under **Administration** click **Manage Apps**. 41 | 3. In the left navigation click **Custom Integrations** and click **Bots**. 42 | 4. Click **Add Configuration** 43 | 5. Type **guardduty** for the Username and click **Add Bot Integration**. 44 | 6. Securely copy the API Token. You'll be adding this to parameter store later on. 45 | 7. Customize the Name and Icon as you see fit and click **Save Integration**. 46 | 47 | #### Create Slack Channel 48 | 49 | Create a new channel for receiving alerts. Invite your bot by typing **@guardduty** in the channel and clicking **invite them to join**. 50 | 51 | ### AWS Resources 52 | 53 | #### Create an S3 Bucket 54 | 55 | In order to package and deploy SAM templates you need to have an S3 bucket where you can upload your artifacts. If you don't already have a bucket you plan on using you can run the command below to create one. 56 | 57 | ``` 58 | aws s3api create-bucket --bucket --region --create-bucket-configuration LocationConstraint= 59 | ``` 60 | 61 | ### Create Parameter 62 | 63 | For this application you need to manually create a Parameter in AWS Systems Manager Parameter Store for your Slack Bot Token. 64 | 65 | ``` 66 | aws ssm put-parameter --name "bot-token-guardduty" --type "SecureString" --value "" 67 | ``` 68 | 69 | ### Create Inspector Role 70 | 71 | If you haven't used Amazon Inspector before you'll need to create an IAM Service-Linked Role. 72 | 73 | 1. Browse to [IAM Console](https://console.aws.amazon.com/iam/home#/home) and click **Roles** in the left navigation. 74 | 2. Click **Create Role** and select **Inspector** for the service that will be using the Role. 75 | 3. Click **Next: Permissions**, **Next: Review**, and then **Create Role**. 76 | 77 | ## Package and Deploy the SAM template 78 | 79 | Package local artifacts and upload to the S3 bucket you previously created. 80 | 81 | ``` 82 | aws cloudformation package --template-file guardduty_workflow.yml --s3-bucket --output-template-file guardduty_workflow_output.yml 83 | ``` 84 | 85 | Deploy the CloudFormation template. 86 | 87 | ``` 88 | aws cloudformation deploy --template-file guardduty_workflow_output.yml --stack-name sam-gd-remediation-workflow --parameter-overrides SlackChannel= SlackTokenName=bot-token-guardduty --capabilities CAPABILITY_NAMED_IAM 89 | ``` 90 | 91 | ## View your GuardDuty Remediation State Machine 92 | 93 | 1. Browse to the [AWS Step Functions](https://us-west-2.console.aws.amazon.com/states/home) 94 | 2. Click **State Machines** in the left navigation and click on **guardduty-workflow**. 95 | 3. Click **Definition** to view the JSON structure and visual representation of the workflow. 96 | 97 | Below are additional details about the Lambda functions included in the State Machine. 98 | 99 | ### State Machine Workflow Details 100 | 101 | ![Architecture](images/workflow.png) 102 | 103 | test. 104 | -------------------------------------------------------------------------------- /detect.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from slackclient import SlackClient 3 | import boto3 4 | import json 5 | import os 6 | 7 | # Global Variables 8 | channel = '#%s' % os.environ['SLACK_CHANNEL'] 9 | token_bot = '%s' % os.environ['SLACK_TOKEN_NAME'] 10 | 11 | def getSlackToken (user): 12 | ssm = boto3.client('ssm') 13 | 14 | token = ssm.get_parameter( 15 | Name='%s' % user, 16 | WithDecryption=True 17 | ) 18 | 19 | token = token['Parameter']['Value'] 20 | 21 | return token 22 | 23 | def getSevColor (sev): 24 | if sev >= 8: 25 | color = '#ff0000' 26 | elif sev < 8 and sev >= 4: 27 | color = '#ffa500' 28 | else: 29 | color = '0000ff' 30 | 31 | return color 32 | 33 | def getRemColor (rem): 34 | if rem == False: 35 | color = '#ff0000' 36 | else: 37 | color = '#83F52C' 38 | 39 | return color 40 | 41 | def PostMessage(channel, token_bot, message, thread_ts): 42 | 43 | # Get Bot Token 44 | gd_token = getSlackToken(token_bot) 45 | 46 | # Slack Client for Web API Requests 47 | slack_client = SlackClient(gd_token) 48 | 49 | if thread_ts == 'NA': 50 | # Post Slack Message 51 | post = slack_client.api_call( 52 | "chat.postMessage", 53 | channel=channel, 54 | as_user='true', 55 | attachments=message 56 | ) 57 | else: 58 | # Post Slack Message 59 | post = slack_client.api_call( 60 | "chat.postMessage", 61 | channel=channel, 62 | as_user='true', 63 | thread_ts=thread_ts, 64 | attachments=message 65 | ) 66 | 67 | return post 68 | 69 | def PublishEvent(event, context): 70 | 71 | # Log Event 72 | print("log -- Event: %s " % json.dumps(event)) 73 | 74 | # Set Event Variables 75 | gd_sev = event['detail']['severity'] 76 | gd_account = event['detail']['accountId'] 77 | gd_region = event['detail']['region'] 78 | gd_desc = event['detail']['description'] 79 | gd_type = event['detail']['type'] 80 | thread_ts = 'NA' 81 | 82 | # Set Severity Color 83 | gd_color = getSevColor(gd_sev) 84 | 85 | 86 | # Set Generic GD Finding Message 87 | message = [ 88 | { 89 | "title": gd_type, 90 | "fields": [ 91 | { 92 | "title": "AccountID", 93 | "value": gd_account, 94 | "short": 'true' 95 | }, 96 | { 97 | "title": "Region", 98 | "value": gd_region, 99 | "short": 'true' 100 | } 101 | ], 102 | "fallback": "Required plain-text summary of the attachment.", 103 | "color": gd_color, 104 | "text": gd_desc, 105 | }] 106 | 107 | # Post Slack Message 108 | post = PostMessage(channel, token_bot, message, thread_ts) 109 | 110 | # Add Slack Thread Id to Event 111 | event["ts"] = post['message']['ts'] 112 | 113 | return event 114 | 115 | def PublishRemediation(event, context): 116 | 117 | # Log Event 118 | print("log -- Event: %s " % json.dumps(event)) 119 | 120 | # Set Event Variables 121 | gd_rem = event['remediation']['success'] 122 | gd_rem_desc = event['remediation']['description'] 123 | gd_rem_title = event['remediation']['title'] 124 | 125 | # Set Severity Color 126 | gd_color = getRemColor(gd_rem) 127 | 128 | # Set Generic GD Finding Message 129 | message = [ 130 | { 131 | "title": gd_rem_title, 132 | "color": gd_color, 133 | "text": gd_rem_desc 134 | }] 135 | 136 | # Post Slack Message 137 | post = PostMessage(channel, token_bot, message, event["ts"]) 138 | 139 | return event 140 | -------------------------------------------------------------------------------- /guardduty_workflow.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Transform: 'AWS::Serverless-2016-10-31' 3 | Description: > 4 | 5 | This SAM template creates an example Remediation Workflow Microservice for GuardDuty findings: 6 | 7 | Parameters: 8 | 9 | ResourcePrefix: 10 | Type: String 11 | Default: guardduty-workflow 12 | Description: Prefix for the resources that are created. 13 | 14 | SlackChannel: 15 | Type: String 16 | Default: soc 17 | Description: Slack channel for sending notifications. 18 | 19 | SlackTokenName: 20 | Type: String 21 | Default: bot-token-guardduty 22 | Description: Name of the Slack Token parameter. # This is the parameter name you stored in Parameter Store 23 | 24 | Resources: 25 | 26 | # CloudWatch Event Rule and Associated IAM Role 27 | StepFunctionEvent: 28 | Type: "AWS::Events::Rule" 29 | Properties: 30 | Name: 31 | Fn::Join: 32 | - '-' 33 | - [!Ref ResourcePrefix, "trigger"] 34 | Description: "Event Rule to trigger the GuardDuty remediation State Machine." 35 | EventPattern: 36 | source: 37 | - aws.guardduty 38 | detail-type: 39 | - "GuardDuty Finding" 40 | State: "ENABLED" 41 | Targets: 42 | - 43 | Arn: !Ref StateMachine 44 | Id: "TargetStateMachine" 45 | RoleArn: !GetAtt [ CWStateExecutionRole, Arn ] 46 | 47 | CWStateExecutionRole: 48 | Type: "AWS::IAM::Role" 49 | Properties: 50 | AssumeRolePolicyDocument: 51 | Version: "2012-10-17" 52 | Statement: 53 | - Effect: "Allow" 54 | Principal: 55 | Service: 56 | - "events.amazonaws.com" 57 | Action: "sts:AssumeRole" 58 | Path: "/" 59 | Policies: 60 | - PolicyName: GDCWStateExecutionPolicy 61 | PolicyDocument: 62 | Version: "2012-10-17" 63 | Statement: 64 | - Effect: Allow 65 | Action: 66 | - "states:StartExecution" 67 | Resource: !Ref StateMachine 68 | 69 | # Event Publishing Lambda Functions 70 | PublishEventLambda: 71 | Type: 'AWS::Serverless::Function' 72 | Properties: 73 | Handler: 'detect.PublishEvent' 74 | Role: !GetAtt [ PublishLambdaRole, Arn ] 75 | Timeout: 60 76 | CodeUri: ./ 77 | Runtime: python2.7 78 | FunctionName: 79 | Fn::Join: 80 | - '-' 81 | - [!Ref ResourcePrefix, 'publish-event'] 82 | Environment: 83 | Variables: 84 | SLACK_CHANNEL: !Ref SlackChannel 85 | SLACK_TOKEN_NAME: !Ref SlackTokenName 86 | 87 | PublishRemediationLambda: 88 | Type: 'AWS::Serverless::Function' 89 | Properties: 90 | Handler: 'detect.PublishRemediation' 91 | Role: !GetAtt [ PublishLambdaRole, Arn ] 92 | Timeout: 60 93 | CodeUri: ./ 94 | Runtime: python2.7 95 | FunctionName: 96 | Fn::Join: 97 | - '-' 98 | - [!Ref ResourcePrefix, 'publish-remediation'] 99 | Environment: 100 | Variables: 101 | SLACK_CHANNEL: !Ref SlackChannel 102 | SLACK_TOKEN_NAME: !Ref SlackTokenName 103 | 104 | PublishLambdaRole: 105 | Type: AWS::IAM::Role 106 | Properties: 107 | AssumeRolePolicyDocument: 108 | Version: '2012-10-17' 109 | Statement: 110 | - Effect: Allow 111 | Principal: 112 | Service: 113 | - lambda.amazonaws.com 114 | Action: 115 | - sts:AssumeRole 116 | Path: '/' 117 | Policies: 118 | - 119 | PolicyName: GDPublishPolicy 120 | PolicyDocument: 121 | Version: '2012-10-17' 122 | Statement: 123 | - 124 | Effect: Allow 125 | Action: 126 | - logs:* 127 | Resource: arn:aws:logs:*:*:* 128 | - 129 | Effect: Allow 130 | Action: 131 | - ssm:GetParameter 132 | - ssm:GetParameters 133 | - ssm:DescribeParameters 134 | Resource: '*' 135 | 136 | # EC2 Remediation Lambda Function (MaliciousIPCaller) 137 | RemediationEC2Lambda: 138 | Type: 'AWS::Serverless::Function' 139 | Properties: 140 | Handler: 'remediation.EC2MaliciousIPCaller' 141 | Role: !GetAtt [ RemediationEC2LambdaRole, Arn ] 142 | Timeout: 60 143 | CodeUri: ./ 144 | Runtime: python2.7 145 | FunctionName: 146 | Fn::Join: 147 | - '-' 148 | - [!Ref ResourcePrefix, 'ec2-malicious-ipcaller'] 149 | Environment: 150 | Variables: 151 | SLACK_CHANNEL: !Ref SlackChannel 152 | SLACK_TOKEN_NAME: !Ref SlackTokenName 153 | 154 | RemediationEC2LambdaRole: 155 | Type: AWS::IAM::Role 156 | Properties: 157 | AssumeRolePolicyDocument: 158 | Version: '2012-10-17' 159 | Statement: 160 | - Effect: Allow 161 | Principal: 162 | Service: 163 | - lambda.amazonaws.com 164 | Action: 165 | - sts:AssumeRole 166 | Path: '/' 167 | Policies: 168 | - 169 | PolicyName: GDEC2Remediation 170 | PolicyDocument: 171 | Version: '2012-10-17' 172 | Statement: 173 | - 174 | Effect: Allow 175 | Action: 176 | - ssm:PutParameter 177 | - ec2:AuthorizeSecurityGroupEgress 178 | - ec2:AuthorizeSecurityGroupIngress 179 | - ec2:CreateSecurityGroup 180 | - ec2:DescribeSecurityGroups 181 | - ec2:RevokeSecurityGroupEgress 182 | - ec2:RevokeSecurityGroupIngress 183 | - ec2:UpdateSecurityGroupRuleDescriptionsEgress 184 | - ec2:UpdateSecurityGroupRuleDescriptionsIngress 185 | - ec2:DescribeInstances 186 | - ec2:UpdateSecurityGroupRuleDescriptionsIngress 187 | - ec2:DescribeVpcs 188 | - ec2:ModifyInstanceAttribute 189 | - lambda:InvokeFunction 190 | - cloudwatch:PutMetricData 191 | - xray:PutTraceSegments 192 | - xray:PutTelemetryRecords 193 | Resource: '*' 194 | - 195 | Effect: Allow 196 | Action: 197 | - logs:* 198 | Resource: arn:aws:logs:*:*:* 199 | 200 | # EC2 Remediation Lambda Function (SSH BruteForce) 201 | 202 | RemediationEC2BruteForceLambda: 203 | Type: 'AWS::Serverless::Function' 204 | Properties: 205 | Handler: 'remediation.EC2BruteForce' 206 | Role: !GetAtt [ RemediationEC2BruteForceLambdaRole, Arn ] 207 | Timeout: 60 208 | CodeUri: ./ 209 | Runtime: python2.7 210 | FunctionName: 211 | Fn::Join: 212 | - '-' 213 | - [!Ref ResourcePrefix, 'ec2-sshbruteforce'] 214 | Environment: 215 | Variables: 216 | SNS_TOPIC_ARN: !Ref EC2BruteForceSNSTopic 217 | SLACK_CHANNEL: !Ref SlackChannel 218 | SLACK_TOKEN_NAME: !Ref SlackTokenName 219 | RESOURCE_PREFIX: !Ref ResourcePrefix 220 | 221 | RemediationEC2BruteForceLambdaRole: 222 | Type: AWS::IAM::Role 223 | Properties: 224 | AssumeRolePolicyDocument: 225 | Version: 2012-10-17 226 | Statement: 227 | - 228 | Effect: Allow 229 | Principal: 230 | Service: 231 | - lambda.amazonaws.com 232 | Action: 233 | - sts:AssumeRole 234 | Path: / 235 | Policies: 236 | - 237 | PolicyName: GDEC2BruteForceRemediation 238 | PolicyDocument: 239 | Version: 2012-10-17 240 | Statement: 241 | - 242 | Effect: Allow 243 | Action: 244 | - inspector:CreateAssessmentTemplate 245 | - inspector:CreateAssessmentTarget 246 | - inspector:CreateResourceGroup 247 | - inspector:ListRulesPackages 248 | - inspector:StartAssessmentRun 249 | - inspector:SubscribeToEvent 250 | - inspector:SetTagsForResource 251 | - inspector:DescribeAssessmentRuns 252 | - ec2:CreateTags 253 | - ec2:DescribeTags 254 | Resource: '*' 255 | - 256 | Effect: Allow 257 | Action: 258 | - ssm:DescribeParameters 259 | - ssm:GetParameter 260 | - ssm:GetParameters 261 | Resource: 262 | Fn::Join: 263 | - ':' 264 | - ["arn:aws:ssm", !Ref "AWS::Region", !Ref "AWS::AccountId", "parameter/*"] 265 | - 266 | Effect: Allow 267 | Action: 268 | - logs:CreateLogGroup 269 | - logs:CreateLogStream 270 | - logs:PutLogEvents 271 | Resource: '*' 272 | 273 | # EC2 Cleanup Lambda Function (SSH BruteForce) 274 | 275 | CleanupEC2BruteForceLambda: 276 | Type: 'AWS::Serverless::Function' 277 | Properties: 278 | Handler: 'remediation.EC2CleanupBruteForce' 279 | Role: !GetAtt [ CleanupEC2BruteForceLambdaRole, Arn ] 280 | Timeout: 60 281 | CodeUri: ./ 282 | Runtime: python2.7 283 | FunctionName: 284 | Fn::Join: 285 | - '-' 286 | - [!Ref ResourcePrefix, 'ec2-sshbruteforce-cleanup'] 287 | Environment: 288 | Variables: 289 | SLACK_CHANNEL: !Ref SlackChannel 290 | SLACK_TOKEN_NAME: !Ref SlackTokenName 291 | Events: 292 | MyTopic: 293 | Type: SNS 294 | Properties: 295 | Topic: !Ref EC2BruteForceSNSTopic 296 | 297 | CleanupEC2BruteForceLambdaRole: 298 | Type: AWS::IAM::Role 299 | Properties: 300 | AssumeRolePolicyDocument: 301 | Version: 2012-10-17 302 | Statement: 303 | - 304 | Effect: Allow 305 | Principal: 306 | Service: 307 | - lambda.amazonaws.com 308 | Action: 309 | - sts:AssumeRole 310 | Path: / 311 | Policies: 312 | - 313 | PolicyName: GDEC2BruteForceCleanup 314 | PolicyDocument: 315 | Version: 2012-10-17 316 | Statement: 317 | - 318 | Effect: Allow 319 | Action: 320 | - inspector:DeleteAssessmentTarget 321 | - inspector:DeleteAssessmentTemplate 322 | - inspector:DescribeAssessmentRuns 323 | - inspector:DescribeAssessmentTemplates 324 | - ec2:DeleteTags 325 | Resource: '*' 326 | - 327 | Effect: Allow 328 | Action: 329 | - ssm:DescribeParameters 330 | - ssm:GetParameter 331 | - ssm:GetParameters 332 | Resource: 333 | Fn::Join: 334 | - ':' 335 | - ["arn:aws:ssm", !Ref "AWS::Region", !Ref "AWS::AccountId", "parameter/*"] 336 | Resource: '*' 337 | - 338 | Effect: Allow 339 | Action: 340 | - logs:CreateLogGroup 341 | - logs:CreateLogStream 342 | - logs:PutLogEvents 343 | Resource: '*' 344 | 345 | # Findings SNS Topic 346 | EC2BruteForceSNSTopic: 347 | Type: AWS::SNS::Topic 348 | Properties: 349 | TopicName: 350 | Fn::Join: 351 | - '-' 352 | - [!Ref ResourcePrefix, 'ec2-sshbruteforce-topic'] 353 | 354 | EC2SNSTopicPolicy: 355 | Type: AWS::SNS::TopicPolicy 356 | Properties: 357 | PolicyDocument: 358 | Id: ID-GD-Topic-Policy 359 | Version: '2012-10-17' 360 | Statement: 361 | - Sid: SID-GD-Example 362 | Effect: Allow 363 | Principal: 364 | Service: inspector.amazonaws.com 365 | Action: sns:Publish 366 | Resource: !Ref EC2BruteForceSNSTopic 367 | Topics: 368 | - !Ref EC2BruteForceSNSTopic 369 | 370 | # IAM Remediation Lambda Functions 371 | 372 | RemediationIAMLambda: 373 | Type: 'AWS::Serverless::Function' 374 | Properties: 375 | Handler: 'remediation.InstanceCredentialExfiltration' 376 | Role: !GetAtt [ RemediationICELambdaRole, Arn ] 377 | Timeout: 60 378 | CodeUri: ./ 379 | Runtime: python2.7 380 | FunctionName: 381 | Fn::Join: 382 | - '-' 383 | - [!Ref ResourcePrefix, 'iam-instance-credential-exfil'] 384 | Environment: 385 | Variables: 386 | SLACK_CHANNEL: !Ref SlackChannel 387 | SLACK_TOKEN_NAME: !Ref SlackTokenName 388 | 389 | RemediationICELambdaRole: 390 | Type: AWS::IAM::Role 391 | Properties: 392 | AssumeRolePolicyDocument: 393 | Version: 2012-10-17 394 | Statement: 395 | - 396 | Effect: Allow 397 | Principal: 398 | Service: 399 | - lambda.amazonaws.com 400 | Action: 401 | - sts:AssumeRole 402 | Path: / 403 | Policies: 404 | - 405 | PolicyName: GDICERemediation 406 | PolicyDocument: 407 | Version: 2012-10-17 408 | Statement: 409 | - 410 | Effect: Allow 411 | Action: 412 | - iam:PutRolePolicy 413 | Resource: '*' 414 | - 415 | Effect: Allow 416 | Action: 417 | - logs:CreateLogGroup 418 | - logs:CreateLogStream 419 | - logs:PutLogEvents 420 | Resource: '*' 421 | 422 | StateExecutionRole: 423 | Type: "AWS::IAM::Role" 424 | Properties: 425 | AssumeRolePolicyDocument: 426 | Version: "2012-10-17" 427 | Statement: 428 | - Effect: "Allow" 429 | Principal: 430 | Service: 431 | - !Sub states.${AWS::Region}.amazonaws.com 432 | Action: "sts:AssumeRole" 433 | Path: "/" 434 | Policies: 435 | - PolicyName: GDStateExecutionPolicy 436 | PolicyDocument: 437 | Version: "2012-10-17" 438 | Statement: 439 | - Effect: Allow 440 | Action: 441 | - "lambda:InvokeFunction" 442 | Resource: "*" 443 | 444 | StateMachine: 445 | Type: "AWS::StepFunctions::StateMachine" 446 | Properties: 447 | StateMachineName: !Ref ResourcePrefix 448 | DefinitionString: 449 | !Sub 450 | - |- 451 | { 452 | "Comment": "A GuardDuty workflow microservice", 453 | "StartAt": "PublishGuardDutyEvent", 454 | "States": { 455 | "PublishGuardDutyEvent": { 456 | "Type": "Task", 457 | "Resource": "${PublishEventArn}", 458 | "TimeoutSeconds": 60, 459 | "Next": "GuardDutyFindings" 460 | }, 461 | "GuardDutyFindings": { 462 | "Type": "Choice", 463 | "Choices": [ 464 | { 465 | "StringEquals": "UnauthorizedAccess:EC2/MaliciousIPCaller.Custom", 466 | "Variable": "$.detail.type", 467 | "Next": "EC2/MaliciousIPCaller.Custom" 468 | }, 469 | { 470 | "StringEquals": "UnauthorizedAccess:EC2/SSHBruteForce", 471 | "Variable": "$.detail.type", 472 | "Next": "EC2/SSHBruteForce" 473 | }, 474 | { 475 | "StringEquals": "UnauthorizedAccess:IAMUser/InstanceCredentialExfiltration", 476 | "Variable": "$.detail.type", 477 | "Next": "IAMUser/InstanceCredentialExfiltration" 478 | } 479 | ], 480 | "Default": "ManualRemediationRequired" 481 | }, 482 | "EC2/MaliciousIPCaller.Custom": { 483 | "Type": "Task", 484 | "Resource": "${RemediationEC2}", 485 | "TimeoutSeconds": 60, 486 | "Next": "PublishGuardDutyRemediation" 487 | }, 488 | "EC2/SSHBruteForce": { 489 | "Type": "Task", 490 | "Resource": "${RemediationEC2SSHBruteForce}", 491 | "TimeoutSeconds": 60, 492 | "Next": "PublishGuardDutyRemediation" 493 | }, 494 | "IAMUser/InstanceCredentialExfiltration": { 495 | "Type": "Task", 496 | "Resource": "${RemediationIAM}", 497 | "TimeoutSeconds": 60, 498 | "Next": "PublishGuardDutyRemediation" 499 | }, 500 | "PublishGuardDutyRemediation": { 501 | "Type": "Task", 502 | "Resource": "${PublishRemediationArn}", 503 | "TimeoutSeconds": 60, 504 | "End": true 505 | }, 506 | "ManualRemediationRequired" : { 507 | "Type" : "Succeed", 508 | "OutputPath": "$" 509 | } 510 | } 511 | } 512 | - 513 | PublishEventArn: 514 | !GetAtt [ PublishEventLambda, Arn ] 515 | PublishRemediationArn: 516 | !GetAtt [ PublishRemediationLambda, Arn ] 517 | RemediationEC2: 518 | !GetAtt [ RemediationEC2Lambda, Arn ] 519 | RemediationEC2SSHBruteForce: 520 | !GetAtt [ RemediationEC2BruteForceLambda, Arn ] 521 | RemediationIAM: 522 | !GetAtt [ RemediationIAMLambda, Arn ] 523 | RoleArn: !GetAtt [ StateExecutionRole, Arn ] -------------------------------------------------------------------------------- /images/aws-gd-remediation-arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmfuchs/aws-guardduty-remediation-workflow/42f1eae92053a330fb8fce99618a73a82e98036b/images/aws-gd-remediation-arch.png -------------------------------------------------------------------------------- /images/workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jmfuchs/aws-guardduty-remediation-workflow/42f1eae92053a330fb8fce99618a73a82e98036b/images/workflow.png -------------------------------------------------------------------------------- /remediation.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | from botocore.exceptions import ClientError 3 | import boto3 4 | import os 5 | import json 6 | import datetime 7 | import uuid 8 | import time 9 | import detect 10 | 11 | # Global Variables 12 | channel = '#%s' % os.environ['SLACK_CHANNEL'] 13 | token_bot = '%s' % os.environ['SLACK_TOKEN_NAME'] 14 | 15 | def EC2MaliciousIPCaller(event, context): 16 | 17 | # Log Event 18 | print("log -- Event: %s " % json.dumps(event)) 19 | 20 | # Set Event Variables 21 | gd_vpc_id = event["detail"]["resource"]["instanceDetails"]["networkInterfaces"][0]["vpcId"] 22 | gd_instance_id = event["detail"]["resource"]["instanceDetails"]["instanceId"] 23 | 24 | # Set Initial Remediation Metadata 25 | event['remediation'] = {} 26 | event['remediation']['success'] = False 27 | event['remediation']['title'] = "GuardDog was unable to remediate the Instance" 28 | event['remediation']['description'] = "Auto remediation was unsuccessful. Please review the finding and remediate manaully." 29 | 30 | # Create Forensics Security Group 31 | ec2 = boto3.client('ec2') 32 | gd_sg_name = 'GuardDuty-Remediation-Workflow-Isolation' 33 | try: 34 | try: 35 | # Create Isolation Security Group 36 | gd_sg = ec2.create_security_group( 37 | GroupName=gd_sg_name, 38 | Description='This Security Group is used to isolate potentially compromised instances.', 39 | VpcId=gd_vpc_id 40 | ) 41 | gd_sg_id = gd_sg['GroupId'] 42 | 43 | # Remove Default Egress Rule 44 | gd_sg = ec2.describe_security_groups( 45 | GroupIds=[ 46 | gd_sg_id, 47 | ] 48 | ) 49 | ec2.revoke_security_group_egress( 50 | GroupId=gd_sg_id, 51 | IpPermissions=gd_sg['SecurityGroups'][0]['IpPermissionsEgress'] 52 | ) 53 | 54 | ec2.authorize_security_group_ingress( 55 | FromPort=22, 56 | CidrIp='10.0.0.0/24', 57 | GroupId=gd_sg_id, 58 | IpProtocol='tcp', 59 | ToPort=22, 60 | ) 61 | except ClientError as e: 62 | print(e) 63 | print("log -- Isolation Security Group already exists.") 64 | 65 | # Get Security Group ID 66 | gd_sg = ec2.describe_security_groups( 67 | Filters=[ 68 | { 69 | 'Name': 'vpc-id', 70 | 'Values': [ 71 | gd_vpc_id, 72 | ] 73 | }, 74 | { 75 | 'Name': 'group-name', 76 | 'Values': [ 77 | gd_sg_name, 78 | ] 79 | } 80 | ] 81 | ) 82 | gd_sg_id = gd_sg['SecurityGroups'][0]['GroupId'] 83 | 84 | # Remove existing Security Groups and Attach the Isolation Security Group 85 | ec2 = boto3.resource('ec2') 86 | gd_instance = ec2.Instance(gd_instance_id) 87 | print("log -- %s, %s" % (gd_instance.id, gd_instance.instance_type)) 88 | 89 | # Get all Security Groups attached to the Instance 90 | all_sg_ids = [sg['GroupId'] for sg in gd_instance.security_groups] 91 | 92 | # Isolate Instance 93 | gd_instance.modify_attribute(Groups=[gd_sg_id]) 94 | 95 | # Set Remediation Metadata 96 | event['remediation']['success'] = True 97 | event['remediation']['title'] = "GuardDog Successfully Isolated Instance ID: %s" % gd_instance.id 98 | event['remediation']['description'] = "Please follow your necessary forensic procedures." 99 | 100 | except ClientError as e: 101 | print(e) 102 | print("log -- Error Auto-Remediating Finding") 103 | 104 | return event 105 | 106 | def EC2BruteForce(event, context): 107 | 108 | # Log Event 109 | print("log -- Event: %s " % json.dumps(event)) 110 | 111 | prefix = os.environ['RESOURCE_PREFIX'] 112 | gd_instance_id = event["detail"]["resource"]["instanceDetails"]["instanceId"] 113 | scan_id = str(uuid.uuid4()) 114 | scan_name = '%s-inspector-scan' % prefix 115 | target_name = '%s-target-%s' % (prefix, event["id"]) 116 | template_name = '%s-template-%s' % (prefix, event["id"]) 117 | assess_name = '%s-assessment-%s' % (prefix, event["id"]) 118 | # Set Initial Remediation Metadata 119 | event['remediation'] = {} 120 | event['remediation']['success'] = False 121 | event['remediation']['title'] = "GuardDog was unable to remediate the Instance" 122 | event['remediation']['description'] = "Auto remediation was unsuccessful. Please review the finding and remediate manaully." 123 | 124 | 125 | 126 | # Kick off Inspector Scan 127 | try: 128 | gd_sev = event['detail']['severity'] 129 | 130 | # Set Severity Color 131 | gd_color = detect.getSevColor(gd_sev) 132 | 133 | # Set Generic GD Finding Message 134 | message = [ 135 | { 136 | "title": 'Compromised Resource Details', 137 | "fields": [ 138 | { 139 | "title": "Instance ID", 140 | "value": gd_instance_id, 141 | "short": 'true' 142 | }, 143 | { 144 | "title": "Public IP", 145 | "value": event["detail"]["resource"]["instanceDetails"]["networkInterfaces"][0]["publicIp"], 146 | "short": 'true' 147 | }, 148 | { 149 | "title": 'Image Description', 150 | "value": event["detail"]["resource"]["instanceDetails"]["imageDescription"], 151 | "short": 'false' 152 | }, 153 | { 154 | "title": "VPC ID", 155 | "value": event["detail"]["resource"]["instanceDetails"]["networkInterfaces"][0]['vpcId'], 156 | "short": 'true' 157 | }, 158 | { 159 | "title": "Subnet ID", 160 | "value": event["detail"]["resource"]["instanceDetails"]["networkInterfaces"][0]['subnetId'], 161 | "short": 'true' 162 | } 163 | ], 164 | "fallback": "Required plain-text summary of the attachment.", 165 | "color": gd_color, 166 | "text": 'Below are some additional details related to the GuardDuty finding.', 167 | }] 168 | 169 | # Post Slack Message 170 | post = detect.PostMessage(channel, token_bot, message, event["ts"]) 171 | 172 | 173 | ec2 = boto3.client('ec2') 174 | inspector = boto3.client('inspector') 175 | 176 | scan_in_progress = False 177 | tags = ec2.describe_tags( 178 | Filters=[ 179 | { 180 | 'Name': 'resource-id', 181 | 'Values': [ 182 | gd_instance_id, 183 | ], 184 | }, 185 | ], 186 | MaxResults=100 187 | ) 188 | 189 | print(tags) 190 | 191 | for i in tags['Tags']: 192 | if i['Key'] == scan_name: 193 | print(i['Key']) 194 | scan_in_progress = True 195 | 196 | if scan_in_progress == False: 197 | print("log -- Event: Running Scan") 198 | ec2.create_tags( 199 | Resources=[ 200 | gd_instance_id, 201 | ], 202 | Tags=[ 203 | { 204 | 'Key': scan_name, 205 | 'Value': scan_id 206 | } 207 | ] 208 | ) 209 | 210 | packages = inspector.list_rules_packages( 211 | maxResults=100 212 | ) 213 | 214 | group = inspector.create_resource_group( 215 | resourceGroupTags=[ 216 | { 217 | 'key': scan_name, 218 | 'value': scan_id 219 | }, 220 | ] 221 | ) 222 | 223 | target = inspector.create_assessment_target( 224 | assessmentTargetName=target_name, 225 | resourceGroupArn=group['resourceGroupArn'] 226 | ) 227 | 228 | template = inspector.create_assessment_template( 229 | assessmentTargetArn=target['assessmentTargetArn'], 230 | assessmentTemplateName=template_name, 231 | durationInSeconds=900, 232 | rulesPackageArns=packages['rulesPackageArns'], 233 | userAttributesForFindings=[ 234 | { 235 | 'key': 'instance-id', 236 | 'value': gd_instance_id 237 | }, 238 | { 239 | 'key': 'scan-name', 240 | 'value': scan_name 241 | }, 242 | { 243 | 'key': 'scan-id', 244 | 'value': scan_id 245 | }, 246 | { 247 | 'key': 'gd-slack-thread', 248 | 'value': event["ts"] 249 | 250 | } 251 | ] 252 | ) 253 | 254 | inspector.subscribe_to_event( 255 | resourceArn=template['assessmentTemplateArn'], 256 | event='ASSESSMENT_RUN_COMPLETED', 257 | topicArn=os.environ['SNS_TOPIC_ARN'] 258 | ) 259 | 260 | assessment = inspector.start_assessment_run( 261 | assessmentTemplateArn=template['assessmentTemplateArn'], 262 | assessmentRunName=assess_name 263 | ) 264 | 265 | # Set Remediation Metadata 266 | event['remediation']['title'] = "GuardDog initiated an AWS Inspector assessment on this instance: %s" % gd_instance_id 267 | else: 268 | print("log -- Event: Scan Already Running") 269 | event['remediation']['title'] = "GuardDog has already initiated an AWS Inspector scan on this instance: %s" % gd_instance_id 270 | 271 | # Set Remediation Metadata 272 | event['remediation']['success'] = True 273 | event['remediation']['description'] = "Please view the console if you'd like to track the progress of the assessment (%s). A new message will be posted after the assessment has been completed." % assess_name 274 | 275 | except ClientError as e: 276 | print(e) 277 | print("log -- Error Starting an AWS Inspector Assessment") 278 | 279 | return event 280 | 281 | def EC2CleanupBruteForce(event, context): 282 | 283 | # Log Event 284 | print("log -- Event: %s " % json.dumps(event)) 285 | 286 | message = json.loads(event['Records'][0]['Sns']['Message']) 287 | cleanup = 'Failed' 288 | 289 | ec2 = boto3.client('ec2') 290 | inspector = boto3.client('inspector') 291 | 292 | 293 | try: 294 | 295 | if message['event'] == 'ASSESSMENT_RUN_COMPLETED': 296 | 297 | run = inspector.describe_assessment_runs( 298 | assessmentRunArns=[ 299 | message['run'], 300 | ] 301 | ) 302 | 303 | for i in run['assessmentRuns'][0]['userAttributesForFindings']: 304 | if i['key'] == 'instance-id': 305 | instance_id = i['value'] 306 | elif i['key'] == 'scan-name': 307 | scan_name = i['value'] 308 | elif i['key'] == 'scan-id': 309 | scan_id = i['value'] 310 | elif i['key'] == 'gd-slack-thread': 311 | thread_ts = i['value'] 312 | 313 | 314 | ec2.delete_tags( 315 | Resources=[ 316 | instance_id, 317 | ], 318 | Tags=[ 319 | { 320 | 'Key': scan_name, 321 | 'Value': scan_id 322 | }, 323 | ] 324 | ) 325 | 326 | #inspector.delete_assessment_template( 327 | # assessmentTemplateArn=message['template'] 328 | #) 329 | 330 | #inspector.delete_assessment_target( 331 | # assessmentTargetArn=message['target'], 332 | #) 333 | 334 | # Set Generic GD Finding Message 335 | message = [ 336 | { 337 | "title": 'Inspector Assessment Complete', 338 | "text": 'The assessment has completed and you can view the report in the console.', 339 | }] 340 | 341 | # Post Slack Message 342 | post = detect.PostMessage(channel, token_bot, message, thread_ts) 343 | 344 | 345 | else: 346 | print("log -- Not a Scan Completion Event") 347 | 348 | except ClientError as e: 349 | print(e) 350 | print("log -- Error Cleaning up") 351 | 352 | return cleanup 353 | 354 | 355 | def InstanceCredentialExfiltration(event, context): 356 | 357 | # Log Event 358 | print("log -- Event: %s " % json.dumps(event)) 359 | 360 | # Set Initial Remediation Metadata 361 | event['remediation'] = {} 362 | event['remediation']['success'] = False 363 | event['remediation']['title'] = "GuardDog was unable to remediate the Instance" 364 | event['remediation']['description'] = "Auto remediation was unsuccessful. Please review the finding and remediate manaully." 365 | 366 | try: 367 | 368 | # Set Clients 369 | iam = boto3.client('iam') 370 | ec2 = boto3.client('ec2') 371 | 372 | # Set Role Variable 373 | role = event['detail']['resource']['accessKeyDetails']['userName'] 374 | 375 | # Current Time 376 | time = datetime.datetime.utcnow().isoformat() 377 | 378 | # Set Revoke Policy 379 | policy = """ 380 | { 381 | "Version": "2012-10-17", 382 | "Statement": { 383 | "Effect": "Deny", 384 | "Action": "*", 385 | "Resource": "*", 386 | "Condition": {"DateLessThan": {"aws:TokenIssueTime": "%s"}} 387 | } 388 | } 389 | """ % time 390 | 391 | # Add policy to Role to Revoke all Current Sessions 392 | iam.put_role_policy( 393 | RoleName=role, 394 | PolicyName='RevokeOldSessions', 395 | PolicyDocument=policy.replace('\n', '').replace(' ', '') 396 | ) 397 | 398 | # Set Remediation Metadata 399 | event['remediation']['success'] = True 400 | event['remediation']['title'] = "GuardDog Successfully Removed all Active Sessions for Role: %s" % role 401 | event['remediation']['description'] = "Please follow your necessary forensic procedures." 402 | 403 | except ClientError as e: 404 | print(e) 405 | print("log -- Error Auto-Remediating Finding") 406 | 407 | return event 408 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | slackclient==1.1.0 --------------------------------------------------------------------------------