├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── automated-actions ├── AWS_Codepipeline_Disable_Stage_Transition │ ├── CloudwatchEventPattern │ ├── IAMPolicy │ ├── LambdaFunction.js │ ├── README.md │ └── cloudformation.template ├── AWS_EBS_VOLUME_LOST │ ├── SydneySummitDemo │ │ ├── aws_ebs_vol_lost_cfn.yaml │ │ ├── aws_ebs_vol_lost_importantapp-cfn.yaml │ │ ├── elasticsearch-objects.json │ │ ├── phd-mock.payload.json │ │ └── readme.md │ ├── aws_ebs_vol_lost_cloudformation.yaml │ ├── mock_payload.json │ ├── readme.md │ └── stepbystep │ │ ├── README.md │ │ ├── alexa_skill.py │ │ ├── create_AWSServiceRoleForAmazonElasticsearchService.json │ │ ├── images │ │ ├── Alexa_Skill_Output.png │ │ ├── CreateAlexaSkills.png │ │ ├── EC2_App.png │ │ ├── ES_Proxy.png │ │ ├── Even_Odd_State_Machine.png │ │ ├── Intro.png │ │ ├── Kibana.png │ │ ├── StepFunction.png │ │ ├── Step_1_1.png │ │ ├── Step_1_Sol.png │ │ ├── Step_2_Sol.png │ │ ├── Step_4_Sol.png │ │ └── Step_Bonus_Sol.png │ │ ├── kibana.json │ │ ├── mockpayload.json │ │ ├── step_1_es_ec2proxy_reinvent_workshop.yml │ │ ├── step_2_app_reinvent_workshop.yml │ │ └── step_3_stepfunctions_reinvent_workshop.yml ├── AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED │ ├── IAMPolicy │ ├── LambdaFunction.js │ ├── README.md │ ├── cloudformation.template │ └── testevent.json ├── AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED │ ├── AWSHealthElasticLoadBalancingENILimitReached.json │ ├── IAMPolicy │ ├── LambdaFunction.js │ └── README.md └── AWS_RISK_CREDENTIALS_EXPOSED │ ├── LICENSE │ ├── README.md │ ├── cloudformation │ └── risk_credentials_exposed.serverless.yaml │ ├── images │ ├── Architecture.png │ └── VisualWorkflow.png │ └── lambda_functions │ ├── delete_access_key_pair.py │ ├── lookup_cloudtrail_events.py │ └── notify_security.py ├── chime-notifier ├── IAMPolicy ├── LambdaFunction.py ├── README.md └── cfn-templates │ ├── chime-notifier.json │ └── chime-notifier.yml ├── coralogix-notifier ├── IAMPolicy ├── LambdaFunction.py ├── README.md └── cfn-templates │ ├── coralogix-notifier.json │ └── coralogix-notifier.yml ├── dos-report-notifier ├── README.md ├── dos-report-notifier.json └── stepbystep │ ├── README.md │ ├── images │ ├── Solution.png │ ├── Step_1_1.png │ ├── Step_1_6.png │ ├── Step_1_Sol.png │ ├── Step_2_Lambda_Create.png │ ├── Step_2_Sol.png │ ├── Step_3_Sol.png │ └── Step_4_Sol.png │ └── mockpayload.json ├── dx-maintenance-notifier ├── DX_Notifier.json └── README.md ├── high-availability-endpoint ├── .gitignore ├── README.md ├── java │ ├── README.md │ ├── build.gradle │ ├── settings.gradle │ └── src │ │ └── main │ │ └── java │ │ └── aws │ │ └── health │ │ └── high │ │ └── availability │ │ └── endpoint │ │ └── demo │ │ ├── ActiveRegionHasChangedException.java │ │ ├── App.java │ │ ├── HighAvailabilityV2HealthClient.java │ │ ├── HighAvailabilityV2Workflow.java │ │ ├── RegionLookup.java │ │ └── RegionLookupException.java └── python │ ├── README.md │ ├── health_client.py │ ├── main.py │ ├── region_lookup.py │ └── requirements.txt ├── images ├── AWSHealthToolsArchitecture.jpg └── cloudformation-launch-stack.png ├── shd-notifier ├── Health-Event-Chat-Post-LambdaFn.py ├── Health-Event-Iterator-LambdaFn.py ├── Health-Event-Poller-LambdaFn.py ├── Health-Event-Status-LambdaFn.py ├── README.md ├── deploy.sh └── shd-notifier.yml ├── slack-notifier ├── IAMPolicy ├── LambdaFunction.py ├── README.md └── cfn-templates │ ├── slack-notifier.json │ └── slack-notifier.yml ├── sms-notifier ├── IAMPolicy ├── LambdaFunction.js ├── README.md ├── sms-notifier.yml └── testHeatlhEvent.json ├── sns-topic-publisher ├── IAMPolicy ├── LambdaFunction.js ├── README.md └── cfn-templates │ ├── sns-topic-publisher.json │ └── sns-topic-publisher.yml └── teams-notifier ├── IAMPolicy ├── LambdaFunction.py ├── README.md └── cfn-templates └── teams-notifier.json /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | *.DS_Store 3 | .AppleDouble 4 | .LSOverride 5 | 6 | # Icon must end with two \r 7 | Icon 8 | 9 | 10 | # Thumbnails 11 | ._* 12 | 13 | # Files that might appear in the root of a volume 14 | .DocumentRevisions-V100 15 | .fseventsd 16 | .Spotlight-V100 17 | .TemporaryItems 18 | .Trashes 19 | .VolumeIcon.icns 20 | .com.apple.timemachine.donotpresent 21 | 22 | # Directories potentially created on remote AFP share 23 | .AppleDB 24 | .AppleDesktop 25 | Network Trash Folder 26 | Temporary Items 27 | .apdisk 28 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/aws/aws-health-tools/issues), or [recently closed](https://github.com/aws/aws-health-tools/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/aws/aws-health-tools/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/aws/aws-health-tools/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health Tools 2 | 3 | ### Description 4 | The samples provided in AWS Health Tools can help you build automation and customized alerts in response to AWS Health events. 5 | 6 | AWS Health provides ongoing visibility into the state of your AWS resources, services, and accounts. The service gives you awareness and remediation guidance for resource performance or availability issues that may affect your applications that run on AWS. AWS Health provides relevant and timely information to help you manage events in progress, as well as be aware of and prepare for planned activities. The service delivers alerts and notifications triggered by changes in the health of AWS resources, so you get near-instant event visibility and guidance to help accelerate troubleshooting. 7 | 8 | More information about AWS Health and Personal Health Dashboard (PHD) is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 9 | 10 | > NOTE: To get notifications about public events (global events that are not tied to your account), you must have a Business or Enterprise support plan from AWS Support. If you call the AWS Health API from an AWS account that doesn't have a Business or Enterprise support plan, you receive a SubscriptionRequiredException error. 11 | 12 | Setup and usage instructions are present for each tool in its respective directory:
13 | 14 | 15 | 16 | #### Solutions: 17 | [AWS Health Aware (AHA) - automated notification solution for sending well-formatted AWS Health Alerts across accounts and regions](https://github.com/aws-samples/aws-health-aware/)
18 | 19 | #### Custom Notifications: 20 | [AWS Health event SMS notifier](sms-notifier/)
21 | [AWS Health event Chime notifier](chime-notifier/)
22 | [AWS Health event Amazon Simple Notification Service (SNS) Topic Publisher](sns-topic-publisher/)
23 | [AWS Health event Slack notifier](slack-notifier/)
24 | [AWS Health event Direct Connect maintenance notifier](dx-maintenance-notifier/)
25 | [AWS Health event Coralogix notifier](coralogix-notifier/)
26 | [AWS Health Abuse event DOS report notifier](dos-report-notifier/)
27 | [AWS Health SHD event Chime/Slack/SNS notifier](shd-notifier/)
28 | [AWS Health Organizational View Alerts](https://github.com/aws-samples/aws-health-organizational-view-alerts)
29 | #### Automated Actions: 30 | [AWS Codepipeline disable stage transition triggered when AWS Health issue event generated](automated-actions/AWS_Codepipeline_Disable_Stage_Transition/)
31 | [AWS Health AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED triggers automated EC2 Instance stop or terminate](automated-actions/AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED/)
32 | [AWS Health AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED triggers freeing up of unused ENIs](automated-actions/AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED/)
33 | [AWS Health AWS_RISK_CREDENTIALS_EXPOSED remediation](automated-actions/AWS_RISK_CREDENTIALS_EXPOSED/)
34 | [AWS Health AWS_EBS_VOLUME_LOST Remediation](automated-actions/AWS_EBS_VOLUME_LOST/)
35 | #### Demos: 36 | [AWS Health API high availability endpoint](high-availability-endpoint/)
37 | 38 | ![Architecture](images/AWSHealthToolsArchitecture.jpg) 39 | 40 | ### License 41 | AWS Health Tools are licensed under the Apache 2.0 License. 42 | 43 | Disclaimer: The “AWS__OPERATIONAL_ISSUE” Amazon CloudWatch event type codes are for events where AWS is posting details to specific AWS accountIds. General service health events are not posted to this event type code at this time. Instead they are currently posted to the Service Health Dashboard (SHD) and are visible via the Personal Health Dashboard (PHD) in the AWS management console as well as returned via the AWS Health API. 44 | -------------------------------------------------------------------------------- /automated-actions/AWS_Codepipeline_Disable_Stage_Transition/CloudwatchEventPattern: -------------------------------------------------------------------------------- 1 | { 2 | "source": [ 3 | "aws.health" 4 | ], 5 | "detail-type": [ 6 | "AWS Health Event" 7 | ], 8 | "detail": { 9 | "service": [ 10 | "EC2" 11 | ], 12 | "eventTypeCategory": [ 13 | "issue" 14 | ] 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /automated-actions/AWS_Codepipeline_Disable_Stage_Transition/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1477516473539", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "arn:aws:logs:*:*:*" 13 | }, 14 | { 15 | "Sid": "Stmt1484165114117", 16 | "Action": [ 17 | "codepipeline:DisableStageTransition" 18 | ], 19 | "Effect": "Allow", 20 | "Resource": "*" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /automated-actions/AWS_Codepipeline_Disable_Stage_Transition/LambdaFunction.js: -------------------------------------------------------------------------------- 1 | // Sample Lambda Function to disable stage transition to pause deployments when an AWS Health issue event is generated. 2 | var AWS = require('aws-sdk'); 3 | var codepipeline = new AWS.CodePipeline(); 4 | 5 | // define configuration 6 | const pipelineName = process.env.pipelineName; //Pipeline Name 7 | const stageName = process.env.stageName; //Stage Name (e.g. Beta) 8 | 9 | //main function which gets AWS Health data from Cloudwatch event 10 | exports.handler = (event, context, callback) => { 11 | //extract details from Cloudwatch event 12 | eventName = event.detail.eventTypeCode; 13 | //disable transitions into the next stage of the pipeline 14 | var params = { 15 | pipelineName: pipelineName, 16 | reason: "AWS Health issue detected - please see AWS Personal Health Dashboard for more details", 17 | stageName: stageName, 18 | transitionType: "Inbound" 19 | }; 20 | codepipeline.disableStageTransition(params, function(err, data) { 21 | if (err) { 22 | const errorMessage = `Error in disabling CodePipeline stage transition for pipeline, ${pipelineName} in response to AWS Health event: ${eventName}.`; 23 | console.log(errorMessage, err); 24 | callback(errorMessage); 25 | } 26 | else { 27 | const successMessage = `Successfully got details from AWS Health event, ${eventName}, and disabled stage transition to ${stageName} for pipeline, ${pipelineName}.`; 28 | console.log(successMessage, data); 29 | callback(null, successMessage); //return success 30 | } 31 | }); 32 | }; 33 | 34 | -------------------------------------------------------------------------------- /automated-actions/AWS_Codepipeline_Disable_Stage_Transition/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health Issue Amazon Cloudwatch event trigger AWS CodePipeline Disable Stage Transition using AWS Lambda 2 | 3 | ### Description 4 | This sample highlights you can automatically stop a deployment when an Amazon EC2 issue occurs by disabling the stage transition in AWS Code Pipeline in response to an AWS Health Issue CloudWatch event. 5 | 6 | ### Setup and Usage 7 | 8 | #### Cloudformation Setup 9 | Choose **Launch Stack** to launch the template in the US East (N. Virginia) Region in your account: 10 | 11 | [![Launch AWS Health Code Pipeline Disable Stage Transition](../../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=AWSHealthCodePipelineDisableDisableStageTransition&templateURL=https://s3.amazonaws.com/aws-health-tools-assets/cloudformation-templates/AWSHealthCodePipelineDisableDisableStageTransition.json) 12 | 13 | Please update your region, CodePipeline name and Stage name according to your requirements. 14 | 15 | #### Manual Setup 16 | 1. Create an IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 17 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 18 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 19 | 20 | 2. Create a Lambda JavaScript function by using the [sample](LambdaFunction.js) provided and choose the IAM role created in step 1. Be sure to update the configuration of the Lambda function per your needs. 21 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 22 | 23 | 3. Create a CloudWatch Events rule to trigger the Lambda function created in step 2 matching an AWS Health Issue. An example of Cloudwatch rule event pattern for EC2 issues is [here](CloudwatchEventPattern). 24 | Documentation on how to create an AWS Health CloudWatch Events rule is available here: http://docs.aws.amazon.com/health/latest/ug/cloudwatch-events-health.html 25 | 26 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 27 | 28 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch events, and AWS Lambda. We recommend testing the example and tailoring it to your environment before using it in your production environment. 29 | 30 | Please also note that this will only capture EC2 issues that are generated by AWS Health as CloudWatch events. 31 | 32 | ### License 33 | AWS Health Tools are licensed under the Apache 2.0 License. 34 | -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/SydneySummitDemo/aws_ebs_vol_lost_importantapp-cfn.yaml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Parameters: 3 | RestoreImageId: 4 | Description: Snapshot to be used as root disk - leave this blank when creating the Stack. 5 | Type: "String" 6 | KeyName: 7 | Description: Name of an existing EC2 KeyPair to enable SSH access to the instance 8 | Type: "AWS::EC2::KeyPair::KeyName" 9 | InstanceType: 10 | Description: WebServer EC2 instance type 11 | Type: "String" 12 | Default: t2.small 13 | AllowedValues: [t1.micro, t2.nano, t2.micro, t2.small, t2.medium] 14 | VpcId: 15 | Type: "AWS::EC2::VPC::Id" 16 | Description: VpcId of your existing Virtual Private Cloud (VPC) 17 | SubnetId: 18 | Type: "AWS::EC2::Subnet::Id" 19 | Description: SubnetId of an existing subnet (for the primary network) in your 20 | Virtual Private Cloud (VPC) 21 | Cloud. 22 | SSHLocation: 23 | Description: The IP address range that can be used to SSH to the EC2 instances 24 | Type: "String" 25 | Default: 0.0.0.0/0 26 | AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2}) 27 | 28 | Conditions: 29 | RestoreImageIdCond: !Not [!Equals [!Ref RestoreImageId, "" ]] 30 | 31 | Resources: 32 | ## Elastic IPs 33 | EIP0: 34 | Type: AWS::EC2::EIP 35 | Properties: 36 | Domain: vpc 37 | EIPAssoc0: 38 | Type: AWS::EC2::EIPAssociation 39 | Properties: 40 | InstanceId: !Ref EC2Instance 41 | AllocationId: !GetAtt [EIP0, AllocationId] 42 | 43 | # Security Groups 44 | SSHSecurityGroup: 45 | Type: AWS::EC2::SecurityGroup 46 | Properties: 47 | VpcId: !Ref VpcId 48 | GroupDescription: Enable SSH access via port 22 49 | SecurityGroupIngress: 50 | - IpProtocol: tcp 51 | FromPort: 22 52 | ToPort: 22 53 | CidrIp: !Ref SSHLocation 54 | 55 | # Instance 56 | EC2Instance: 57 | Type: AWS::EC2::Instance 58 | Properties: 59 | ImageId: 60 | !If 61 | - RestoreImageIdCond 62 | - !Ref RestoreImageId 63 | - !GetAtt CustomLookupAmi.ImageId 64 | InstanceType: !Ref InstanceType 65 | KeyName: !Ref KeyName 66 | SubnetId: !Ref SubnetId 67 | Tags: 68 | - Key: Name 69 | Value: !Ref AWS::StackName 70 | 71 | # Custom Resources 72 | # AMI Lookup 73 | CustomLookupAmi: 74 | Type: Custom::LookupAmi 75 | Version: 1.0 76 | Properties: 77 | ServiceToken: !GetAtt LambdaLookupAmi.Arn 78 | Region: !Ref 'AWS::Region' 79 | LambdaLookupAmi: 80 | Type: "AWS::Lambda::Function" 81 | Properties: 82 | Handler: index.lambda_handler 83 | Role: !GetAtt ExecutionRole.Arn 84 | Runtime: python3.6 85 | Timeout: 60 86 | Code: 87 | ZipFile: | 88 | import boto3 89 | import cfnresponse 90 | from dateutil import parser 91 | 92 | def lambda_handler(event, context): 93 | try: 94 | if event['RequestType'] == 'Delete': 95 | responseData = {'Delete': 'SUCCESS'} 96 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) 97 | else: 98 | region = event['ResourceProperties']['Region'] 99 | 100 | def newest_image(list_of_images): 101 | latest = None 102 | for image in list_of_images: 103 | if not latest: 104 | latest = image 105 | continue 106 | if parser.parse(image['CreationDate']) > parser.parse(latest['CreationDate']): 107 | latest = image 108 | return latest 109 | 110 | client = boto3.client('ec2', region_name=region) 111 | 112 | filters = [{'Name': 'name', 'Values': ['amzn-ami-hvm-*']}, {'Name': 'description', 'Values': ['Amazon Linux AMI*']}, {'Name': 'architecture', 'Values': ['x86_64']}, {'Name': 'owner-alias', 'Values': ['amazon']}] 113 | response = client.describe_images(Owners=['amazon'], Filters=filters) 114 | 115 | ami = newest_image(response['Images']) 116 | responseData = {'ImageId': ami['ImageId']} 117 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) 118 | except Exception as e: 119 | responseData = {'Error': str(e)} 120 | cfnresponse.send(event, context, cfnresponse.FAILED, responseData) 121 | 122 | # Create AMI Snapshot 123 | CustomCreateSnap: 124 | Type: Custom::CreateSnap 125 | Version: 1.0 126 | Properties: 127 | ServiceToken: !GetAtt LambdaCreateSnap.Arn 128 | InstanceId: !Ref EC2Instance 129 | Region: !Ref 'AWS::Region' 130 | LambdaCreateSnap: 131 | Type: "AWS::Lambda::Function" 132 | Properties: 133 | Handler: index.lambda_handler 134 | Role: !GetAtt ExecutionRole.Arn 135 | Runtime: python3.6 136 | Timeout: 120 137 | Code: 138 | ZipFile: | 139 | import boto3 140 | import cfnresponse 141 | 142 | def lambda_handler(event, context): 143 | try: 144 | if event['RequestType'] == 'Delete': 145 | responseData = {'Delete': 'SUCCESS'} 146 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) 147 | else: 148 | instance = event['ResourceProperties']['InstanceId'] 149 | region = event['ResourceProperties']['Region'] 150 | 151 | client = boto3.client('ec2', region_name=region) 152 | 153 | getvolume = client.describe_instances( 154 | InstanceIds=[instance], 155 | ) 156 | 157 | createami = client.create_image( 158 | InstanceId=instance, 159 | Name=instance, 160 | NoReboot=True 161 | ) 162 | responseData = {'AmiId': createami['ImageId'], 'VolumeId': getvolume['Reservations'][0]['Instances'][0]['BlockDeviceMappings'][0]['Ebs']['VolumeId']} 163 | cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData) 164 | except Exception as e: 165 | responseData = {'Error': str(e)} 166 | cfnresponse.send(event, context, cfnresponse.FAILED, responseData) 167 | 168 | # IAM Execution Role for Lambda Functions 169 | ExecutionRole: 170 | Type: "AWS::IAM::Role" 171 | Properties: 172 | AssumeRolePolicyDocument: 173 | Version: 2008-10-17 174 | Statement: 175 | - 176 | Effect: Allow 177 | Principal: 178 | Service: 179 | - events.amazonaws.com 180 | - lambda.amazonaws.com 181 | - states.amazonaws.com 182 | Action: 183 | - sts:AssumeRole 184 | ManagedPolicyArns: 185 | - arn:aws:iam::aws:policy/AdministratorAccess 186 | 187 | Outputs: 188 | EC2Instance: 189 | Description: EC2 Instance 190 | Value: !Ref EC2Instance 191 | EIP: 192 | Description: Elastic IP 193 | Value: !Ref EIP0 194 | VolumeId: 195 | Description: Volume ID for mock PHD Event 196 | Value: !GetAtt CustomCreateSnap.VolumeId 197 | AMI: 198 | Description: AMI created for backup 199 | Value: !GetAtt CustomCreateSnap.AmiId -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/SydneySummitDemo/phd-mock.payload.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "DetailType": "AWS Health Event", 4 | "Source": "awshealth.mock", 5 | "Time": "2018-04-01T00:55:52Z", 6 | "Resources": [ 7 | "vol-xxxxxxxxxxxxxxxxx" 8 | ], 9 | "Detail": "{\"eventArn\":\"arn:aws:health:ap-southeast-2::event/event-1486428952619\",\"service\":\"EBS\",\"eventTypeCode\":\"AWS_EBS_VOLUME_LOST\",\"eventTypeCategory\":\"issue\",\"startTime\":\"Tue, 7 Feb 2017 00:55:52 GMT\",\"eventDescription\":[{\"language\":\"en_US\",\"latestDescription\":\".\"}],\"affectedEntities\":[{\"entityValue\":\"vol-xxxxxxxxxxxxxxxxx\"}]}" 10 | } 11 | ] -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/SydneySummitDemo/readme.md: -------------------------------------------------------------------------------- 1 | ## AWS Health AWS_EBS_VOLUME_LOST 2 | 3 | **Note:** This instruction is deprecated. Please refer to the [stepbystep/README](https://github.com/aws/aws-health-tools/blob/master/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/README.md) for the latest instruction. 4 | 5 | --- 6 | 7 | ### Description 8 | Underlying hardware related to your EBS volume has failed, and the data associated with the volume is unrecoverable. 9 | If you have an EBS snapshot of the volume, you need to restore that volume from your snapshot. 10 | This tools checks if the failed volume has a snapshot and is part of a root volume on an EC2 instance. 11 | Tool will restore the instance root volume from latest snapshot automatically if it does, and upload the results to an Elasticsearch instance. 12 | Notification on update will be sent to SNS topic assigned. 13 | 14 | ### Core Functionality Stack > [![Launch EBS VOLUME LOST Stack into N. Virginia with CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/images/cloudformation-launch-stack-button.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=AWSEBSVolLost&templateURL=https://s3.amazonaws.com/aws-health-tools-assets/cloudformation-templates/aws_ebs_vol_lost_cfn.yaml) 15 | 16 | ### Important App Stack > [![Launch IMPORTANT APP Stack into N. Virginia with CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/images/cloudformation-launch-stack-button.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=AWSEBSVolLost&templateURL=https://s3.amazonaws.com/aws-health-tools-assets/cloudformation-templates/aws_ebs_vol_lost_importantapp-cfn.yaml) 17 | 18 | ### Setup 19 | 1. Launch the CloudFormation Core Functionality Stack (**aws_ebs_vol_lost_cfn.yaml**). 20 | * This template will build out the required Step and Lambda functions that will action a Personal Health Dashboard event. It also creates a small Elasticsearch domain for visualisation. 21 | 1. Launch the CloudFormation Important App stack (**aws_ebs_vol_lost_importantapp-cfn.yaml**). 22 | * This template will build out a mock application that will be impacted by an EBS service disruption. 23 | 24 | ##### Creating a Mock Event 25 | 26 | 1. With both CloudFormation stacks completed - copy the **VolumeId** from the Outputs of the **Important App** stack. 27 | 1. Replace all **vol-xxxxxxxxxxxxxxxxx** values in the **phd-mock-payload.json** with the copied value. 28 | 1. Modifiy the **time** to within the past 14 days in **phd-mock-event.json**. 29 | 1. Post the mock event to CloudWatch using the AWS CLI command **aws events put-events --entries file://phd-mock-event.json** - this will trigger a CloudWatch Rule that will in turn launch the Step Function to replace the volume. 30 | 1. Open the Kibana dashboard (the URL can be found in the Outputs of the **Core Functionality** Stack) 31 | 1. In Kibana, under **Management > Index Patterns**, create an index pattern named **phd-events** using **PhdEventTime** as the **Time Filter**. 32 | 1. Under **Management > Saved Objects**, import **elasticsearch-objects.json**, overwriting all objects, and using **phd-events** as the new index pattern. 33 | 1. Navigate to **Dashboard > PHD Events** to see the event(s). 34 | 1. Repeat steps 1 to 4 to create additional mock events. 35 | 36 | #### CloudFormation 37 | Choose **Launch Stack** to launch the CloudFormation template in the US East (N. Virginia) Region in your account: 38 | 39 | The **Core Functionality** CloudFormation template requires the following parameters: 40 | * *SNSTopicName* - Enter the SNS topic to send notification to - this must exist in US East (N. Virginia) region 41 | * *PublicCidr* - The public IP from which Kibana will be accessible 42 | * *SubnetIds* - Two public subnets for Kibana access 43 | * *VpcId* - The VPC to which the subnets belong 44 | 45 | The **Important App** CloudFormation template requires the following parameters: 46 | * *InstanceType* - The size of the EC2 Instance 47 | * *KeyName* - The Keypair used to access the EC2 Instance 48 | * *RestoreImageId* - Leave blank. This is used by the Step Function for automatic replacement 49 | * *SSHLocation* - The public IP from wich the EC2 Instance wil be accessible 50 | * *SubnetId* - The subnet in which the EC2 Instance will reside 51 | * *VpcId* - The VPC to which the subnet belongs 52 | 53 | #### Disclaimer 54 | 55 | These CloudFormation templates are for demo and proof-of-concept purposes only. They and are not intended for production environments. Amongst other deficiencies, they: 56 | * do not follow the rule of least privileged access, and will create IAM Roles with the 'AdministratorAccess' AWS Managed policy 57 | * will serve public traffic from the Elasticsearch domain over unencrypted HTTP connections 58 | 59 | ### License 60 | AWS Health Tools are licensed under the Apache 2.0 License. 61 | -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/mock_payload.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "DetailType": "AWS Health Event", 4 | "Source": "awshealth.mock", 5 | "Time": "2017-02-07T00:55:52Z", 6 | "Resources": [ 7 | "vol-04e5b75b51ba5e835" 8 | ], 9 | "Detail": "{\"eventArn\":\"arn:aws:health:ap-southeast-2::event/event-1486428952619\",\"service\":\"EBS\",\"eventTypeCode\":\"AWS_EBS_VOLUME_LOST\",\"eventTypeCategory\":\"issue\",\"startTime\":\"Tue, 7 Feb 2017 00:55:52 GMT\",\"eventDescription\":[{\"language\":\"en_US\",\"latestDescription\":\".\"}],\"affectedEntities\":[{\"entityValue\":\"vol-04e5b75b51ba5e835\"}]}" 10 | } 11 | ] -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/readme.md: -------------------------------------------------------------------------------- 1 | ## AWS Health AWS_EBS_VOLUME_LOST 2 | 3 | ### Description 4 | Underlying hardware related to your EBS volume has failed, and the data associated with the volume is unrecoverable. 5 | If you have an EBS snapshot of the volume, you need to restore that volume from your snapshot. 6 | This tools checks if the failed volume has a snapshot and is part of a root volume on an EC2 instance. 7 | Tool will restore the instance root volume from latest snapshot automatically if it does. 8 | Notification on update will be sent to SNS topic assigned. 9 | 10 | [![Launch Stack into N. Virginia with CloudFormation](http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/images/cloudformation-launch-stack-button.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=AWSEBSVolLost&templateURL=https://s3.amazonaws.com/aws-health-tools-assets/cloudformation-templates/aws_ebs_vol_lost_cloudformation.yaml) 11 | 12 | ### Setup 13 | 1. Launch a cloudformation stack from template in region where you would like to monitor the volume : aws_ebs_vol_lost_cloudformation.yaml. 14 | 2. Place in SNS topic to send update to SNSTopic parameter. 15 | 16 | 17 | #### CloudFormation 18 | Choose **Launch Stack** to launch the CloudFormation template in the US East (N. Virginia) Region in your account: 19 | 20 | The CloudFormation template requires the following parameters: 21 | 22 | *SNS topic* - Enter the SNS topic to send notification to. 23 | 24 | ### License 25 | AWS Health Tools are licensed under the Apache 2.0 License. 26 | -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/alexa_skill.py: -------------------------------------------------------------------------------- 1 | import json 2 | import datetime 3 | import urllib.request 4 | import dateutil.parser 5 | import math 6 | import os 7 | 8 | WELCOME_MESSAGE = ("Welcome to Production Environment! You can ask me about your production environment status!") 9 | EXIT_SKILL_MESSAGE = "Thank you! Enjoy the rest of your summit!" 10 | HELP_MESSAGE = ("I know stuff about your production environment! Ask away!") 11 | STATE_START = "Start" 12 | STATE = STATE_START 13 | 14 | date_handler = lambda obj: obj.strftime('%Y-%m-%d %H:%M:%S') 15 | 16 | def getLatestPhdEvent(): 17 | es = "http://"+os.environ['ESELB'] 18 | index = 'phd-full-events' 19 | query = { 20 | "size": 1, 21 | "sort": [ 22 | { 23 | "PhdEventTime": { 24 | "order": "desc" 25 | } 26 | } 27 | ] 28 | } 29 | 30 | # Elasticsearch Request/Response 31 | payload = json.dumps(query).encode('utf-8') # Encode query for HTTP request 32 | request = urllib.request.Request(es + '/' + index + '/_search', payload, {'Content-Type': 'application/json'}, method='GET') # Build HTTP request 33 | response = urllib.request.urlopen(request).read() # Send Request 34 | response = json.loads(response.decode('utf-8')) # Decode response and convert to JSON 35 | return response['hits']['hits'][0]['_source'] # Return query payload 36 | 37 | # --------------- entry point ----------------- 38 | 39 | def lambda_handler(event, context): 40 | print(event) 41 | 42 | """ App entry point """ 43 | if event['request']['type'] == "LaunchRequest": 44 | return on_launch() 45 | elif event['request']['type'] == "IntentRequest": 46 | return on_intent(event['request'], event['session']) 47 | elif event['request']['type'] == "SessionEndedRequest": 48 | return on_session_ended(event['request']) 49 | 50 | 51 | # --------------- response handlers ----------------- 52 | 53 | def on_intent(request, session): 54 | """ Called on receipt of an Intent """ 55 | 56 | intent = request['intent'] 57 | intent_name = request['intent']['name'] 58 | 59 | #print("on_intent " +intent_name) 60 | get_state(session) 61 | 62 | if 'dialogState' in request: 63 | #delegate to Alexa until dialog sequence is complete 64 | if request['dialogState'] == "STARTED" or request['dialogState'] == "IN_PROGRESS": 65 | print (request['dialogState']) 66 | return dialog_response("", False) 67 | 68 | if intent_name == "GetNewEventIntent": 69 | return get_event() 70 | elif intent_name == "AMAZON.HelpIntent": 71 | return do_help() 72 | elif intent_name == "AMAZON.StopIntent": 73 | return do_stop() 74 | elif intent_name == "AMAZON.CancelIntent": 75 | return do_stop() 76 | else: 77 | print("invalid intent reply with help") 78 | return do_help() 79 | 80 | 81 | def do_stop(): 82 | attributes = {"state":globals()['STATE']} 83 | return response(attributes, response_plain_text(EXIT_SKILL_MESSAGE, True)) 84 | 85 | def do_help(): 86 | global STATE 87 | STATE = STATE_START 88 | attributes = {"state":globals()['STATE']} 89 | return response(attributes, response_plain_text(HELP_MESSAGE, True)) 90 | 91 | def on_launch(): 92 | return get_welcome_message() 93 | 94 | def on_session_ended(request): 95 | if request['reason']: 96 | end_reason = request['reason'] 97 | print("on_session_ended reason: " + end_reason) 98 | 99 | def get_state(session): 100 | """ get and set the current state """ 101 | 102 | global STATE 103 | 104 | if 'attributes' in session: 105 | if 'state' in session['attributes']: 106 | STATE = session['attributes']['state'] 107 | else: 108 | STATE = STATE_START 109 | else: 110 | STATE = HELP_MESSAGE 111 | 112 | 113 | # --------------- response string formatters ----------------- 114 | def get_welcome_message(): 115 | attributes = {"state":globals()['STATE']} 116 | return response(attributes, response_plain_text(WELCOME_MESSAGE, False)) 117 | 118 | def getDateTimeFromISO8601String(s): 119 | d = dateutil.parser.parse(s) 120 | return d 121 | 122 | def get_event(): 123 | attributes = {"state":globals()['STATE']} 124 | 125 | payload = getLatestPhdEvent() 126 | print(payload) 127 | 128 | ## Get Time ## 129 | x = payload['PhdEventTime'] 130 | timeiso = getDateTimeFromISO8601String(x) 131 | 132 | ## Convert to AU/Melbourne ## 133 | y = dateutil.parser.parse(x) 134 | meltimeiso = y + datetime.timedelta(hours=int(os.environ['timezonedelta'])) 135 | eventtimestr = json.dumps(meltimeiso, default = date_handler) 136 | 137 | eventtime = datetime.datetime.strptime(eventtimestr.replace("\"", ""), "%Y-%m-%d %H:%M:%S") 138 | systemname = payload['ResourceStack']['StackName'] 139 | eventid = payload['PhdEventId'] 140 | recoverytime = payload['RestoredResources']['RestoredVolumes'][0]['CreateTime'] 141 | recoverystatus = payload['NOTIFMESSAGE']['Message'] 142 | 143 | # Compose Event time 144 | eventdate = str(eventtime.year) + "-" + str(eventtime.month) + "-" + str(eventtime.day) 145 | eventtimestr = str(eventtime.hour) + ":" + str(eventtime.minute) 146 | dtime = datetime.datetime.strptime(eventtimestr, "%H:%M") 147 | eventtime = dtime.strftime("%I:%M %p") 148 | 149 | # Find Recovery Time 150 | reventlist = payload['ResourceStack']['StackEvents'] 151 | for revent in reventlist: 152 | if revent['ResourceType'] == "AWS::CloudFormation::Stack": 153 | if revent['ResourceStatus'] == "UPDATE_COMPLETE": 154 | rendtime = revent['Timestamp'] 155 | 156 | endtime = getDateTimeFromISO8601String(rendtime) 157 | diff = endtime - timeiso 158 | diffseconds = diff.total_seconds() 159 | diffhours = diffseconds // 3600 160 | diffminutes = (diffseconds % 3600) // 60 161 | diffseconds = diffseconds % 60 162 | recoveryhours = str(math.ceil(diffhours)) 163 | recoveryminutes = str(math.ceil(diffminutes)) 164 | recoveryseconds = str(math.ceil(diffseconds)) 165 | 166 | LATEST_EVENT = ( "On "+ eventdate + " at " + eventtime + "! System " + systemname + " was down! " + "System is now recovered ! " + " Total Recovery time is " + recoveryhours + " hours " + recoveryminutes + " minutes and " + recoveryseconds + " seconds " + "! Please check kibana for recovery details!") 167 | return response(attributes, response_plain_text(LATEST_EVENT, True)) 168 | 169 | def response(attributes, speech_response): 170 | """ create a simple json response """ 171 | 172 | return { 173 | 'version': '1.0', 174 | 'sessionAttributes': attributes, 175 | 'response': speech_response 176 | } 177 | 178 | def response_plain_text(output, endsession): 179 | """ create a simple json plain text response """ 180 | 181 | return { 182 | 'outputSpeech': { 183 | 'type': 'PlainText', 184 | 'text': output 185 | }, 186 | 'shouldEndSession': endsession 187 | } 188 | -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/create_AWSServiceRoleForAmazonElasticsearchService.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Resources": { 4 | "RootRole": { 5 | "Type": "AWS::IAM::Role", 6 | "Properties": { 7 | "RoleName":"AWSServiceRoleForAmazonElasticsearchService", 8 | "AssumeRolePolicyDocument": { 9 | "Version" : "2012-10-17", 10 | "Statement": [ { 11 | "Effect": "Allow", 12 | "Principal": { 13 | "Service": [ "es.amazonaws.com" ] 14 | }, 15 | "Action": [ "sts:AssumeRole" ] 16 | } ] 17 | }, 18 | "Path": "/aws-service-role/es.amazonaws.com/", 19 | "Policies": [ { 20 | "PolicyName": "root", 21 | "PolicyDocument": { 22 | "Version": "2012-10-17", 23 | "Statement": [ 24 | { 25 | "Action": [ 26 | "ec2:CreateNetworkInterface", 27 | "ec2:DeleteNetworkInterface", 28 | "ec2:DescribeNetworkInterfaces", 29 | "ec2:ModifyNetworkInterfaceAttribute", 30 | "ec2:DescribeSecurityGroups", 31 | "ec2:DescribeSubnets", 32 | "ec2:DescribeVpcs" 33 | ], 34 | "Effect": "Allow", 35 | "Resource": "*" 36 | } 37 | ] 38 | } 39 | } ] 40 | } 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Alexa_Skill_Output.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Alexa_Skill_Output.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/CreateAlexaSkills.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/CreateAlexaSkills.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/EC2_App.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/EC2_App.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/ES_Proxy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/ES_Proxy.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Even_Odd_State_Machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Even_Odd_State_Machine.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Intro.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Kibana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Kibana.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/StepFunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/StepFunction.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_1_1.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_1_Sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_1_Sol.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_2_Sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_2_Sol.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_4_Sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_4_Sol.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_Bonus_Sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/images/Step_Bonus_Sol.png -------------------------------------------------------------------------------- /automated-actions/AWS_EBS_VOLUME_LOST/stepbystep/mockpayload.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "DetailType": "AWS Health Event", 4 | "Source": "awshealth.mock", 5 | "Time": "2018-11-27T01:30:00Z", 6 | "Resources": [ 7 | "<>" 8 | ], 9 | "Detail": "{\"eventArn\":\"arn:aws:health:us-east-1::event/event-1486428952619\",\"service\":\"EBS\",\"eventTypeCode\":\"AWS_EBS_VOLUME_LOST\",\"eventTypeCategory\":\"issue\",\"startTime\":\"Tue, 27 Nov 2018 01:30:00 GMT\",\"eventDescription\":[{\"language\":\"en_US\",\"latestDescription\":\"Your volume experienced a failure due to multiple component failures and we were unable to recover it. Although EBS volumes are designed for reliability, including being backed by multiple physical drives, we are still exposed to durability risks when multiple concurrent component failures occur before we are able to restore redundancy. We publish our durability expectations on the EBS detail page here (http://aws.amazon.com/ebs/details).\\n\\nFind out what you can do to fix this issue at https://aws.amazon.com/premiumsupport/knowledge-center/ebs-error-status/\\n\\nWe apologize for the inconvenience this may have caused you. If you have any further questions or comments regarding this matter, please contact us at: http://aws.amazon.com/support\"}],\"affectedEntities\":[{\"entityValue\":\"<>\"}]}" 10 | } 11 | ] -------------------------------------------------------------------------------- /automated-actions/AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1477516473539", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "arn:aws:logs:*:*:*" 13 | }, 14 | { 15 | "Sid": "Stmt1477680111144", 16 | "Action": [ 17 | "ec2:StopInstances" 18 | ], 19 | "Effect": "Allow", 20 | "Resource": "*" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /automated-actions/AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED/LambdaFunction.js: -------------------------------------------------------------------------------- 1 | // Sample Lambda Function to stop EC2 instances when AWS Health AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED events are generated. This is useful for situations where there is data redundancy and automated launch of instnaces (e.g. via Autoscaling). 2 | var AWS = require('aws-sdk'); 3 | 4 | // define configuration 5 | const tagKey = process.env.TAG_KEY; 6 | const tagValue = process.env.TAG_VALUE; 7 | const action = process.env.EC2_ACTION; 8 | const dryRun = process.env.DRY_RUN; 9 | 10 | function getMatchingInstances(affectedEntities){ 11 | //initialize an empty array 12 | var instances = []; 13 | // loop through entities 14 | for ( var i=0; i < affectedEntities.length; i+=1 ) 15 | { 16 | var instanceId = affectedEntities[i].entityValue; 17 | // check that the instance has tags 18 | if (typeof (affectedEntities[i].tags) != 'undefined') { 19 | // check that tags match 20 | if (affectedEntities[i].tags[[tagKey]] == tagValue){ 21 | // add instanceid to the array 22 | instances.push(instanceId); 23 | } 24 | else console.log ('The following instance does not match the configured tag: ', instanceId); 25 | } 26 | else console.log ('The following instance does not match the configured tag: ', instanceId); 27 | } 28 | return instances 29 | } 30 | 31 | function setupClient(region){ 32 | // set the region for the sdk 33 | AWS.config.update({region: region}); 34 | //create the ec2 client 35 | return new AWS.EC2(); 36 | } 37 | 38 | function getParams(instances, dryRun){ 39 | // setup parameters 40 | var instancesParams = { 41 | InstanceIds: instances, 42 | DryRun: false 43 | }; 44 | // enable DryRun if set in environment variables 45 | if (dryRun == 'true') { 46 | instancesParams.DryRun = true; 47 | console.log() 48 | } 49 | return instancesParams 50 | } 51 | 52 | //main function which gets AWS Health data from Cloudwatch event 53 | exports.handler = (event, context, callback) => { 54 | console.log(event); 55 | console.log(JSON.stringify(event)); 56 | // function to handle ec2 API response 57 | function handleResponse(err, data) { 58 | if (err) { // an error occurred 59 | if (err.code == 'DryRunOperation') { 60 | console.log(instances, region, err.message); 61 | callback(null, awsHealthSuccessMessage); 62 | } else { 63 | console.log(instances, region, err, err.stack); 64 | throw err; 65 | } 66 | 67 | } else { 68 | console.log(`Instance ${action}: `, instances, region); 69 | //return success 70 | callback(null, awsHealthSuccessMessage); 71 | } // successful response 72 | } 73 | 74 | //extract details from Cloudwatch event 75 | var eventName = event.detail.eventTypeCode; 76 | var affectedEntities = event.detail.affectedEntities; 77 | var region = event.region; 78 | 79 | const awsHealthSuccessMessage = `Successfully got details from AWS Health event, ${eventName} and executed automated action.`; 80 | 81 | // get affected instances that match the required tags 82 | instances = getMatchingInstances(affectedEntities); 83 | 84 | if (instances.length > 0) { //there are some instances to take action on 85 | 86 | //create an ec2 api client in the event's region 87 | var ec2 = setupClient(region); 88 | 89 | // setup parameters 90 | var instancesParams = getParams(instances, dryRun); 91 | 92 | console.log (`attempting to ${action} the following instances: `, instances); 93 | // Call either the Terminate or the Stop API 94 | if (action == 'Terminate') ec2.terminateInstances(instancesParams, handleResponse); 95 | else ec2.stopInstances(instancesParams, handleResponse); 96 | 97 | } else { 98 | console.log('No instances in the event match the required tags, exiting without any action'); 99 | callback(null, awsHealthSuccessMessage); 100 | } 101 | 102 | }; 103 | -------------------------------------------------------------------------------- /automated-actions/AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED 2 | 3 | ### Description 4 | EC2 has detected a performance degradation of one or more physical storage drives that backs the instance store volumes of your Amazon EC2 instance. Because of this degradation, some instance store volumes could be unresponsive or exhibit poor performance. 5 | 6 | ### Setup and Usage 7 | You can automatically stop or terminate EC2 instances that have degraded instance-store performance using Amazon Cloudwatch events and AWS Lambda. 8 | 9 | #### CloudFormation 10 | Choose **Launch Stack** to launch the CloudFormation template in the US East (N. Virginia) Region in your account: 11 | 12 | [![Launch AWS Health Automated Action](../../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=EC2InstancStopuponInstanceStoreDegradation&templateURL=https://s3.amazonaws.com/aws-health-tools-assets/cloudformation-templates/aa-instance-store-degraded.json) 13 | 14 | The CloudFormation template requires the following parameters: 15 | 16 | *Action to take* - Whether to Stop or Terminate affected instances 17 | 18 | *Tag Key* - Instances must have a matching tag key and value, this is the key to match 19 | 20 | *Tag Value* - the tag value to match 21 | 22 | *Dry Run* - Set to true for testing. setting this to true will run the requested EC2 API in DryRun mode, not actually stopping/terminating instances 23 | 24 | #### Manual setup 25 | 26 | 1. Create an IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 27 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 28 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 29 | 30 | 2. Create a Lambda JavaScript function by using the [sample](LambdaFunction.js) provided and choose the IAM role created in step 1. The sample Lambda function will stop EC2 instances when AWS Health AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED events are generated. This is useful for situations where there is data redundancy and fault tolerance (for example, when using Auto Scaling). Be sure to set the appropriate tags and region in the configuration section of the Lambda function. 31 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 32 | 33 | 3. Create a CloudWatch Events rule to trigger the Lambda function created in step 2 matching the AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED event. 34 | Documentation on how to create an AWS Health CloudWatch Events rule is available here: http://docs.aws.amazon.com/health/latest/ug/cloudwatch-events-health.html 35 | 36 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 37 | 38 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing the example and tailoring it to your environment before using it in your production environment. 39 | 40 | ### License 41 | AWS Health Tools are licensed under the Apache 2.0 License. 42 | -------------------------------------------------------------------------------- /automated-actions/AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED/testevent.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "7bf73129-1428-4cd3-a780-95db273d1602", 4 | "detail-type": "AWS Health Event", 5 | "source": "aws.health", 6 | "account": "123456789012", 7 | "time": "2016-06-05T06:27:57Z", 8 | "region": "us-west-2", 9 | "resources": [ 10 | "i-abcd1111" 11 | ], 12 | "detail": { 13 | "eventArn": "arn:aws:health:us-west-2::event/AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED_90353408594353980", 14 | "service": "EC2", 15 | "eventTypeCode": "AWS_EC2_INSTANCE_STORE_DRIVE_PERFORMANCE_DEGRADED", 16 | "eventTypeCategory": "issue", 17 | "startTime": "Sat, 05 Jun 2016 15:10:09 GMT", 18 | "eventDescription": [ 19 | { 20 | "language": "en_US", 21 | "latestDescription": "A description of the event will be provided here" 22 | } 23 | ], 24 | "affectedEntities": [ 25 | { 26 | "entityValue": "i-abcd1111", 27 | "tags": { 28 | "stage": "prod", 29 | "app": "my-app" 30 | } 31 | } 32 | ] 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /automated-actions/AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1477516473539", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "arn:aws:logs:*:*:*" 13 | }, 14 | { 15 | "Sid": "Stmt1477680111144", 16 | "Action": [ 17 | "ec2:DescribeNetworkInterfaces", 18 | "ec2:DeleteNetworkInterface" 19 | ], 20 | "Effect": "Allow", 21 | "Resource": "*" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /automated-actions/AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED/LambdaFunction.js: -------------------------------------------------------------------------------- 1 | // Sample Lambda Function to remove unattached ENIs in the region of the event when AWS Health AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED events are generated. 2 | // This is useful for situations where you might have leftover ENIs that are not used and are preventing load balancer scaling 3 | 'use strict'; 4 | var AWS = require('aws-sdk'); 5 | const dryRun = ((process.env.DRY_RUN || 'true') == 'true'); 6 | const maxEniToProcess = process.env.MAX_ENI || 100; 7 | var ec2 = null; // scoping object so both functions can see it 8 | 9 | //main function which gets AWS Health data from Cloudwatch event 10 | exports.handler = (event, context, callback) => { 11 | //extract details from Cloudwatch event 12 | var eventName = event.detail.eventTypeCode; 13 | var region = event.region; 14 | const awsHealthSuccessMessage = `Successfully got details from AWS Health event ${eventName} and executed automated action in ${region}. Further details in CloudWatch Logs.`; 15 | 16 | // we only need to run this automation once per invocation since the issue 17 | // of ENI exhaustion is regional and not dependent on the load balancers in the alert 18 | // Event will only trigger for one region so we don't have to loop that 19 | AWS.config.update({region: region}); 20 | AWS.config.update({maxRetries: 3}); 21 | ec2 = new AWS.EC2(); // creating the object now that we know event region 22 | 23 | console.log ('Getting the list of available ENI in region %s', region); 24 | var params = { 25 | Filters: [{Name: 'status',Values: ['available']}] 26 | }; 27 | 28 | ec2.describeNetworkInterfaces(params, function(err, data) { 29 | if (err) 30 | { 31 | console.log( region, err, err.stack); // an error occurred 32 | callback('Error describing ENIs; check CloudWatch Logs for details'); 33 | } 34 | else 35 | { 36 | var numberToProcess = data.NetworkInterfaces.length; 37 | if ((maxEniToProcess > 0) && (data.NetworkInterfaces.length > maxEniToProcess)) numberToProcess = maxEniToProcess; 38 | console.log('Found %s available ENI; processing %s',data.NetworkInterfaces.length,numberToProcess); 39 | // for each interface, remove it 40 | for ( var i=0; i < numberToProcess; i+=1) 41 | { 42 | deleteNetworkInterface(data.NetworkInterfaces[i].NetworkInterfaceId,dryRun); 43 | } 44 | 45 | callback(null, awsHealthSuccessMessage); //return success 46 | } 47 | }); 48 | }; 49 | 50 | //This function removes an ENI 51 | function deleteNetworkInterface (networkInterfaceId, dryrun) { 52 | console.log ('Running code to delete ENI %s with Dry Run set to %s', networkInterfaceId, dryrun); 53 | var deleteNetworkInterfaceParams = { 54 | NetworkInterfaceId: networkInterfaceId, 55 | DryRun: dryrun 56 | }; 57 | ec2.deleteNetworkInterface(deleteNetworkInterfaceParams, function(err, data) { 58 | if (err) 59 | { 60 | switch (err.code) 61 | { 62 | case 'DryRunOperation': 63 | console.log('Dry run attempt complete for %s after %s retries', networkInterfaceId, this.retryCount); 64 | break; 65 | case 'RequestLimitExceeded': 66 | console.log('Request limit exceeded while processing %s after %s retries', networkInterfaceId, this.retryCount); 67 | break; 68 | default: 69 | console.log(networkInterfaceId, err, err.stack); 70 | } 71 | } 72 | else console.log('ENI %s deleted after %s retries', networkInterfaceId, this.retryCount); // successful response 73 | }); 74 | } -------------------------------------------------------------------------------- /automated-actions/AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED 2 | 3 | ### Description 4 | An ELB has attempted to change load balancer nodes (for example, to scale) and this operation has been impacted by a lack of available ENIs in the associated region. ELB needs available ENIs to successfully do node operations. Clearing this up requires either freeing up ENIs (such as by deleting unattached ENIs), terminating instances or requesting a limit increase via http://docs.aws.amazon.com/general/latest/gr/aws_service_limits.html. 5 | 6 | BE CAREFUL: This script, if so configured, will delete unattached ENIs from your environment. If you regularly leave ENIs unattached for a reason, use caution here. 7 | 8 | ### Setup and Usage 9 | 10 | #### Cloudformation Setup 11 | Choose **Launch Stack** to launch the template in the US East (N. Virginia) Region in your account: 12 | 13 | [![Launch AWS Health Code Elastic Load Balancing ENI Limit Reached](../../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=AWSHealthElasticLoadBalancingENILimitReached&templateURL=https://s3.amazonaws.com/aws-health-tools-assets/cloudformation-templates/AWSHealthElasticLoadBalancingENILimitReached.json) 14 | 15 | Setting the Dry Run parameter to true (the default) will keep the script from actually doing deletions. Set it to false to enable deletion. 16 | Setting the Max ENI parameter (default 100) to a value higher than zero will limit the number of ENIs processed. Setting it to zero will cause the script to process all of the found unattached ENIs. Care should be used here as you can end up with the API calls being throttled by EC2. The script is configured for a limited number of retry attempts and in development testing 100 was found to be a good reliable value for Max ENIs. 17 | 18 | #### Manual Setup 19 | You can automatically delete unattached ENIs using Amazon Cloudwatch events and AWS Lambda using the following instructions: 20 | 21 | 1. Create an IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 22 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 23 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 24 | 25 | 2. Create a Lambda JavaScript function by using the [sample](LambdaFunction.js) provided and choose the IAM role created in step 1. The sample Lambda function will query for unattached ENIs when AWS Health AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED events are generated and delete unattached ENIs. This is useful for situations where you might have created ENIs for testing purposes but forgotten to remove them. 26 | 27 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 28 | 29 | 3. Create a CloudWatch Events rule to trigger the Lambda function created in step 2 matching the AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED event. 30 | Documentation on how to create an AWS Health CloudWatch Events rule is available here: http://docs.aws.amazon.com/health/latest/ug/cloudwatch-events-health.html 31 | 32 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 33 | 34 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing the example and tailoring it to your environment before using it in your production environment. 35 | 36 | #### Testing structure 37 | You can use the following for testing the function via the Lambda console. 38 | Replace the region us-east-2 with the region containing your target ENIs. 39 | 40 | ``` 41 | { 42 | "region": "us-east-2", 43 | "detail": {"eventTypeCode": "AWS_ELASTICLOADBALANCING_ENI_LIMIT_REACHED"} 44 | } 45 | ``` 46 | 47 | ### License 48 | AWS Health Tools are licensed under the Apache 2.0 License. 49 | 50 | -------------------------------------------------------------------------------- /automated-actions/AWS_RISK_CREDENTIALS_EXPOSED/images/Architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_RISK_CREDENTIALS_EXPOSED/images/Architecture.png -------------------------------------------------------------------------------- /automated-actions/AWS_RISK_CREDENTIALS_EXPOSED/images/VisualWorkflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/automated-actions/AWS_RISK_CREDENTIALS_EXPOSED/images/VisualWorkflow.png -------------------------------------------------------------------------------- /automated-actions/AWS_RISK_CREDENTIALS_EXPOSED/lambda_functions/delete_access_key_pair.py: -------------------------------------------------------------------------------- 1 | import boto3 2 | 3 | iam = boto3.client('iam') 4 | 5 | 6 | def lambda_handler(event, context): 7 | account_id = event['account'] 8 | time_discovered = event['time'] 9 | details = event['detail'] 10 | access_key_id = details['affectedEntities'][0]['entityValue'] 11 | print('Looking up username for access key pair...') 12 | username = get_username_from_key(access_key_id) 13 | print('Deleting exposed access key pair...') 14 | delete_exposed_key_pair(username, access_key_id) 15 | return { 16 | "account_id": account_id, 17 | "time_discovered": time_discovered, 18 | "username": username, 19 | "deleted_key": access_key_id 20 | } 21 | 22 | 23 | def get_username_from_key(access_key_id): 24 | """ Retrieves username last associated with specified IAM access key ID. 25 | 26 | Args: 27 | access_key_id (string): IAM access key ID to lookup user with. 28 | 29 | Returns: 30 | (string) 31 | Username last associated with specified IAM access key ID. 32 | 33 | """ 34 | try: 35 | response = iam.get_access_key_last_used( 36 | AccessKeyId=access_key_id 37 | ) 38 | except Exception as e: 39 | print(e) 40 | print('Unable to retrieve username for access key "{}".'.format(access_key_id)) 41 | raise(e) 42 | return response['UserName'] 43 | 44 | 45 | def delete_exposed_key_pair(username, access_key_id): 46 | """ Deletes IAM access key pair identified by access key ID for specified user. 47 | 48 | Args: 49 | username (string): Username of IAM user to delete key pair for. 50 | access_key_id (string): IAM access key ID to identify key pair to delete. 51 | 52 | Returns: 53 | (None) 54 | 55 | """ 56 | try: 57 | iam.delete_access_key( 58 | UserName=username, 59 | AccessKeyId=access_key_id 60 | ) 61 | except Exception as e: 62 | print(e) 63 | print('Unable to delete access key "{}" for user "{}".'.format(access_key_id, username)) 64 | raise(e) 65 | -------------------------------------------------------------------------------- /automated-actions/AWS_RISK_CREDENTIALS_EXPOSED/lambda_functions/lookup_cloudtrail_events.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import collections 3 | import boto3 4 | 5 | cloudtrail = boto3.client('cloudtrail') 6 | 7 | 8 | def lambda_handler(event, context): 9 | account_id = event['account_id'] 10 | time_discovered = event['time_discovered'] 11 | username = event['username'] 12 | deleted_key = event['deleted_key'] 13 | endtime = datetime.datetime.now() # Create start and end time for CloudTrail lookup 14 | interval = datetime.timedelta(hours=24) 15 | starttime = endtime - interval 16 | print('Retrieving events...') 17 | events = get_events(username, starttime, endtime) 18 | print('Summarizing events...') 19 | event_names, resource_names, resource_types = get_events_summaries(events) 20 | return { 21 | "account_id": account_id, 22 | "time_discovered": time_discovered, 23 | "username": username, 24 | "deleted_key": deleted_key, 25 | "event_names": event_names, 26 | "resource_names": resource_names, 27 | "resource_types": resource_types 28 | } 29 | 30 | 31 | def get_events(username, starttime, endtime): 32 | """ Retrieves detailed list of CloudTrail events that occured between the specified time interval. 33 | 34 | Args: 35 | username (string): Username to lookup CloudTrail events for. 36 | starttime(datetime): Start of interval to lookup CloudTrail events between. 37 | endtime(datetime): End of interval to lookup CloudTrail events between. 38 | 39 | Returns: 40 | (dict) 41 | Dictionary containing list of CloudTrail events occuring between the start and end time with detailed information for each event. 42 | 43 | """ 44 | try: 45 | response = cloudtrail.lookup_events( 46 | LookupAttributes=[ 47 | { 48 | 'AttributeKey': 'Username', 49 | 'AttributeValue': username 50 | }, 51 | ], 52 | StartTime=starttime, 53 | EndTime=endtime, 54 | MaxResults=50 55 | ) 56 | except Exception as e: 57 | print(e) 58 | print('Unable to retrieve CloudTrail events for user "{}"'.format(username)) 59 | raise(e) 60 | return response 61 | 62 | 63 | def get_events_summaries(events): 64 | """ Summarizes CloudTrail events list by reducing into counters of occurences for each event, resource name, and resource type in list. 65 | 66 | Args: 67 | events (dict): Dictionary containing list of CloudTrail events to be summarized. 68 | 69 | Returns: 70 | (list, list, list) 71 | Lists containing name:count tuples of most common occurences of events, resource names, and resource types in events list. 72 | 73 | """ 74 | event_name_counter = collections.Counter() 75 | resource_name_counter = collections.Counter() 76 | resource_type_counter = collections.Counter() 77 | for event in events['Events']: 78 | resources = event.get("Resources") 79 | event_name_counter.update([event.get('EventName')]) 80 | if resources is not None: 81 | resource_name_counter.update([resource.get("ResourceName") for resource in resources]) 82 | resource_type_counter.update([resource.get("ResourceType") for resource in resources]) 83 | return event_name_counter.most_common(10), resource_name_counter.most_common(10), resource_type_counter.most_common(10) 84 | -------------------------------------------------------------------------------- /automated-actions/AWS_RISK_CREDENTIALS_EXPOSED/lambda_functions/notify_security.py: -------------------------------------------------------------------------------- 1 | import os 2 | import boto3 3 | 4 | TOPIC_ARN = os.environ['TOPIC_ARN'] # ARN for SNS topic to post message to 5 | 6 | TEMPLATE = '''At {} the IAM access key {} for user {} on account {} was deleted after it was found to have been publicly exposed on the internet. 7 | Below are summaries of the most recent actions, resource names, and resource types associated with this user over the last 24 hours. 8 | 9 | Actions: 10 | {} 11 | 12 | Resource Names: 13 | {} 14 | 15 | Resource Types: 16 | {} 17 | 18 | These are summaries of only the most recent API calls made by this user. Please ensure your account remains secure by further reviewing the API calls made by this user in CloudTrail.''' 19 | 20 | ERROR_MSG = '''An error occured when attempting to delete an exposed IAM access key for one of your IAM users. Please login to your account and navigate to the Personal Health Dashboard for more details on this incident and how to resolve it. 21 | 22 | Personal Health Dashboard Link: https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues''' 23 | 24 | sns = boto3.client('sns') 25 | 26 | 27 | def lambda_handler(event, context): 28 | if event.get('error-info') is not None: 29 | publish_msg("Security Alert: Exposed IAM Key - Error Deleting Key", ERROR_MSG) 30 | return 31 | account_id = event['account_id'] 32 | username = event['username'] 33 | deleted_key = event['deleted_key'] 34 | time_discovered = event['time_discovered'] 35 | event_names = event['event_names'] 36 | resource_names = event['resource_names'] 37 | resource_types = event['resource_types'] 38 | subject = 'Security Alert: Exposed IAM Key For User {} On Account {}'.format(username, account_id) 39 | print("Generating message body...") 40 | event_summary = generate_summary_str(event_names) 41 | rname_summary = generate_summary_str(resource_names) 42 | rtype_summary = generate_summary_str(resource_types) 43 | message = TEMPLATE.format(time_discovered, 44 | deleted_key, 45 | username, 46 | account_id, 47 | event_summary, 48 | rname_summary, 49 | rtype_summary 50 | ) 51 | print("Publishing message...") 52 | publish_msg(subject, message) 53 | 54 | 55 | def generate_summary_str(summary_items): 56 | """ Generates formatted string containing CloudTrail summary info. 57 | 58 | Args: 59 | summary_items (list): List of tuples containing CloudTrail summary info. 60 | 61 | Returns: 62 | (string) 63 | Formatted string containing CloudTrail summary info. 64 | 65 | """ 66 | return '\t' + '\n\t'.join('{}: {}'.format(item[0], item[1]) for item in summary_items) 67 | 68 | 69 | def publish_msg(subject, message): 70 | """ Publishes message to SNS topic. 71 | 72 | Args: 73 | subject (string): Subject of message to be published to topic. 74 | message (string): Content of message to be published to topic. 75 | 76 | Returns: 77 | (None) 78 | 79 | """ 80 | try: 81 | sns.publish( 82 | TopicArn=TOPIC_ARN, 83 | Message=message, 84 | Subject=subject, 85 | MessageStructure='string' 86 | ) 87 | except Exception as e: 88 | print(e) 89 | print('Could not publish message to SNS topic "{}"'.format(TOPIC_ARN)) 90 | raise e 91 | -------------------------------------------------------------------------------- /chime-notifier/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Resource": "*" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /chime-notifier/LambdaFunction.py: -------------------------------------------------------------------------------- 1 | # Sample Lambda Function to post notifications to a Chime room when an AWS Health event happens 2 | from __future__ import print_function 3 | import json 4 | import logging 5 | import os 6 | from urllib2 import Request, urlopen, URLError, HTTPError 7 | # Setting up logging 8 | logger = logging.getLogger() 9 | logger.setLevel(logging.INFO) 10 | # main function 11 | def lambda_handler(event, context): 12 | """Post a message to the Chime Room when a new AWS Health event is generated""" 13 | message = str(event['detail']['eventDescription'][0]['latestDescription'] + " https://phd.aws.amazon.com/phd/home?region=us-east-1#/event-log?eventID=" + event['detail']['eventArn']) 14 | json.dumps(message) 15 | chime_message = {'Content': message} 16 | logger.info(str(chime_message)) 17 | webhookurl = str(os.environ['CHIMEWEBHOOK']) 18 | req = Request(webhookurl, json.dumps(chime_message)) 19 | try: 20 | response = urlopen(req) 21 | response.read() 22 | logger.info("Message posted: %s", chime_message['Content']) 23 | except HTTPError as e: 24 | logger.error("Request failed : %d %s", e.code, e.reason) 25 | except URLError as e: 26 | logger.error("Server connection failed: %s", e.reason) 27 | 28 | -------------------------------------------------------------------------------- /chime-notifier/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health Chime Notifier 2 | 3 | ### Description 4 | 5 | This tool can be used to post alerts to a Chime room when AWS Health events are generated by using AWS Lambda and Amazon CloudWatch Events. 6 | The message will contain the latest event description and a link to the AWS Health Console; e.g. "Event Description https://phd.aws.amazon.com/phd/home?region=us-east-1#/event-log?eventID=arn:aws:health:us-west-2::event/AWS_EVENT_ID". 7 | 8 | ### Chime Setup 9 | Follow these steps to configure the webhook in Chime: 10 | 11 | 1. Click the cog wheel from your Chime room 12 | 13 | 2. Select "Manage WebHooks". 14 | 15 | 3. Create Webhook 16 | 17 | 4. Copy the webhook URL and use it as the value for the *HookURL* parameter in the Cloudformation template. 18 | 19 | Security Note: WebHooks should be treated like passwords and should not be shared publicily. 20 | 21 | ### AWS setup using CloudFormation 22 | 23 | #### CloudFormation 24 | 25 | Choose **Launch Stack** to launch the template in the US East (N. Virginia) Region in your account. 26 | 27 | [![Launch AWS Health Chime Notifier](../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=ChimeNotifier&templateURL=https://s3.amazonaws.com/aws-health-tools-assets/cloudformation-templates/chime-notifier.json) 28 | 29 | The CloudFormation template requires the following parameter: 30 | 31 | *HookURL* - Incoming web hook url from Slack setup. 32 | 33 | 34 | ### AWS Manual Setup 35 | 36 | 1. Create an IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 37 | 38 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 39 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 40 | 41 | 2. Create a Lambda Python function by using the [sample](LambdaFunction.py) provided and choose the IAM role created in step 1. Add an enviroment variable with key CHIMEWEBHOOK and the webhook URL from the Chime setup above as value. 42 | 43 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 44 | 45 | 3. Create a CloudWatch Events rule to trigger the Lambda function created in step 2 for AWS Health events. 46 | 47 | Documentation on how to create an AWS Health CloudWatch Events rule is available here: http://docs.aws.amazon.com/health/latest/ug/cloudwatch-events-health.html 48 | 49 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 50 | 51 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing the example and tailoring it to your environment before using it in your production environment. 52 | 53 | You can also test the Lambda function by invoking it manually and using a [sample AWS Health event data.](http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#health-event-types) 54 | 55 | ### License 56 | AWS Health Tools are licensed under the Apache 2.0 License. 57 | -------------------------------------------------------------------------------- /chime-notifier/cfn-templates/chime-notifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | "HookURL": { 5 | "Type": "String", 6 | "Description": "Please enter the web hook url from Chime:", 7 | "NoEcho": true 8 | } 9 | }, 10 | "Resources": { 11 | "LambdaFunctionRole": { 12 | "Type": "AWS::IAM::Role", 13 | "Properties": { 14 | "AssumeRolePolicyDocument": { 15 | "Version": "2012-10-17", 16 | "Statement": [ 17 | { 18 | "Effect": "Allow", 19 | "Principal": { 20 | "Service": [ 21 | "lambda.amazonaws.com" 22 | ] 23 | }, 24 | "Action": [ 25 | "sts:AssumeRole" 26 | ] 27 | } 28 | ] 29 | }, 30 | "Path": "/" 31 | } 32 | }, 33 | "LambdaRolePolicies": { 34 | "Type": "AWS::IAM::Policy", 35 | "Properties": { 36 | "PolicyName": "LambdaPolicy", 37 | "PolicyDocument": { 38 | "Version": "2012-10-17", 39 | "Statement": [ 40 | { 41 | "Sid": "Stmt12349896368829", 42 | "Action": [ 43 | "logs:CreateLogGroup", 44 | "logs:CreateLogStream", 45 | "logs:PutLogEvents" 46 | ], 47 | "Effect": "Allow", 48 | "Resource": "arn:aws:logs:*:*:*" 49 | } 50 | ] 51 | }, 52 | "Roles": [ 53 | { 54 | "Ref": "LambdaFunctionRole" 55 | } 56 | ] 57 | } 58 | }, 59 | "ChimeNotifierLambdaFn": { 60 | "Type": "AWS::Lambda::Function", 61 | "Properties": { 62 | "Handler": "index.lambda_handler", 63 | "Role": { 64 | "Fn::GetAtt": [ 65 | "LambdaFunctionRole", 66 | "Arn" 67 | ] 68 | }, 69 | "Environment": { 70 | "Variables": { 71 | "CHIMEWEBHOOK": { 72 | "Ref": "HookURL" 73 | } 74 | } 75 | }, 76 | "Code": { 77 | "ZipFile": { 78 | "Fn::Sub": "#Sample Lambda Function to post notifications to a Chime room when an AWS Health event happens\nfrom __future__ import print_function \nimport boto3 \nimport json \nimport logging \nimport os \nfrom urllib2 import Request, urlopen, URLError, HTTPError \n# Setting up logging \nlogger = logging.getLogger() \nlogger.setLevel(logging.INFO) \n# main function \ndef lambda_handler(event, context): \n message = str(event['detail']['eventDescription'][0]['latestDescription'] + \" https://phd.aws.amazon.com/phd/home?region=us-east-1#/event-log?eventID=\" + event['detail']['eventArn'])\n json.dumps(message) \n chime_message = {'Content': message}\n logger.info(str(chime_message))\n webhookurl = str(os.environ['CHIMEWEBHOOK'])\n req = Request(webhookurl, json.dumps(chime_message)) \n try: \n response = urlopen(req) \n response.read() \n logger.info(\"Message posted: %s\", chime_message['Content']) \n except HTTPError as e: \n logger.error(\"Request failed : %d %s\", e.code, e.reason) \n except URLError as e: \n logger.error(\"Server connection failed: %s\", e.reason) \n" 79 | } 80 | }, 81 | "Runtime": "python2.7", 82 | "Timeout": "60" 83 | } 84 | }, 85 | "LambdaInvokePermission": { 86 | "Type": "AWS::Lambda::Permission", 87 | "Properties": { 88 | "FunctionName": { 89 | "Fn::GetAtt": [ 90 | "ChimeNotifierLambdaFn", 91 | "Arn" 92 | ] 93 | }, 94 | "Action": "lambda:InvokeFunction", 95 | "Principal": "events.amazonaws.com", 96 | "SourceArn": { 97 | "Fn::GetAtt": [ 98 | "CloudWatchEventRule", 99 | "Arn" 100 | ] 101 | } 102 | } 103 | }, 104 | "CloudWatchEventRule": { 105 | "Type": "AWS::Events::Rule", 106 | "Properties": { 107 | "Description": "EventRule", 108 | "EventPattern": { 109 | "source": [ 110 | "aws.health" 111 | ] 112 | }, 113 | "State": "ENABLED", 114 | "Targets": [ 115 | { 116 | "Arn": { 117 | "Fn::GetAtt": [ 118 | "ChimeNotifierLambdaFn", 119 | "Arn" 120 | ] 121 | }, 122 | "Id": "ChimeNotifierLambdaFn" 123 | } 124 | ] 125 | } 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /chime-notifier/cfn-templates/chime-notifier.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Parameters: 4 | HookURL: 5 | Type: String 6 | Description: 'Please enter the web hook url from Chime:' 7 | NoEcho: true 8 | Resources: 9 | LambdaFunctionRole: 10 | Type: AWS::IAM::Role 11 | Properties: 12 | AssumeRolePolicyDocument: 13 | Version: '2012-10-17' 14 | Statement: 15 | - Effect: Allow 16 | Principal: 17 | Service: 18 | - lambda.amazonaws.com 19 | Action: 20 | - sts:AssumeRole 21 | Path: "/" 22 | LambdaRolePolicies: 23 | Type: AWS::IAM::Policy 24 | Properties: 25 | PolicyName: LambdaPolicy 26 | PolicyDocument: 27 | Version: '2012-10-17' 28 | Statement: 29 | - Sid: Stmt12349896368829 30 | Action: 31 | - logs:CreateLogGroup 32 | - logs:CreateLogStream 33 | - logs:PutLogEvents 34 | Effect: Allow 35 | Resource: arn:aws:logs:*:*:* 36 | Roles: 37 | - Ref: LambdaFunctionRole 38 | ChimeNotifierLambdaFn: 39 | Type: AWS::Lambda::Function 40 | Properties: 41 | Handler: index.lambda_handler 42 | Role: 43 | Fn::GetAtt: 44 | - LambdaFunctionRole 45 | - Arn 46 | Environment: 47 | Variables: 48 | CHIMEWEBHOOK : !Ref HookURL 49 | Code: 50 | ZipFile: !Sub | 51 | #Sample Lambda Function to post notifications to a Chime room when an AWS Health event happens 52 | from __future__ import print_function 53 | import json 54 | import logging 55 | import os 56 | from urllib2 import Request, urlopen, URLError, HTTPError 57 | # Setting up logging 58 | logger = logging.getLogger() 59 | logger.setLevel(logging.INFO) 60 | # main function 61 | def lambda_handler(event, context): 62 | message = str(event['detail']['eventDescription'][0]['latestDescription'] + " https://phd.aws.amazon.com/phd/home?region=us-east-1#/event-log?eventID=" + event['detail']['eventArn']) 63 | json.dumps(message) 64 | chime_message = {'Content': message} 65 | logger.info(str(chime_message)) 66 | webhookurl = str(os.environ['CHIMEWEBHOOK']) 67 | req = Request(webhookurl, json.dumps(chime_message)) 68 | try: 69 | response = urlopen(req) 70 | response.read() 71 | logger.info("Message posted: %s", chime_message['Content']) 72 | except HTTPError as e: 73 | logger.error("Request failed : %d %s", e.code, e.reason) 74 | except URLError as e: 75 | logger.error("Server connection failed: %s", e.reason) 76 | Runtime: python2.7 77 | Timeout: '60' 78 | LambdaInvokePermission: 79 | Type: AWS::Lambda::Permission 80 | Properties: 81 | FunctionName: 82 | Fn::GetAtt: 83 | - ChimeNotifierLambdaFn 84 | - Arn 85 | Action: lambda:InvokeFunction 86 | Principal: events.amazonaws.com 87 | SourceArn: 88 | Fn::GetAtt: 89 | - CloudWatchEventRule 90 | - Arn 91 | CloudWatchEventRule: 92 | Type: AWS::Events::Rule 93 | Properties: 94 | Description: EventRule 95 | EventPattern: 96 | source: 97 | - aws.health 98 | State: ENABLED 99 | Targets: 100 | - Arn: 101 | Fn::GetAtt: 102 | - ChimeNotifierLambdaFn 103 | - Arn 104 | Id: ChimeNotifierLambdaFn 105 | -------------------------------------------------------------------------------- /coralogix-notifier/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Resource": "*" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /coralogix-notifier/LambdaFunction.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import logging 4 | import json 5 | import urllib.error 6 | from urllib.request import Request, urlopen 7 | 8 | CORALOGIX_LOG_URL = os.getenv('CORALOGIX_LOG_URL') 9 | PRIVATE_KEY = os.getenv('PRIVATE_KEY') 10 | APP_NAME = os.getenv('APP_NAME') 11 | SUB_SYSTEM = os.getenv('SUB_SYSTEM') 12 | 13 | WARN = 4 14 | TIMEOUT = os.getenv('CORALOGIX_TIMEOUT_HTTP', 30) 15 | RETRIES = os.getenv('CORALOGIX_RETRIES_HTTP', 2) 16 | 17 | logger = logging.getLogger() 18 | logger.setLevel(logging.INFO) 19 | 20 | def lambda_handler(event, context): 21 | message = { 22 | "privateKey": str(PRIVATE_KEY), 23 | "applicationName": str(APP_NAME), 24 | "subsystemName": str(SUB_SYSTEM), 25 | "logEntries": [{"timestamp": (time.time() * 1000), "severity": WARN, "text": event}] 26 | } 27 | jsondata = json.dumps(message).encode('utf-8') 28 | for attempt in range(int(RETRIES)): 29 | try: 30 | req = Request(CORALOGIX_LOG_URL) 31 | req.add_header('Content-Type', 'application/json; charset=utf-8') 32 | req.add_header('Content-Length', len(jsondata)) 33 | response = urlopen(req, data=jsondata,timeout=TIMEOUT) 34 | if response.getcode() == 200: 35 | logger.info("Health log published to Coralogix successfully 200 OK") 36 | return True 37 | else: 38 | logger.error("health log publish failed, status code %d, %b", response.getcode(), response.read) 39 | except urllib.error.URLError as e: 40 | logger.error("URL Error %s", e) 41 | except urllib.error.HTTPError as e: 42 | logger.error("HTTP Error %s", e) 43 | logger.info("attempt number %d", attempt + 1) 44 | time.sleep(5) 45 | -------------------------------------------------------------------------------- /coralogix-notifier/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health Coralogix Notifier 2 | 3 | ### Description 4 | 5 | This tool can be used to send logs to Coralogix endpoints when an AWS Health event happens by using AWS Lambda and Amazon CloudWatch Events. 6 | 7 | ### Setup and Usage 8 | 9 | Choose **Launch Stack** to launch the template in the US East (N. Virginia) Region in your account: 10 | 11 | [![Launch AWS Health Coralogix Notifier](../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=CoralogixNotifier&templateURL=https://aws-health-tools-assets.s3.amazonaws.com/cloudformation-templates/coralogix-notifier.json) 12 | 13 | The CloudFormation template requires the following parameters: 14 | 15 | - AWS Health Tool configuration 16 | - **CORALOGIX_LOG_URL**: The Coralogix logs ingress endpoint. 17 | - **PRIVATE_KEY**: Your Coralogix private key (sensitive). 18 | - **APP_NAME**: In Coralogix logs should be tagged by application name and sub system name. 19 | - **SUB_SYSTEM**: In Coralogix logs should be tagged by application name and sub system name. 20 | 21 | 22 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 23 | 24 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing this example and tailoring it to your environment before using it in your production environment. 25 | 26 | ### License 27 | AWS Health Tools are licensed under the Apache 2.0 License. 28 | -------------------------------------------------------------------------------- /coralogix-notifier/cfn-templates/coralogix-notifier.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: 2010-09-09 2 | Parameters: 3 | CoralogixLogURL: 4 | Type: String 5 | Description: 'Please enter the Coralogix log URL endpoint:' 6 | PrivateKey: 7 | Type: String 8 | Description: |- 9 | A private key which is used to validate your authenticity 10 | Please enter your private key: 11 | AppName: 12 | Type: String 13 | Description: |- 14 | The name of your main application 15 | Please enter your app name: 16 | SubSystem: 17 | Type: String 18 | Description: |- 19 | Your application probably has multiple subsystems 20 | Please enter your sub system name: 21 | Resources: 22 | CxNotifierLambdaRole: 23 | Type: 'AWS::IAM::Role' 24 | Properties: 25 | AssumeRolePolicyDocument: 26 | Version: 2012-10-17 27 | Statement: 28 | - Effect: Allow 29 | Principal: 30 | Service: 31 | - lambda.amazonaws.com 32 | Action: 33 | - 'sts:AssumeRole' 34 | Path: / 35 | CxLambdaRolePolicies: 36 | Type: 'AWS::IAM::Policy' 37 | Properties: 38 | PolicyName: LambdaPolicy 39 | PolicyDocument: 40 | Version: 2012-10-17 41 | Statement: 42 | - Sid: Stmt12349896368829 43 | Action: 44 | - 'logs:CreateLogGroup' 45 | - 'logs:CreateLogStream' 46 | - 'logs:PutLogEvents' 47 | Effect: Allow 48 | Resource: 'arn:aws:logs:*:*:*' 49 | Roles: 50 | - !Ref CxNotifierLambdaRole 51 | CoralogixNotifierLambda: 52 | Type: 'AWS::Lambda::Function' 53 | Properties: 54 | Handler: index.lambda_handler 55 | Role: !GetAtt 56 | - CxNotifierLambdaRole 57 | - Arn 58 | Code: 59 | ZipFile: 60 | Fn::Sub: | 61 | # Sample Lambda Function to post notifications to a slack channel when an AWS Health event happens 62 | import os 63 | import time 64 | import logging 65 | import json 66 | import urllib.error 67 | from urllib.request import Request, urlopen 68 | 69 | CORALOGIX_LOG_URL = os.getenv('CORALOGIX_LOG_URL') 70 | PRIVATE_KEY = os.getenv('PRIVATE_KEY') 71 | APP_NAME = os.getenv('APP_NAME') 72 | SUB_SYSTEM = os.getenv('SUB_SYSTEM') 73 | 74 | WARN = 4 75 | TIMEOUT = os.getenv('CORALOGIX_TIMEOUT_HTTP', 30) 76 | RETRIES = os.getenv('CORALOGIX_RETRIES_HTTP', 2) 77 | 78 | logger = logging.getLogger() 79 | logger.setLevel(logging.INFO) 80 | 81 | def lambda_handler(event, context): 82 | def send(e): 83 | message = { 84 | "privateKey": str(PRIVATE_KEY), 85 | "applicationName": str(APP_NAME), 86 | "subsystemName": str(SUB_SYSTEM), 87 | "logEntries": [{"timestamp": (time.time() * 1000), "severity": WARN, "text": event}] 88 | } 89 | jsondata = json.dumps(message).encode('utf-8') 90 | for attempt in range(int(RETRIES)): 91 | try: 92 | req = Request(CORALOGIX_LOG_URL) 93 | req.add_header('Content-Type', 'application/json; charset=utf-8') 94 | req.add_header('Content-Length', len(jsondata)) 95 | response = urlopen(req, data=jsondata,timeout=TIMEOUT) 96 | if response.getcode() == 200: 97 | logger.info("Health log published to Coralogix successfully 200 OK") 98 | return True 99 | else: 100 | logger.error("health log publish failed, status code %d, %b", response.getcode(), response.read) 101 | except urllib.error.URLError as e: 102 | logger.error("URL Error %s", e) 103 | except urllib.error.HTTPError as e: 104 | logger.error("HTTP Error %s", e) 105 | logger.info("attempt number %d", attempt + 1) 106 | time.sleep(5) 107 | 108 | entities = event.get("detail", {}).get("affectedEntities") 109 | resources = event.get("resources") 110 | if(type(entities) == list and type(resources) == list and sorted(list(map(lambda x: x["entityValue"], entities))) == sorted(resources) ): 111 | event["detail"].pop("affectedEntities", None) 112 | event.pop("resources", None) 113 | for entity in entities: 114 | event["detail"]["affectedEntity"] = entity 115 | event["resource"] = entity.get("entityValue") 116 | send(event) 117 | else: 118 | send(event) 119 | Environment: 120 | Variables: 121 | CORALOGIX_LOG_URL: !Ref CoralogixLogURL 122 | PRIVATE_KEY: !Ref PrivateKey 123 | APP_NAME: !Ref AppName 124 | SUB_SYSTEM: !Ref SubSystem 125 | Tags: 126 | - Key: coralogix.com/monitor 127 | Value: 'true' 128 | Runtime: python3.8 129 | Timeout: '60' 130 | CxLambdaInvokePermission: 131 | Type: 'AWS::Lambda::Permission' 132 | Properties: 133 | FunctionName: !GetAtt 134 | - CoralogixNotifierLambda 135 | - Arn 136 | Action: 'lambda:InvokeFunction' 137 | Principal: events.amazonaws.com 138 | SourceArn: !GetAtt 139 | - CloudWatchRuleHealth 140 | - Arn 141 | CloudWatchRuleHealth: 142 | Type: 'AWS::Events::Rule' 143 | Properties: 144 | Description: EventRule for Coralogix 145 | EventPattern: 146 | source: 147 | - aws.health 148 | State: ENABLED 149 | Targets: 150 | - Arn: !GetAtt 151 | - CoralogixNotifierLambda 152 | - Arn 153 | Id: CoralogixNotifierLambda 154 | -------------------------------------------------------------------------------- /dos-report-notifier/README.md: -------------------------------------------------------------------------------- 1 | ## DOS Report SNS Topic Notifier 2 | 3 | ### Description 4 | This tool can be used to send custom notifications to a SNS topic when an AWS DOS report is generated using AWS Health, AWS Lambda and Amazon CloudWatch Events. SNS topic subscribers (for example, web servers, email addresses, Amazon SQS queues, or AWS Lambda functions) can consume or receive the message or notification over one of the supported protocols (Amazon SQS, HTTP/S, email, SMS, Lambda) when they are subscribed to the topic. More information about SNS is available here: http://docs.aws.amazon.com/sns/latest/dg/welcome.html 5 | 6 | ### Setup and Usage 7 | 8 | #### Setup using CloudFormation 9 | 10 | Choose **Launch Stack** to launch this template in the US East (N. Virginia) Region in your account: 11 | 12 | Launch Stack 13 | 14 | Please update the region and the SNS topic name according to your requirements. 15 | 16 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 17 | 18 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing the example and tailoring it to your environment before using it in your production environment. 19 | 20 | ### License 21 | AWS Health Tools are licensed under the Apache 2.0 License. 22 | 23 | -------------------------------------------------------------------------------- /dos-report-notifier/dos-report-notifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | "SNSTopicName": { 5 | "Type": "String", 6 | "Description": "Please enter your SNS Topic Name. (SNS Topic must exist in the same region where this stack is launched)." 7 | } 8 | }, 9 | "Resources": { 10 | "LambdaFunctionRole": { 11 | "Type": "AWS::IAM::Role", 12 | "Properties": { 13 | "AssumeRolePolicyDocument": { 14 | "Version": "2012-10-17", 15 | "Statement": [ 16 | { 17 | "Effect": "Allow", 18 | "Principal": { 19 | "Service": [ 20 | "lambda.amazonaws.com" 21 | ] 22 | }, 23 | "Action": [ 24 | "sts:AssumeRole" 25 | ] 26 | } 27 | ] 28 | }, 29 | "Path": "/" 30 | } 31 | }, 32 | "LambdaRolePolicies": { 33 | "Type": "AWS::IAM::Policy", 34 | "Properties": { 35 | "PolicyName": "LambdaPolicy", 36 | "PolicyDocument": { 37 | "Version": "2012-10-17", 38 | "Statement": [ 39 | { 40 | "Sid": "Stmt1477516473539", 41 | "Action": [ 42 | "logs:CreateLogGroup", 43 | "logs:CreateLogStream", 44 | "logs:PutLogEvents" 45 | ], 46 | "Effect": "Allow", 47 | "Resource": "arn:aws:logs:*:*:*" 48 | }, 49 | { 50 | "Sid": "Stmt1484080345748", 51 | "Action": [ 52 | "sns:Publish" 53 | ], 54 | "Effect": "Allow", 55 | "Resource": { 56 | "Fn::Join": [ 57 | "", 58 | [ 59 | "arn:aws:sns:", 60 | { 61 | "Ref": "AWS::Region" 62 | }, 63 | ":", 64 | { 65 | "Ref": "AWS::AccountId" 66 | }, 67 | ":", 68 | { 69 | "Ref": "SNSTopicName" 70 | } 71 | ] 72 | ] 73 | } 74 | } 75 | ] 76 | }, 77 | "Roles": [ 78 | { 79 | "Ref": "LambdaFunctionRole" 80 | } 81 | ] 82 | } 83 | }, 84 | "SNSPublishFunction": { 85 | "Type": "AWS::Lambda::Function", 86 | "Properties": { 87 | "Handler": "index.handler", 88 | "Role": { 89 | "Fn::GetAtt": [ 90 | "LambdaFunctionRole", 91 | "Arn" 92 | ] 93 | }, 94 | "Environment": { 95 | "Variables": { 96 | "SNSARN": { 97 | "Fn::Join": [ 98 | "", 99 | [ 100 | "arn:aws:sns:", 101 | { 102 | "Ref": "AWS::Region" 103 | }, 104 | ":", 105 | { 106 | "Ref": "AWS::AccountId" 107 | }, 108 | ":", 109 | { 110 | "Ref": "SNSTopicName" 111 | } 112 | ] 113 | ] 114 | } 115 | } 116 | }, 117 | "Code": { 118 | "ZipFile": { 119 | "Fn::Join": [ 120 | "", 121 | [ 122 | "// Sample Lambda Function to send notifications to a SNS topic when an AWS Health event happens\n", 123 | "var AWS = require('aws-sdk');\n", 124 | "var sns = new AWS.SNS();\n", 125 | "\n", 126 | "// define configuration\n", 127 | "const snsTopic =process.env.SNSARN; //use ARN", 128 | "\n", 129 | "//main function which gets AWS Health data from Cloudwatch event\n", 130 | "exports.handler = (event, context, callback) => {\n", 131 | " //extract details from Cloudwatch event\n", 132 | " healthMessage = event.detail.eventDescription[0].latestDescription + ' For more details, please see https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues';\n", 133 | " eventName = event.detail.eventTypeCode\n", 134 | " //prepare message for SNS to publish\n", 135 | " var snsPublishParams = {\n", 136 | " Message: healthMessage, \n", 137 | " Subject: eventName,\n", 138 | " TopicArn: snsTopic\n", 139 | " };\n", 140 | " sns.publish(snsPublishParams, function(err, data) {\n", 141 | " if (err) {\n", 142 | " const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`;\n", 143 | " console.log(snsPublishErrorMessage, err);\n", 144 | " callback(snsPublishErrorMessage);\n", 145 | " } \n", 146 | " else {\n", 147 | " const snsPublishSuccessMessage = `Successfully got details from AWS Health event, ${eventName} and published to SNS topic.`;\n", 148 | " console.log(snsPublishSuccessMessage, data);\n", 149 | " callback(null, snsPublishSuccessMessage); //return success\n", 150 | " }\n", 151 | " });\n", 152 | "};" 153 | ] 154 | ] 155 | } 156 | }, 157 | "Runtime": "nodejs6.1", 158 | "Timeout": "25" 159 | } 160 | }, 161 | "LambdaInvokePermission": { 162 | "Type": "AWS::Lambda::Permission", 163 | "Properties": { 164 | "FunctionName": { 165 | "Fn::GetAtt": [ 166 | "SNSPublishFunction", 167 | "Arn" 168 | ] 169 | }, 170 | "Action": "lambda:InvokeFunction", 171 | "Principal": "events.amazonaws.com", 172 | "SourceArn": { 173 | "Fn::GetAtt": [ 174 | "CloudWatchEventRule", 175 | "Arn" 176 | ] 177 | } 178 | } 179 | }, 180 | "CloudWatchEventRule": { 181 | "Type": "AWS::Events::Rule", 182 | "Properties": { 183 | "Description": "EventRule", 184 | "EventPattern": { 185 | "source": [ 186 | "aws.health" 187 | ], 188 | "detail-type": [ 189 | "AWS Health Abuse Event" 190 | ], 191 | "detail": { 192 | "service": [ 193 | "ABUSE" 194 | ], 195 | "eventTypeCategory": [ 196 | "issue" 197 | ], 198 | "eventTypeCode": [ 199 | "AWS_ABUSE_DOS_REPORT" 200 | ] 201 | } 202 | }, 203 | "State": "ENABLED", 204 | "Targets": [ 205 | { 206 | "Arn": { 207 | "Fn::GetAtt": [ 208 | "SNSPublishFunction", 209 | "Arn" 210 | ] 211 | }, 212 | "Id": "SNSPublishFunction" 213 | } 214 | ] 215 | } 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /dos-report-notifier/stepbystep/images/Solution.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/dos-report-notifier/stepbystep/images/Solution.png -------------------------------------------------------------------------------- /dos-report-notifier/stepbystep/images/Step_1_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/dos-report-notifier/stepbystep/images/Step_1_1.png -------------------------------------------------------------------------------- /dos-report-notifier/stepbystep/images/Step_1_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/dos-report-notifier/stepbystep/images/Step_1_6.png -------------------------------------------------------------------------------- /dos-report-notifier/stepbystep/images/Step_1_Sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/dos-report-notifier/stepbystep/images/Step_1_Sol.png -------------------------------------------------------------------------------- /dos-report-notifier/stepbystep/images/Step_2_Lambda_Create.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/dos-report-notifier/stepbystep/images/Step_2_Lambda_Create.png -------------------------------------------------------------------------------- /dos-report-notifier/stepbystep/images/Step_2_Sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/dos-report-notifier/stepbystep/images/Step_2_Sol.png -------------------------------------------------------------------------------- /dos-report-notifier/stepbystep/images/Step_3_Sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/dos-report-notifier/stepbystep/images/Step_3_Sol.png -------------------------------------------------------------------------------- /dos-report-notifier/stepbystep/images/Step_4_Sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/dos-report-notifier/stepbystep/images/Step_4_Sol.png -------------------------------------------------------------------------------- /dos-report-notifier/stepbystep/mockpayload.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "DetailType": "AWS Health Abuse Event", 4 | "Source": "awsmock.health", 5 | "Time": "2018-11-27T01:30:00Z <>", 6 | "Resources": [ 7 | "arn:aws:ec2:us-east-1:<>:instance/i-1234", 8 | "arn:aws:ec2:us-east-1:<>:instance/i-4567" 9 | ], 10 | "Detail": "{\"eventArn\": \"arn:aws:health:global::event/AWS_ABUSE_DOS_REPORT_3223324344_3243_234_34_34\",\"service\": \"ABUSE\",\"eventTypeCode\": \"AWS_ABUSE_DOS_REPORT\",\"eventTypeCategory\": \"issue\",\"startTime\": \"Tue, 27 Nov 2018 01:30:00 GMT\",\"eventDescription\": [{\"language\": \"en_US\",\"latestDescription\": \"Denial of Service (DOS) attack has been reported to have been caused by AWS resources in your account.\"}],\"affectedEntities\": [{\"entityValue\": \"arn:aws:ec2:us-east-1:<>:instance/i-1234\"},{\"entityValue\": \"arn:aws:ec2:us-east-1:<>:instance/i-4567\"}]}" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /dx-maintenance-notifier/DX_Notifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | "EmailAddress": { 5 | "Description": "Please enter an email address to subscribe to the SNS topic. To subscribe additional email addresses and for other subscription options, go to SNS >> Topics >> \"DXMaintNotify\" >> Create subscription", 6 | "Type": "String", 7 | "AllowedPattern": "[a-zA-Z0-9_.+-]+@([a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]*\\.)+[a-zA-Z]+", 8 | "ConstraintDescription": "Please enter a valid email address for the SNS subscription" 9 | } 10 | }, 11 | "Resources": { 12 | "LambdaFunctionRole": { 13 | "Type": "AWS::IAM::Role", 14 | "Properties": { 15 | "AssumeRolePolicyDocument": { 16 | "Version": "2012-10-17", 17 | "Statement": [ 18 | { 19 | "Effect": "Allow", 20 | "Principal": { 21 | "Service": [ 22 | "lambda.amazonaws.com" 23 | ] 24 | }, 25 | "Action": [ 26 | "sts:AssumeRole" 27 | ] 28 | } 29 | ] 30 | }, 31 | "Path": "/" 32 | } 33 | }, 34 | "SnsTopic": { 35 | "Type": "AWS::SNS::Topic", 36 | "Properties": { 37 | "KmsMasterKeyId": "alias/aws/sns", 38 | "DisplayName": "DXMaintNotify", 39 | "TopicName" : "DXMaintNotify", 40 | "Subscription" : [ 41 | {"Endpoint": {"Ref":"EmailAddress"}, "Protocol": "email"} 42 | ] 43 | } 44 | }, 45 | 46 | 47 | "LambdaRolePolicies": { 48 | "Type": "AWS::IAM::Policy", 49 | "Properties": { 50 | "PolicyName": "LambdaPolicy", 51 | "PolicyDocument": { 52 | "Version": "2012-10-17", 53 | "Statement": [ 54 | { 55 | "Sid": "Stmt1477516473539", 56 | "Action": [ 57 | "logs:CreateLogGroup", 58 | "logs:CreateLogStream", 59 | "logs:PutLogEvents" 60 | ], 61 | "Effect": "Allow", 62 | "Resource": "arn:aws:logs:*:*:*" 63 | }, 64 | { 65 | "Sid": "Stmt1484080345748", 66 | "Action": [ 67 | "sns:Publish" 68 | ], 69 | "Effect": "Allow", 70 | "Resource": {"Ref": "SnsTopic"} 71 | } 72 | ] 73 | }, 74 | "Roles": [ 75 | { 76 | "Ref": "LambdaFunctionRole" 77 | } 78 | ] 79 | } 80 | }, 81 | 82 | 83 | "SNSPublishFunction": { 84 | 85 | "Type": "AWS::Lambda::Function", 86 | "DependsOn" : "SnsTopic", 87 | "Properties": { 88 | "Handler": "index.handler", 89 | "Role": { 90 | "Fn::GetAtt": [ 91 | "LambdaFunctionRole", 92 | "Arn" 93 | ] 94 | }, 95 | "Environment": { 96 | "Variables": { 97 | "SNS_TOPIC": { 98 | "Ref": "SnsTopic" 99 | } 100 | } 101 | }, 102 | "Code": { 103 | "ZipFile": { 104 | "Fn::Join": [ 105 | "", 106 | [ 107 | "// Sample Lambda Function to send notifications to a SNS topic when an AWS Health event happens\n", 108 | "const { SNSClient, PublishCommand } = require(\"@aws-sdk/client-sns\");\n", 109 | "const snsClient = new SNSClient();\n", 110 | "\n", 111 | "// define configuration\n", 112 | "const snsTopic = process.env.SNS_TOPIC;", 113 | "\n", 114 | "// main function which gets AWS Health data from Cloudwatch event\n", 115 | "const handler = async (event, context) => {\n", 116 | " let healthMessage = `${event.detail.eventDescription[0].latestDescription}. For more details, please see https://phd.aws.amazon.com/phd/home?region=${event.region}#/dashboard/open-issues\n\n", 117 | "Region: ${event.region}\n", 118 | "Account Id: ${event.account}\n", 119 | "Affected Resources:`;\n", 120 | "\n", 121 | " for (let resource of event.resources) {\n", 122 | " healthMessage += `\\n${resource}`;\n", 123 | " }\n", 124 | "\n", 125 | " healthMessage += `\\n\\nStart Time: ${event.detail.startTime}\\nEnd Time: ${event.detail.endTime}`;\n", 126 | "\n", 127 | " const eventName = event.detail.eventTypeCode;\n", 128 | "\n", 129 | " // prepare message for SNS to publish\n", 130 | " const snsPublishParams = {\n", 131 | " Message: healthMessage,\n", 132 | " Subject: eventName,\n", 133 | " TopicArn: snsTopic\n", 134 | " };\n", 135 | "\n", 136 | " try {\n", 137 | " const command = new PublishCommand(snsPublishParams);\n", 138 | " const data = await snsClient.send(command);\n", 139 | " const snsPublishSuccessMessage = `Successfully got details from AWS Health event, ${eventName} and published to SNS topic.`;\n", 140 | " console.log(snsPublishSuccessMessage, data);\n", 141 | " return snsPublishSuccessMessage;\n", 142 | " } catch (err) {\n", 143 | " const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`;\n", 144 | " console.log(snsPublishErrorMessage, err);\n", 145 | " throw new Error(snsPublishErrorMessage);\n", 146 | " }\n", 147 | "};\n", 148 | "\n", 149 | "module.exports = { handler };\n" 150 | ] 151 | ] 152 | } 153 | }, 154 | "Runtime": "nodejs18.x", 155 | "Timeout": "25" 156 | } 157 | }, 158 | "LambdaInvokePermission": { 159 | "Type": "AWS::Lambda::Permission", 160 | "Properties": { 161 | "FunctionName": { 162 | "Fn::GetAtt": [ 163 | "SNSPublishFunction", 164 | "Arn" 165 | ] 166 | }, 167 | "Action": "lambda:InvokeFunction", 168 | "Principal": "events.amazonaws.com", 169 | "SourceArn": { 170 | "Fn::GetAtt": [ 171 | "CloudWatchEventRule", 172 | "Arn" 173 | ] 174 | } 175 | } 176 | }, 177 | "CloudWatchEventRule": { 178 | "Type": "AWS::Events::Rule", 179 | "Properties": { 180 | "Description": "EventRule", 181 | "EventPattern": { 182 | "source": [ 183 | "aws.health" 184 | ], 185 | "detail-type": [ 186 | "AWS Health Event" 187 | ], 188 | "detail": { 189 | "service": [ 190 | "DIRECTCONNECT" 191 | ] 192 | } 193 | }, 194 | "State": "ENABLED", 195 | "Targets": [ 196 | { 197 | "Arn": { 198 | "Fn::GetAtt": [ 199 | "SNSPublishFunction", 200 | "Arn" 201 | ] 202 | }, 203 | "Id": "SNSPublishFunction" 204 | } 205 | ] 206 | } 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /dx-maintenance-notifier/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Direct Connect Maintenance Notifier 2 | 3 | ### Description 4 | This tool can be used to send Direct Connect maintenance notifications to SNS topic when a Direct Connect maintenance is scheduled by using AWS Lambda and AWS CloudWatch Events. SNS topic subscribers (for example, web servers, email addresses, Amazon SQS queues, or AWS Lambda functions) can consume or receive the message or notification over one of the supported protocols (Amazon SQS, HTTP/S, email, SMS, Lambda) when they are subscribed to the topic. More information about SNS is available here: http://docs.aws.amazon.com/sns/latest/dg/welcome.html 5 | 6 | ### Setup and Usage 7 | 8 | #### Setup using CloudFormation 9 | 10 | Choose **Launch Stack** to launch the AWS Direct Connect Maintenance Notifier template in the US West (Oregon) Region in your account. Please note that you need to launch this stack in just one region, you'll still be notified of the Direct Connects in other regions: 11 | 12 | Launch Stack 13 | 14 | Please update the region and the stack name according to your requirements. 15 | 16 | Here is a sample of the notification you receive when there is a maintenance scheduled on your Direct Connect resource: 17 | 18 | --------------------- 19 | Planned maintenance has been scheduled on an AWS Direct Connect router in Seattle, WA. During this maintenance window, your AWS Direct Connect services associated with this event may become unavailable. 20 | 21 | This maintenance is scheduled to avoid disrupting redundant connections at the same time. 22 | 23 | If you encounter any problems with your connection after the end of this maintenance window, please contact us at https://aws.amazon.com/support. For more details, please see https://phd.aws.amazon.com/phd/home?region=us-west-2#/dashboard/open-issues 24 | 25 | Region: us-west-2 26 |
Account Id: xxxxxxxxxxxx 27 | 28 | Affected Resources: 29 |
dxcon-xxxxxxxx 30 |
dxvif-xxxxxxxx 31 | 32 | Start Time: Thu, 6 Jul 2017 08:30:00 GMT 33 |
End Time: Thu, 6 Jul 2017 12:30:00 GMT 34 | 35 | --------------------- 36 | #### Manual setup 37 | 38 | 1. Create an IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 39 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 40 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 41 | 42 | 2. Create a Lambda JavaScript function by using the [sample](LambdaFunction.js) provided and choose the IAM role created in step 1. Update the configuration section of the script with the SNS topic ARN. 43 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 44 | 45 | 3. Create a CloudWatch Events rule to trigger the Lambda function created in step 2 for AWS Health events. 46 | Documentation on how to create AWS Health CloudWatch Events rules is available here: http://docs.aws.amazon.com/health/latest/ug/cloudwatch-events-health.html 47 | 48 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 49 | 50 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing the example and tailoring it to your environment before using it in your production environment. 51 | 52 | ### License 53 | AWS Health Tools are licensed under the Apache 2.0 License. 54 | 55 | 56 | -------------------------------------------------------------------------------- /high-availability-endpoint/.gitignore: -------------------------------------------------------------------------------- 1 | # java demo 2 | java/.gradle/ 3 | java/build 4 | java/buildSrc/build/ 5 | java/gradle/ 6 | java/gradlew 7 | java/gradlew.bat 8 | java/wrapper/ 9 | 10 | # python demo 11 | python/v-aws-health-env 12 | python/__pycache__ -------------------------------------------------------------------------------- /high-availability-endpoint/README.md: -------------------------------------------------------------------------------- 1 | high-availability-endpoint 2 | -------------------------- 3 | 4 | This repository contains sample code for using the AWS Health API's high availability endpoint to determine which region to connect to in order to get the latest Health information. 5 | 6 | ## Background 7 | 8 | AWS Health is a RESTful web service that uses HTTPS as a transport and JSON as a message serialization format. Your application code can make requests directly to the AWS Health API. When using the REST API directly, you must write the necessary code to sign and authenticate your requests. For more information, see the [AWS Health API Reference](https://docs.aws.amazon.com/health/latest/APIReference/). 9 | 10 | **NOTE**: You must have a Business or Enterprise support plan from [AWS Support](http://aws.amazon.com/premiumsupport/) to use the AWS Health API. If you call the AWS Health API from an AWS account that doesn't have a Business or Enterprise support plan, you receive a ```SubscriptionRequiredException``` error. 11 | 12 | You can simplify application development by using the AWS SDKs that wrap the AWS Health REST API calls. You provide your credentials, and then these libraries take care of authentication and request signing. 13 | AWS Health also provides a Personal Health Dashboard in the AWS Management Console that you can use to view and search for events and affected entities. 14 | 15 | ## Endpoints 16 | 17 | The AWS Health API follows a [multi-Region application architecture](http://aws.amazon.com/solutions/implementations/multi-region-application-architecture/) and has two regional endpoints in an active-passive configuration. To support active-passive DNS failover, AWS Health provides a single, global endpoint. You can determine the active endpoint and corresponding signing Region by performing a DNS lookup on the global endpoint. This lets you know which endpoint to use in your code so that you can get the latest information from AWS Health. 18 | 19 | When you make a request to the global endpoint, you must specify your AWS access credentials to the regional endpoint that you target and configure the signing for your Region. Otherwise, your authentication might fail. For more information, see [Signing AWS Health API requests](https://docs.aws.amazon.com/health/latest/ug/health-api.html#signing). 20 | 21 | The following table represents the default configuration. 22 | 23 | | Description | Signing Region | Endpoint | Protocol | 24 | | ----------- | -------------- | -------- | -------- | 25 | | Active | us-east-1 | health.us.east-1.amazonaws.com | HTTPS | 26 | | Passive | us-east-2 | health.us-east-2.amazonaws.com | HTTPS | 27 | | Global | us-east-1 This is the signing Region of the current active endpoint. | global.health.amazonaws.com | HTTPS | 28 | 29 | For China regions see [this configuration](https://docs.amazonaws.cn/en_us/health/latest/ug/health-api.html#endpoints). 30 | 31 | The method to determine if an endpoint is the _active endpoint_ is to do a DNS lookup on the _global endpoint CNAME_ and extract the region from the resolved name. 32 | 33 | For example, the following command completes a DNS lookup on the global.health.amazonaws.com endpoint. The command then returns the us-east-1 Region endpoint: 34 | 35 | ``` 36 | $ dig global.health.amazonaws.com | grep CNAME 37 | global.health.amazonaws.com. 10 IN CNAME health.us-east-1.amazonaws.com 38 | ``` 39 | 40 | The active endpoint region is us-east-1. 41 | 42 | Both the active and passive endpoints will return AWS Health data. However, the latest AWS Health data will only be available from the active endpoint. Data from the passive endpoint will be eventually consistent. We recommend that you **restart any workflows when the active endpoint changes**. 43 | 44 | ## Demos 45 | 46 | For examples on using the high availability endpoint with the AWS SDKs see: 47 | 48 | * [Java demo](java/) 49 | * [Python demo](python/) 50 | 51 | ## License 52 | 53 | AWS Health Tools are licensed under the Apache 2.0 License -------------------------------------------------------------------------------- /high-availability-endpoint/java/README.md: -------------------------------------------------------------------------------- 1 | ## Using the high availability endpoint java demo 2 | 3 | To build and run this demo: 4 | 5 | 1. Download / clone the repo [AWS Health high availability endpoint demo](https://github.com/aws/aws-health-tools/high-availability-endpoint) from GitHub 6 | 7 | 2. [Install Gradle](https://docs.gradle.org/current/userguide/installation.html) 8 | 9 | 3. Navigate to the java demo project directory in a command line window: 10 | 11 | ``` 12 | cd java 13 | ``` 14 | 15 | 4. Compile the demo by entering the following command: 16 | 17 | ``` 18 | gradle build 19 | ``` 20 | 21 | 5. Set the AWS credentials: 22 | 23 | [Configure AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). e.g. using profiles or environment variables 24 | 25 | ``` 26 | export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE" 27 | export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 28 | export AWS_SESSION_TOKEN="your-aws-token" 29 | ``` 30 | 31 | 6. Enter the following command to run the demo: 32 | 33 | ``` 34 | gradle run 35 | ``` 36 | 37 | The output will look something like: 38 | 39 | ``` 40 | > Task :run 41 | [main] INFO aws.health.high.availability.endpoint.demo.HighAvailabilityV2Workflow - EventDetails(Event=Event(Arn=arn:aws:health:global::event/CONFIG/AWS_CONFIG_OPERATIONAL_NOTIFICATION/AWS_CONFIG_OPERATIONAL_NOTIFICATION_88a43e8a-e419-4ca7-9baa-56bcde4dba3, Service=CONFIG, EventTypeCode=AWS_CONFIG_OPERATIONAL_NOTIFICATION, EventTypeCategory=accountNotification, Region=global, StartTime=2020-09-11T02:55:49.899Z, LastUpdatedTime=2020-09-11T03:46:31.764Z, StatusCode=open, EventScopeCode=ACCOUNT_SPECIFIC), EventDescription=EventDescription(LatestDescription=As part of our ongoing efforts to optimize costs associated with recording changes related to certain ephemeral workloads, AWS Config is scheduled to release an update to relationships modeled within ConfigurationItems (CI) for 7 EC2 resource types on August 1, 2021. Examples of ephemeral workloads include changes to Amazon Elastic Compute Cloud (Amazon EC2) Spot Instances, Amazon Elastic MapReduce jobs, and Amazon EC2 Autoscaling. This update will optimize CI models for EC2 Instance, SecurityGroup, Network Interface, Subnet, VPC, VPN Gateway, and Customer Gateway resource types to record direct relationships and deprecate indirect relationships. 42 | 43 | A direct relationship is defined as a one-way relationship (A->B) between a resource (A) and another resource (B), and is typically derived from the Describe API response of resource (A). An indirect relationship, on the other hand, is a relationship that AWS Config infers (B->A), in order to create a bidirectional relationship. For example, EC2 instance -> Security Group is a direct relationship, since security groups are returned as part of the describe API response for an EC2 instance. But Security Group -> EC2 instance is an indirect relationship, since EC2 instances are not returned when describing an EC2 Security group. 44 | 45 | Until now, AWS Config has recorded both direct and indirect relationships. With the launch of Advanced queries in March 2019, indirect relationships can easily be answered by running Structured Query Language (SQL) queries such as: 46 | 47 | SELECT 48 | resourceId, 49 | resourceType 50 | WHERE 51 | resourceType ='AWS::EC2::Instance' 52 | AND 53 | relationships.resourceId = 'sg-234213' 54 | 55 | By deprecating indirect relationships, we can optimize the information contained within a Configuration Item while reducing AWS Config costs related to relationship changes. This is especially useful in case of ephemeral workloads where there is a high volume of configuration changes for EC2 resource types. 56 | 57 | Which resource relationships are being removed? 58 | 59 | Resource Type: Related Resource Type 60 | 1 AWS::EC2::CustomerGateway: AWS::VPN::Connection 61 | 2 AWS::EC2::Instance: AWS::EC2::EIP, AWS::EC2::RouteTable 62 | 3 AWS::EC2::NetworkInterface: AWS::EC2::EIP, AWS::EC2::RouteTable 63 | 4 AWS::EC2::SecurityGroup: AWS::EC2::Instance, AWS::EC2::NetworkInterface 64 | 5 AWS::EC2::Subnet: AWS::EC2::Instance, AWS::EC2::NetworkACL, AWS::EC2::NetworkInterface, AWS::EC2::RouteTable 65 | 6 AWS::EC2::VPC: AWS::EC2::Instance, AWS::EC2::InternetGateway, AWS::EC2::NetworkACL, AWS::EC2::NetworkInterface, AWS::EC2::RouteTable, AWS::EC2::Subnet, AWS::EC2::VPNGateway, AWS::EC2::SecurityGroup 66 | 7 AWS::EC2::VPNGateway: AWS::EC2::RouteTable, AWS::EC2::VPNConnection 67 | 68 | Alternate mechanism to retrieve this relationship information: 69 | The SelectResourceConfig API accepts a SQL SELECT command, performs the corresponding search, and returns resource configurations matching the properties. You can use this API to retrieve the same relationship information. For example, to retrieve the list of all EC2 Instances related to a particular VPC vpc-1234abc, you can use the following query: 70 | 71 | SELECT 72 | resourceId, 73 | resourceType 74 | WHERE 75 | resourceType ='AWS::EC2::Instance' 76 | AND 77 | relationships.resourceId = 'vpc-1234abc' 78 | 79 | If you have any questions regarding this deprecation plan, please contact AWS Support [1]. Additional sample queries to retrieve the relationship information for the resources listed above is provided in [2]. 80 | 81 | [1] https://aws.amazon.com/support 82 | [2] https://docs.aws.amazon.com/config/latest/developerguide/examplerelationshipqueries.html), EventMetadata={}) 83 | [main] INFO aws.health.high.availability.endpoint.demo.HighAvailabilityV2Workflow - EventDetails(Event=Event(Arn=arn:aws:health:us-west-2::event/STORAGEGATEWAY/AWS_STORAGEGATEWAY_OPERATIONAL_ISSUE/AWS_STORAGEGATEWAY_OPERATIONAL_ISSUE_WQPFF_7809546408, Service=STORAGEGATEWAY, EventTypeCode=AWS_STORAGEGATEWAY_OPERATIONAL_ISSUE, EventTypeCategory=issue, Region=us-west-2, StartTime=2020-09-13T19:46:48.335Z, LastUpdatedTime=2020-09-14T01:30:16.216Z, StatusCode=open, EventScopeCode=PUBLIC), EventDescription=EventDescription(LatestDescription=Storage Gateway VMs Offline 84 | 85 | [12:46 PM PDT] Beginning at 7:39 AM PDT, we are experiencing an issue where Storage Gateway VMs appear to be offline and are not able to perform out of cache reads or upload to our service. The gateways will continue to accept writes but will not be able to proceed to upload them to our service until this issue is resolved. We have identified the root cause and are working towards resolution. 86 | 87 | [02:59 PM PDT] We continue to work towards resolution on the issue impacting Storage Gateway. The Gateways will continue to accept writes but will not be able to proceed to upload them to our service until this issue is resolved. Based upon the information available at this time, full resolution is estimated to take 2 hours. We are working through the recovery process now and will continue to keep you updated if this timeline changes. 88 | 89 | [05:29 PM PDT] We are beginning to see recovery in some AWS Regions for the issue impacting Storage Gateway. We continue to work towards resolution for all impacted Regions. We will update the message for each AWS Region as recovery occurs. 90 | 91 | [06:30 PM PDT] Beginning at 7:39 AM PDT we experienced an issue impacting Storage Gateway in the US-WEST-2 Region. During the event Gateways were unable to perform out of cache reads and appeared offline in the SGW console. Gateways continued to accept writes but were unable to upload them to the AWS service. The issue has been resolved and the service is operating normally. ), EventMetadata={}) 92 | ``` 93 | 94 | ## References 95 | 96 | * AWS Java SDK V2 health client [javadoc](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/health/HealthClient.html) and [source code](https://repo1.maven.org/maven2/software/amazon/awssdk/health/2.14.2/) 97 | * The library used in this demo for DNS lookups - [dnsjava](https://github.com/dnsjava/dnsjava) 98 | 99 | ## License 100 | 101 | AWS Health Tools are licensed under the Apache 2.0 License -------------------------------------------------------------------------------- /high-availability-endpoint/java/build.gradle: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | plugins { 5 | // Apply the java plugin to add support for Java 6 | id 'java' 7 | 8 | // Apply the application plugin to add support for building a CLI application. 9 | id 'application' 10 | } 11 | 12 | repositories { 13 | // Use jcenter for resolving dependencies. 14 | // You can declare any Maven/Ivy/file repository here. 15 | jcenter() 16 | } 17 | 18 | dependencies { 19 | // This dependency is used by the application. 20 | implementation 'software.amazon.awssdk:health:2.+' 21 | implementation 'dnsjava:dnsjava:3.1.0' 22 | implementation 'org.slf4j:slf4j-simple:1.7.30' 23 | 24 | // Use JUnit Jupiter API for testing. 25 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0' 26 | 27 | // Use JUnit Jupiter Engine for testing. 28 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.6.0' 29 | } 30 | 31 | application { 32 | // Define the main class for the application. 33 | mainClassName = 'aws.health.high.availability.endpoint.demo.App' 34 | } 35 | 36 | test { 37 | // Use junit platform for unit tests 38 | useJUnitPlatform() 39 | } 40 | -------------------------------------------------------------------------------- /high-availability-endpoint/java/settings.gradle: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | rootProject.name = 'aws-health-high-availability-endpoint-demo-java' 5 | -------------------------------------------------------------------------------- /high-availability-endpoint/java/src/main/java/aws/health/high/availability/endpoint/demo/ActiveRegionHasChangedException.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | package aws.health.high.availability.endpoint.demo; 4 | 5 | public final class ActiveRegionHasChangedException extends Exception { 6 | public ActiveRegionHasChangedException(String message) { 7 | super(message); 8 | } 9 | 10 | public ActiveRegionHasChangedException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | } -------------------------------------------------------------------------------- /high-availability-endpoint/java/src/main/java/aws/health/high/availability/endpoint/demo/App.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | package aws.health.high.availability.endpoint.demo; 4 | 5 | /** 6 | * Sample workflow for using the AWS Health API high availability endpoint 7 | */ 8 | public class App { 9 | public static void main(String[] args) throws RegionLookupException { 10 | 11 | // Example using the HealthClient from the AWS Java SDK V2 12 | HighAvailabilityV2Workflow v2Workflow = new HighAvailabilityV2Workflow(); 13 | v2Workflow.doWorkflow(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /high-availability-endpoint/java/src/main/java/aws/health/high/availability/endpoint/demo/HighAvailabilityV2HealthClient.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | package aws.health.high.availability.endpoint.demo; 4 | 5 | import software.amazon.awssdk.services.health.HealthClient; 6 | import software.amazon.awssdk.services.health.HealthClientBuilder; 7 | import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; 8 | import software.amazon.awssdk.regions.Region; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * This is an example of using the Health API endpoint DNS lookup strategy 15 | * with the AWS Java SDK V2 Health client. 16 | */ 17 | public final class HighAvailabilityV2HealthClient { 18 | private static String activeRegion; 19 | 20 | private static HealthClient healthClient; 21 | 22 | private HighAvailabilityV2HealthClient() {} 23 | 24 | public static synchronized HealthClient getHealthClient() throws RegionLookupException, ActiveRegionHasChangedException { 25 | String currentActiveRegion = RegionLookup.getCurrentActiveRegion(); 26 | if (activeRegion == null) { 27 | // This is the first time we've done the DNS lookup 28 | activeRegion = currentActiveRegion; 29 | } else { 30 | // If the active region has changed since the last time we did the 31 | // DNS lookup we throw an exception to let the calling code know 32 | // abount the change 33 | if (!currentActiveRegion.equals(activeRegion)) { 34 | String oldActiveRegion = activeRegion; 35 | activeRegion = currentActiveRegion; 36 | 37 | if (healthClient != null) { 38 | // Close the existing client so that the next call to this 39 | // method will create a new client using the new active 40 | // region 41 | healthClient.close(); 42 | healthClient = null; 43 | } 44 | 45 | throw new ActiveRegionHasChangedException("Active region has changed from [" + oldActiveRegion + "] to [" + currentActiveRegion + "]"); 46 | } 47 | } 48 | 49 | if (healthClient == null) { 50 | // Create the Health client using the region extraced from the global 51 | // endpoint CNAME 52 | healthClient = HealthClient.builder() 53 | .region(Region.of(activeRegion)) 54 | .credentialsProvider( 55 | // See https://docs.aws.amazon.com/sdk-for-java/v2/developer-guide/setup-credentials.html 56 | DefaultCredentialsProvider.builder().build() 57 | ) 58 | .build(); 59 | } 60 | 61 | return healthClient; 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /high-availability-endpoint/java/src/main/java/aws/health/high/availability/endpoint/demo/HighAvailabilityV2Workflow.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | package aws.health.high.availability.endpoint.demo; 4 | 5 | import software.amazon.awssdk.services.health.HealthClient; 6 | import software.amazon.awssdk.services.health.model.DateTimeRange; 7 | import software.amazon.awssdk.services.health.model.DescribeEventDetailsRequest; 8 | import software.amazon.awssdk.services.health.model.DescribeEventDetailsResponse; 9 | import software.amazon.awssdk.services.health.model.DescribeEventsRequest; 10 | import software.amazon.awssdk.services.health.model.DescribeEventsResponse; 11 | import software.amazon.awssdk.services.health.model.Event; 12 | import software.amazon.awssdk.services.health.model.EventFilter; 13 | import software.amazon.awssdk.services.health.model.EventStatusCode; 14 | import software.amazon.awssdk.services.health.paginators.DescribeEventsIterable; 15 | 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | 19 | import java.time.Duration; 20 | import java.time.Instant; 21 | 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | /** 27 | * Example workflow when using the AWS Health API high availability endpoint 28 | * and the AWS Java V2 SDK. 29 | */ 30 | public class HighAvailabilityV2Workflow { 31 | private static final Logger log = LoggerFactory.getLogger(HighAvailabilityV2Workflow.class); 32 | 33 | /** 34 | * Helper method to provide a shorter way of referring to the singleton 35 | * client method 36 | */ 37 | private HealthClient getV2Client() throws RegionLookupException, ActiveRegionHasChangedException { 38 | return HighAvailabilityV2HealthClient.getHealthClient(); 39 | } 40 | 41 | /** 42 | * Log the details of an event 43 | */ 44 | private void eventDetails(Event event) throws RegionLookupException, ActiveRegionHasChangedException { 45 | // NOTE: It is more efficient to call describeEventDetails with a batch 46 | // of eventArns, but for simplicitly of this demo we call it with a 47 | // single eventArn 48 | DescribeEventDetailsRequest detailsRequest = DescribeEventDetailsRequest.builder() 49 | .eventArns(Collections.singleton(event.arn())) 50 | .build(); 51 | 52 | DescribeEventDetailsResponse detailsResponse = getV2Client().describeEventDetails(detailsRequest); 53 | detailsResponse.successfulSet().stream().forEach(detail -> log.info(detail.toString())); 54 | } 55 | 56 | private void describeEvents() throws RegionLookupException, ActiveRegionHasChangedException { 57 | // Describe events using the same default filters as the Personal Health Dashboard (PHD). i.e 58 | // 59 | // Return all open or upcoming events which started in the last 7 days, ordered by event lastUpdatedTime 60 | 61 | List eventStatusCodes = new ArrayList<>(); 62 | eventStatusCodes.add(EventStatusCode.OPEN); 63 | eventStatusCodes.add(EventStatusCode.UPCOMING); 64 | 65 | DateTimeRange sevenDaysAgo = DateTimeRange.builder().from(Instant.now().minus(Duration.ofDays(7))).build(); 66 | 67 | DescribeEventsRequest describeEventsRequest = DescribeEventsRequest.builder() 68 | .filter(EventFilter.builder().eventStatusCodes(eventStatusCodes).startTimes(Collections.singleton(sevenDaysAgo)).build()) 69 | .build(); 70 | 71 | int numberOfMatchingEvents = 0; 72 | DescribeEventsIterable describeEventsResponses = getV2Client().describeEventsPaginator(describeEventsRequest); 73 | for (DescribeEventsResponse eventsResponse : describeEventsResponses) { 74 | for (Event event: eventsResponse.events()) { 75 | eventDetails(event); 76 | numberOfMatchingEvents++; 77 | } 78 | } 79 | 80 | if (numberOfMatchingEvents == 0) { 81 | log.info("There are no AWS Health events that match the given filters"); 82 | } 83 | } 84 | 85 | public void doWorkflow() throws RegionLookupException { 86 | // An example workflow using the AWS Health API's high availability 87 | // endpoint and the AWS Java SDK V2 88 | 89 | // If the active endpoint changes we recommend you restart any 90 | // workflows. 91 | // 92 | // In this sample code we throw an exception if the active 93 | // endpoint changes in the middle of a workflow and restart the 94 | // workflow using the new active endpoint. 95 | boolean restartWorkflow = true; 96 | 97 | while (restartWorkflow) { 98 | try { 99 | // Describe events for the account 100 | describeEvents(); 101 | restartWorkflow = false; 102 | } catch (ActiveRegionHasChangedException ex) { 103 | log.info("The AWS Health API active region has changed. Restarting the workflow using the new active region!" + ex); 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /high-availability-endpoint/java/src/main/java/aws/health/high/availability/endpoint/demo/RegionLookup.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | package aws.health.high.availability.endpoint.demo; 4 | 5 | import org.xbill.DNS.Lookup; 6 | import org.xbill.DNS.CNAMERecord; 7 | import org.xbill.DNS.TextParseException; 8 | import org.xbill.DNS.Type; 9 | import org.xbill.DNS.Record; 10 | 11 | public final class RegionLookup { 12 | public static final String GLOBAL_HEALTH_ENDPOINT = "global.health.amazonaws.com"; 13 | 14 | public static String getCurrentActiveRegion() throws RegionLookupException { 15 | // Init lookup object 16 | Lookup dnsLookup = null; 17 | try { 18 | dnsLookup = new Lookup(GLOBAL_HEALTH_ENDPOINT, Type.CNAME); 19 | } catch (TextParseException e) { 20 | throw new RegionLookupException("Failed to parse DNS name [" + GLOBAL_HEALTH_ENDPOINT + "]", e); 21 | } 22 | dnsLookup.setCache(null); // Never use caching, always do a full a DNS lookup 23 | 24 | // Do the DNS lookup 25 | Record[] records = dnsLookup.run(); 26 | if (records == null || records.length == 0) { 27 | throw new RegionLookupException("Failed to resolve the DNS name [" + GLOBAL_HEALTH_ENDPOINT + "]"); 28 | } 29 | CNAMERecord cnameRecord = (CNAMERecord)records[0]; 30 | String regionalDNSName = cnameRecord.getTarget().toString(true); 31 | 32 | // The CNAME will look something like: "health.us-east-1.amazonaws.com" 33 | // Extract the region name (e.g. us-east-1) to use for SigV4 signing 34 | String[] cnameParts = regionalDNSName.split("\\."); 35 | String regionName = cnameParts[1].toLowerCase(); // The region is always the second item in the array 36 | 37 | return regionName; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /high-availability-endpoint/java/src/main/java/aws/health/high/availability/endpoint/demo/RegionLookupException.java: -------------------------------------------------------------------------------- 1 | // Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | // SPDX-License-Identifier: Apache-2.0 3 | package aws.health.high.availability.endpoint.demo; 4 | 5 | public final class RegionLookupException extends Exception { 6 | public RegionLookupException(String message) { 7 | super(message); 8 | } 9 | 10 | public RegionLookupException(String message, Throwable cause) { 11 | super(message, cause); 12 | } 13 | } -------------------------------------------------------------------------------- /high-availability-endpoint/python/README.md: -------------------------------------------------------------------------------- 1 | ## Using the high availability endpoint python demo 2 | 3 | To build and run this demo: 4 | 5 | 1. Download / clone the repo [AWS Health high availability endpoint demo](https://github.com/aws/aws-health-tools/high-availability-endpoint) from GitHub 6 | 7 | 2. Install python 3 8 | 9 | See the [Python 3 Installation & Setup Guide](https://realpython.com/installing-python/) for steps on installing Python for Linux, macOS and Windows 10 | 11 | 3. Navigate to the python demo project directory in a command line window: 12 | 13 | ``` 14 | cd python 15 | ``` 16 | 17 | 4. Create a virtual environment by running the following commands: 18 | 19 | ``` 20 | pip3 install virtualenv 21 | virtualenv -p python3 v-aws-health-env 22 | ``` 23 | 24 | NOTE: For Python 3.3 and newer you can use the built-in [venv module](https://docs.python.org/3/library/venv.html) to create a virtual environment, instead of installing virtualenv. 25 | 26 | ``` 27 | python3 -m venv v-aws-health-env 28 | ``` 29 | 30 | 5. Activate the virtual environment by running the command: 31 | 32 | ``` 33 | source v-aws-health-env/bin/activate 34 | ``` 35 | 36 | 6. Install dependencies by running the command: 37 | 38 | ``` 39 | pip install -r requirements.txt 40 | ``` 41 | 42 | 7. Set the AWS credentials: 43 | 44 | [Configure AWS credentials](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). e.g. using profiles or environment variables 45 | 46 | ``` 47 | export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE" 48 | export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" 49 | export AWS_SESSION_TOKEN="your-aws-token" 50 | ``` 51 | 52 | 8. Enter the following command to run the demo: 53 | 54 | ``` 55 | python3 main.py 56 | ``` 57 | 58 | The output will look something like: 59 | 60 | ``` 61 | INFO:botocore.credentials:Found credentials in environment variables. 62 | INFO:root:Details: {'arn': 'arn:aws:health:global::event/SECURITY/AWS_SECURITY_NOTIFICATION/AWS_SECURITY_NOTIFICATION_0e35e47e-2247-47c4-a9a5-876544042721', 'service': 'SECURITY', 'eventTypeCode': 'AWS_SECURITY_NOTIFICATION', 'eventTypeCategory': 'accountNotification', 'region': 'global', 'startTime': datetime.datetime(2020, 8, 19, 23, 30, 42, 476000, tzinfo=tzlocal()), 'lastUpdatedTime': datetime.datetime(2020, 8, 20, 20, 44, 9, 547000, tzinfo=tzlocal()), 'statusCode': 'open', 'eventScopeCode': 'PUBLIC'}, description: {'latestDescription': 'This is the second notice regarding TLS requirements on FIPS endpoints.\n\nWe are in the process of updating all AWS Federal Information Processing Standard (FIPS) endpoints across all AWS regions to Transport Layer Security (TLS) version 1.2 by March 31, 2021 . In order to avoid an interruption in service, we encourage you to act now, by ensuring that you connect to AWS FIPS endpoints at a TLS version of 1.2. If your client applications fail to support TLS 1.2 it will result in connection failures when TLS versions below 1.2 are no longer supported.\n\nBetween now and March 31, 2021 AWS will remove TLS 1.0 and TLS 1.1 support from each FIPS endpoint where no connections below TLS 1.2 are detected over a 30-day period. After March 31, 2021 we may deploy this change to all AWS FIPS endpoints, even if there continue to be customer connections detected at TLS versions below 1.2. \n\nWe will provide additional updates and reminders on the AWS Security Blog, with a ‘TLS’ tag [1]. If you need further guidance or assistance, please contact AWS Support [2] or your Technical Account Manager (TAM). Additional information is below.\n\nHow can I identify clients that are connecting with TLS 1.0/1.1?\nFor customers using S3 [3], Cloudfront [4] or Application Load Balancer [5] you can use your access logs to view the TLS connection information for these services, and identify client connections that are not at TLS 1.2. If you are using the AWS Developer Tools on your clients, you can find information on how to properly configure your client’s TLS versions by visiting Tools to Build on AWS [7] or our associated AWS Security Blog has a link for each unique code language [7].\n\nWhat is Transport Layer Security (TLS)?\nTransport Layer Security (TLS Protocols) are cryptographic protocols designed to provide secure communication across a computer network [6].\n\nWhat are AWS FIPS endpoints? \nAll AWS services offer Transport Layer Security (TLS) 1.2 encrypted endpoints that can be used for all API calls. Some AWS services also offer FIPS 140-2 endpoints [9] for customers that require use of FIPS validated cryptographic libraries. \n\n[1] https://aws.amazon.com/blogs/security/tag/tls/\n[2] https://aws.amazon.com/support\n[3] https://docs.aws.amazon.com/AmazonS3/latest/dev/LogFormat.html\n[4] https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/AccessLogs.html\n[5] https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html\n[6] https://aws.amazon.com/tools\n[7] https://aws.amazon.com/blogs/security/tls-1-2-to-become-the-minimum-for-all-aws-fips-endpoints\n[8] https://en.wikipedia.org/wiki/Transport_Layer_Security\n[9] https://aws.amazon.com/compliance/fips'} 63 | ``` 64 | 65 | 9. Deactive the virtual environment 66 | 67 | Lastly, when you have finished with everything you can deactivate the environment by running the following command: 68 | 69 | ``` 70 | deactivate 71 | ``` 72 | 73 | ## References 74 | 75 | * [Boto3 Python SDK AWS Health doco](https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/health.html#Health.Client) 76 | * The library used in this demo for DNS lookups - dnspython [doco](https://dnspython.readthedocs.io/en/stable/) and [source code](https://github.com/rthalley/dnspython/) 77 | 78 | ## License 79 | 80 | AWS Health Tools are licensed under the Apache 2.0 License 81 | -------------------------------------------------------------------------------- /high-availability-endpoint/python/health_client.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | from region_lookup import active_region 4 | import boto3 5 | 6 | class ActiveRegionHasChangedError(Exception): 7 | """Rasied when the active region has changed""" 8 | pass 9 | 10 | class HealthClient: 11 | __active_region = None 12 | __client = None 13 | 14 | @staticmethod 15 | def client(): 16 | if not HealthClient.__active_region: 17 | HealthClient.__active_region = active_region() 18 | else: 19 | current_active_region = active_region() 20 | if current_active_region != HealthClient.__active_region: 21 | old_active_region = HealthClient.__active_region 22 | HealthClient.__active_region = current_active_region 23 | 24 | if HealthClient.__client: 25 | HealthClient.__client = None 26 | 27 | raise ActiveRegionHasChangedError('Active region has changed from [' + old_active_region + '] to [' + current_active_region + ']') 28 | 29 | if not HealthClient.__client: 30 | HealthClient.__client = boto3.client('health', region_name=HealthClient.__active_region) 31 | 32 | return HealthClient.__client -------------------------------------------------------------------------------- /high-availability-endpoint/python/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | # SPDX-License-Identifier: Apache-2.0 4 | from health_client import HealthClient, ActiveRegionHasChangedError 5 | import datetime 6 | import logging 7 | logging.basicConfig(level=logging.INFO) 8 | 9 | def event_details(event): 10 | # NOTE: It is more efficient to call describe_event_details with a batch 11 | # of eventArns, but for simplicitly of this demo we call it with a 12 | # single eventArn 13 | event_details_response = HealthClient.client().describe_event_details(eventArns=[event['arn']]) 14 | for event_details in event_details_response['successfulSet']: 15 | logging.info('Details: %s, description: %s', event_details['event'], event_details['eventDescription']) 16 | 17 | def describe_events(): 18 | events_paginator = HealthClient.client().get_paginator('describe_events') 19 | 20 | # Describe events using the same default filters as the Personal Health 21 | # Dashboard (PHD). i.e 22 | # 23 | # Return all open or upcoming events which started in the last 7 days, 24 | # ordered by event lastUpdatedTime 25 | 26 | events_pages = events_paginator.paginate(filter={ 27 | 'startTimes': [ 28 | { 29 | 'from': datetime.datetime.now() - datetime.timedelta(days=7) 30 | } 31 | ], 32 | 'eventStatusCodes': ['open', 'upcoming'] 33 | }) 34 | 35 | number_of_matching_events = 0 36 | for events_page in events_pages: 37 | for event in events_page['events']: 38 | number_of_matching_events += 1 39 | event_details(event) 40 | 41 | if number_of_matching_events == 0: 42 | logging.info('There are no AWS Health events that match the given filters') 43 | 44 | 45 | # If the active endpoint changes we recommend you restart any workflows. 46 | # 47 | # In this sample code we throw an exception if the active endpoint changes in 48 | # the middle of a workflow and restart the workflow using the new active 49 | # endpoint. 50 | restart_workflow = True 51 | 52 | while restart_workflow: 53 | try: 54 | describe_events() 55 | restart_workflow = False 56 | except ActiveRegionHasChangedError as are: 57 | logging.info("The AWS Health API active region has changed. Restarting the workflow using the new active region!, %s", are) -------------------------------------------------------------------------------- /high-availability-endpoint/python/region_lookup.py: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | import dns.resolver 4 | 5 | class RegionLookupError(Exception): 6 | """Rasied when there was a problem when looking up the active region""" 7 | pass 8 | 9 | def active_region(): 10 | qname = 'global.health.amazonaws.com' 11 | try: 12 | answers = dns.resolver.resolve(qname, 'CNAME') 13 | except Exception as e: 14 | raise RegionLookupError('Failed to resolve {}'.format(qname), e) 15 | if len(answers) != 1: 16 | raise RegionLookupError('Failed to get a single answer when resolving {}'.format(qname)) 17 | name = str(answers[0].target) # e.g. health.us-east-1.amazonaws.com. 18 | region_name = name.split('.')[1] # Region name is the 1st in split('.') -> ['health', 'us-east-1', 'amazonaws', 'com', ''] 19 | return region_name -------------------------------------------------------------------------------- /high-availability-endpoint/python/requirements.txt: -------------------------------------------------------------------------------- 1 | # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: Apache-2.0 3 | boto3 > 1.15 4 | dnspython >= 2.0.0 -------------------------------------------------------------------------------- /images/AWSHealthToolsArchitecture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/images/AWSHealthToolsArchitecture.jpg -------------------------------------------------------------------------------- /images/cloudformation-launch-stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws/aws-health-tools/0dc3bc4f35c2fd67bf66993adb2bccf939dc806c/images/cloudformation-launch-stack.png -------------------------------------------------------------------------------- /shd-notifier/Health-Event-Iterator-LambdaFn.py: -------------------------------------------------------------------------------- 1 | # Health-Event-Iterator-LambdaFn 2 | 3 | # Increments a count variable by 1 until it reaches a maximum value (maxCount) 4 | # then sets the value to 0. Simple iterator. 5 | # Input: maxCount - int, maximum count, defaults to 15 6 | # count - int, current count, defaults to 0 7 | # Output: current count 8 | # 9 | 10 | import json # essential to read json 11 | import os # required to read in the os variables 12 | import boto3 # AWS CLI, required to poll AWS Health 13 | 14 | # Static vars 15 | COUNT=0 # default counter starting value 16 | MAXCOUNT=15 # default maximum count number before reset 17 | 18 | # Main lambda function 19 | def lambda_handler(event, context): 20 | # read in the count, if missing default to COUNT 21 | try: 22 | count= event['count'] 23 | except Exception as e: 24 | eMessage="WARN: Missing count defaulting to %i" % COUNT 25 | print(eMessage) 26 | count=COUNT 27 | 28 | # read in the maxCount, if missing default to 15 29 | try: 30 | maxCount= event['maxCount'] 31 | except Exception as e: 32 | eMessage="WARN: Missing maxCount defaulting to %i" % MAXCOUNT 33 | print(eMessage) 34 | maxCount=MAXCOUNT 35 | 36 | count=count+1 # increment the counter 37 | if (count==maxCount): count=0 # set the count back to 0 when maxCount is reached 38 | return count # return the count as the output 39 | -------------------------------------------------------------------------------- /shd-notifier/Health-Event-Poller-LambdaFn.py: -------------------------------------------------------------------------------- 1 | # Health-Event-Poller-lambdaFn 2 | # Lambda Function to poll for open health events and execute a Step Function 3 | # (SFN) - state machine to deal with them 4 | 5 | # Inputs: Optional environment variables 6 | # DEBUG - enables debugging, only will start one SFN 7 | # WAIT_TIME - minutes to wait before reposting event status to Chime 8 | # Outputs: Executes a SFN, one for each open event detected. 9 | # Since the event name matches the SFN name, duplicate executions 10 | # of the same event will be rejected. 11 | # Notes: Pagination is not supported, we handle a maximum of 100 open events 12 | # 13 | 14 | import json # essential to read json 15 | import os # required to read in the os variable for the Webhook 16 | import logging # handy to keep track of things 17 | import boto3 # AWS CLI, required to poll AWS Health 18 | from botocore.exceptions import ClientError 19 | 20 | # Static vars 21 | eventStatusCodes='open' # open events only, for debug try closed 22 | eventTypeCategories='issue' # SHD events are always issues 23 | maxEvents=100 # That is the maximum events that can be pulled in one operation 24 | # for now hard coded the name of the SFN ARN to run against 25 | stateMachineArn=os.getenv('SFN_ARN','') 26 | maxEventID=80 # maximum size of the name parameter passed to SFN 27 | defWaitTime=15 # default the wait time to 15 28 | 29 | # Read in the OS environment variables, default any missing vars 30 | # read in the debugging flag, if 1 enable debug log level and some messages, defaults to 0 31 | DEBUG = int(os.getenv('DEBUG',0)) # set DEBUG environment variable to 1 to enable testing 32 | # Setting up logging, default to INFO level 33 | logger = logging.getLogger() 34 | if (DEBUG): 35 | logger.setLevel(logging.DEBUG) 36 | logger.debug("DEBUGGING ON") # send debug status 37 | else: 38 | logger.setLevel(logging.INFO) 39 | # read in the wait time, default to DEF_WAIT_TIME 40 | WAIT_TIME= int(os.getenv('WAIT_TIME',defWaitTime)) 41 | logger.debug("WAIT_TIME= %i" % WAIT_TIME) 42 | try: 43 | REGION_FILTER= str(os.getenv('REGION_FILTER', '[]')) 44 | logger.debug("REGION_FILTER= %s" % REGION_FILTER) 45 | REGION_FILTER=json.loads(REGION_FILTER) 46 | except Exception as e: 47 | logger.error(e) 48 | eMessage= 'ERROR: Invalid REGION_FILTER specified!' 49 | logger.error(eMessage) 50 | raise Exception(eMessage) 51 | 52 | # Extracts the name field from the ARN 53 | # Input: Issues ARN 54 | # Output: The name of the ARN trimmed to the maxEventID size 55 | def trimArnToName(arn): 56 | # Health ARN Pattern: arn:aws:health:[^:]*:[^:]*:event/[\w-]+ 57 | # set the issues name from the ARN to match the SFN's name 58 | eventIDPos= arn.rfind('/') 59 | eventStr= arn[eventIDPos:] 60 | # Trim the SFN Name to the maxEventID size 61 | eventID= eventStr[1:maxEventID] 62 | logger.debug("SFN name: %s" % (eventID)) 63 | return eventID 64 | 65 | # Main lambda function 66 | def lambda_handler(event, context): 67 | 68 | # Load the AWS Health API 69 | health= boto3.client('health', region_name='us-east-1') 70 | # Build the filter 71 | event_filter = {"eventStatusCodes": [eventStatusCodes],"eventTypeCategories": [ eventTypeCategories ]} 72 | if len(REGION_FILTER)>0: 73 | event_filter['regions']=REGION_FILTER 74 | # Poll the open events 75 | events_dict= health.describe_events( 76 | filter=event_filter, 77 | maxResults=maxEvents 78 | ) 79 | open_issues=events_dict['events'] 80 | if (len(open_issues)==0): 81 | print("No open issues detected.") # nothing to see here... 82 | logger.info("No open issues detected.") 83 | else: 84 | logger.info("Number of open issues: %s" % (len(open_issues))) 85 | # load the step state machine API 86 | stepClient = boto3.client('stepfunctions') 87 | # for every open issue, lets execute a state machine 88 | for issue in open_issues: 89 | # Skip events that are not PUBLIC events that appear on the SHD (i.e. Account specific events) 90 | if issue['eventScopeCode'] != 'PUBLIC': 91 | logger.info("Non-public issue not on SHD, skipping: %s" % (issue['arn'])) 92 | continue 93 | logger.info("Starting Step Function for issue: %s" % (issue['arn'])) 94 | # Execute state machine pass in the issues ARN and the WAIT_TIME 95 | input_str="{\"eventArn\":\"%s\",\"maxCount\": %i}" % (issue['arn'],WAIT_TIME) 96 | logger.debug("SFN Arn: %s" % (stateMachineArn)) 97 | logger.debug("SFN input: %s" % (input_str)) 98 | # extract the eventID field within the name size limit 99 | eventID=trimArnToName(issue['arn']) 100 | # ok lets fire up the state machine 101 | try: 102 | response = stepClient.start_execution( 103 | stateMachineArn=stateMachineArn, 104 | name=eventID, 105 | input= input_str 106 | ) 107 | except ClientError as e: 108 | if e.response['Error']['Code'] == 'ExecutionAlreadyExists': 109 | # Duplicate Event ID's will be ignored since they were handled. 110 | logger.info("Event already executed named: %s" % (eventID)) 111 | if (DEBUG): 112 | logger.debug("DEBUG: Duplicate event detected for: %s" % (eventID)) 113 | break # Only run one issue in debug, even already exists 114 | continue 115 | else: 116 | # we were unable to start the SFN, which is a severe error 117 | print(e) 118 | message= 'ERROR: Unable to start state machine' 119 | print(message) 120 | raise Exception(message) 121 | if (DEBUG): break # Only run one issue in debug 122 | 123 | -------------------------------------------------------------------------------- /shd-notifier/Health-Event-Status-LambdaFn.py: -------------------------------------------------------------------------------- 1 | # Health-Event-Status-LambdaFn 2 | 3 | # Given an events ARN return if it is open or closed 4 | # Input: Event ARN 5 | # Output: Event Status (open, closed, upcoming) 6 | 7 | import json # essential to read json 8 | import os # required to read in the os variables 9 | import boto3 # AWS CLI, required to poll AWS Health 10 | 11 | # Static vars 12 | maxEvents=10 # Max is 100, but we expect just 1 13 | 14 | # Main lambda function 15 | def lambda_handler(event, context): 16 | # read in the eventArn input 17 | eventArn= event['eventArn'] 18 | # Load the AWS Health API 19 | health= boto3.client('health', region_name='us-east-1') 20 | # Pull the event matching the Arn passed in, catch any errors 21 | try: 22 | events_dict= health.describe_events( 23 | filter={'eventArns': [eventArn]}, 24 | maxResults=maxEvents 25 | ) 26 | except Exception as e: 27 | print(e) 28 | message= 'ERROR: getting events status' 29 | print(message) 30 | raise Exception(message) 31 | # pull out just the events 32 | our_events=events_dict['events'] 33 | # now lets validate we received what we expected, 1 result 34 | if (len(our_events)==0): 35 | # Error state, no match 36 | message= 'ERROR: ARN not detected' 37 | print(message) 38 | raise Exception(message) 39 | elif (len(our_events)>1): 40 | # Error state, too many matches 41 | message="ERROR: Multiple ARNs detected" 42 | print(message) 43 | raise Exception(message) 44 | else: 45 | # return the status code for our one event 46 | statusCode= our_events[0]['statusCode'] 47 | return statusCode 48 | -------------------------------------------------------------------------------- /shd-notifier/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health SHD Notifier 2 | 3 | ### Description 4 | 5 | This tool can be used to send Service Health Dashboard (SHD) postings to Chime, Slack or an SNS topic. A notification for each update to the SHD event will be sent, as well as an optional "no update" message for ongoing events. It uses a polling approach as SHD postings do not trigger Health Events at this time. Step Functions are used to track each event and send notification updates while the issue is not resolved, including optional "no update since last message" notifications. 6 | 7 | ### Setup and Usage 8 | 9 | #### Pre-launch requirements - Determining your delivery endpoints 10 | - If using SNS, create and configure the SNS topic(s) before deploying the CloudFormation stack. 11 | - If using Chime or Slack, follow the documentation for creating and determining the endpoint string(s). 12 | 13 | #### Deploying the CloudFormation Stack 14 | Choose **Launch Stack** to launch the template in the US East (N. Virginia) Region in your account: 15 | 16 | [![Launch AWS Health SMS Notifier](../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=ShdNotifier&templateURL=https://s3.amazonaws.com/aws-health-tools-assets/cloudformation-templates/shd-notifier.yml) 17 | 18 | The CloudFormation template requires the following parameters: 19 | 20 | - **AppName** - The base name for the underlying lambda functions 21 | 22 | - **ChatClient** - Which Client should be used for notifications. Only one client is supported per deployment. Options are: 23 | - chime 24 | - slack 25 | - sns 26 | 27 | - **EndpointArray** - An array of one or more endpoint strings for the client notifications. Examples for each client: 28 | - chime: ["https://hooks.chime.aws/incomingwebhooks/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXX?token=XXXXXXXXXXXXXXXXXXXX"] 29 | - slack: ["https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX"] 30 | - sns: ["arn:aws:sns:us-east-1:XXXXXXXXXXXX:SNS_Topic_Name"] 31 | 32 | - **Bail** - Disable sending of messages when there is no update. Allowed values: 33 | - 0 (Send "no update" messages every 15 minutes) 34 | - 1 (Send messages only when a new update has been made) 35 | 36 | - **LambdaRate** - The frequency to check for new SHD postings or updates. Allowed values: 37 | - rate(1 minute) 38 | - rate(5 minutes) 39 | - rate(10 minutes) 40 | 41 | - **MessagePrefix** - A prefix for each update message. Examples include: 42 | - [SHD AUTO] 43 | - This is an automated notification from the AWS Service Health Dashboard. Current status can always be found at http://status.aws.amazon.com 44 | 45 | - **RegionFilter** - An optional array of region strings to limit notifications of SHD postings to only regions of interest . Examples include: 46 | - ["us-west-2","us-east-1","global"] 47 | - ["global"] 48 | 49 | - **DEBUG** - Control debug log level and messages. Allowed values: 50 | - 0 (Disable Debugging) 51 | - 1 (Enable debugging) 52 | 53 | - **WaitSeconds** - Control State Machine wait period of state transition. Allowed values: 54 | - 60 (1 minutes - Allows Standard State machine to run for 2.5 days) 55 | - 300 (5 minutes - Allows Standard State machine to run for 12.5 days) 56 | - 600 (10 minutes - Allows Standard State machine to run for 25 days) 57 | 58 | #### Post-CloudFormation Installation Step 59 | Due to the CloudFormation limit on inline Lambda functions, after the CloudFormation stack has completed successfully, the **deploy.sh** script will need to be run to update the code for the Lambda functions.
60 | Syntax: **deploy.sh** _\_ _\_
61 | - *CF_APPNAME* = The *AppName* defined when deploying the CloudFormation template 62 | - *REGION* = The region of the deployed CloudFormation template 63 | 64 | ### License 65 | AWS Health Tools are licensed under the Apache 2.0 License. 66 | -------------------------------------------------------------------------------- /shd-notifier/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Updates the Lambda functions that are too big to be inlined in the CloudFormation Template 3 | 4 | set -e 5 | APPNAME=$1 6 | REGION=$2 7 | if [ $# -lt 2 ]; then 8 | echo 1>&2 "$0: not enough arguments." 9 | echo 1>&2 "Usage: $0 " 10 | echo 1>&2 " CF_APPNAME: The name of the deployed CloudFormation template to update" 11 | echo 1>&2 " REGION: The region of the deployed CloudFormation template" 12 | exit 2 13 | fi 14 | 15 | AWS_VERSION=$(aws --version) 16 | AWS_COMMAND_FLAGS="" 17 | 18 | # check if aws version is 2.x 19 | # we need to stop the command from waiting for user input 20 | if [[ $AWS_VERSION == *"aws-cli/2"* ]]; then 21 | AWS_COMMAND_FLAGS="--no-cli-pager" 22 | fi 23 | 24 | createOrUpdate () { 25 | FUNC_NAME=$1 26 | SOURCE=$2 27 | cp "$SOURCE" index.py 28 | zip "$FUNC_NAME.zip" index.py 29 | aws lambda update-function-code --function-name "$FUNC_NAME" --zip-file "fileb://$FUNC_NAME.zip" --region "$REGION" $AWS_COMMAND_FLAGS 30 | rm "$FUNC_NAME.zip" 31 | rm index.py 32 | } 33 | createOrUpdate "$APPNAME-Chat-Post" "Health-Event-Chat-Post-LambdaFn.py" 34 | createOrUpdate "$APPNAME-Poller" "Health-Event-Poller-LambdaFn.py" 35 | createOrUpdate "$APPNAME-Status" "Health-Event-Status-LambdaFn.py" 36 | createOrUpdate "$APPNAME-Iterator" "Health-Event-Iterator-LambdaFn.py" 37 | -------------------------------------------------------------------------------- /slack-notifier/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Resource": "*" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /slack-notifier/LambdaFunction.py: -------------------------------------------------------------------------------- 1 | ''' 2 | This is a sample function to send AWS Health event messages to a Slack channel. 3 | 4 | Follow these steps to configure the webhook in Slack: 5 | 6 | 1. Navigate to https://.slack.com/apps 7 | 8 | 2. Search for and select "Incoming WebHooks". 9 | 10 | 3. Select "Add Configuration" and choose the default channel where messages will be sent. Then click "Add Incoming WebHooks Integration". 11 | 12 | 4. Copy the webhook URL from the setup instructions and use it in the configuration section bellow 13 | 14 | You can also use KMS to encrypt the webhook URL as shown here: https://aws.amazon.com/blogs/aws/new-slack-integration-blueprints-for-aws-lambda/ 15 | ''' 16 | 17 | import json 18 | import logging 19 | from urllib.request import Request, urlopen, URLError, HTTPError 20 | 21 | #configuration 22 | 23 | # The Slack channel to send a message to stored in the slackChannel environment variable 24 | SLACK_CHANNEL = '#awshealth' 25 | # Add the webhook URL from Slack below 26 | HOOK_URL = 'https://hooks.slack.com/services/example' 27 | # Setting up logging 28 | logger = logging.getLogger() 29 | logger.setLevel(logging.INFO) 30 | # main function 31 | def handler(event, context): 32 | message = str( 33 | event['detail']['eventDescription'][0]['latestDescription'] + 34 | '\n\n for details.' 37 | ) 38 | json.dumps(message) 39 | slack_message = { 40 | 'channel': SLACK_CHANNEL, 41 | 'text': message, 42 | 'username': 'AWS - Personal Health Updates' 43 | } 44 | logger.info(str(slack_message)) 45 | req = Request( 46 | HOOK_URL, 47 | data=json.dumps(slack_message).encode('utf-8'), 48 | headers={'content-type': 'application/json'} 49 | ) 50 | try: 51 | response = urlopen(req) 52 | response.read() 53 | logger.info('Message posted to: %s', slack_message['channel']) 54 | except HTTPError as e: 55 | logger.error('Request failed : %d %s', e.code, e.reason) 56 | except URLError as e: 57 | logger.error('Server connection failed: %s', e.reason) 58 | 59 | -------------------------------------------------------------------------------- /slack-notifier/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health Slack Notifier 2 | 3 | ### Description 4 | 5 | This tool can be used to post alerts to a Slack channel when AWS Health events are generated by using AWS Lambda and Amazon CloudWatch Events. 6 | 7 | ### Slack Setup 8 | Follow these steps to configure the webhook in Slack: 9 | 10 | 1. Navigate to https://<your-team-domain>.slack.com/apps 11 | 12 | 2. Search for and select "Incoming WebHooks". 13 | 14 | 3. Select "Add Configuration" and choose the default channel where messages will be sent. Then click "Add Incoming WebHooks Integration". 15 | 16 | 4. Copy the webhook URL from the setup instructions and use it in the AWS Setup section that follows. 17 | 18 | 19 | ### AWS setup using CloudFormation 20 | 21 | #### CloudFormation 22 | Choose **Launch Stack** to launch the template in the US East (N. Virginia) Region in your account: 23 | 24 | 25 | Launch Stack 26 | 27 | The CloudFormation template requires the following parameters: 28 | 29 | *SlackChannel* - A Slack channel name where you would like the notifications to get pushed. 30 | 31 | *HookURL* - Incoming web hook url from Slack setup. 32 | 33 | 34 | 35 | ### AWS Manual Setup 36 | 37 | 1. Create an IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 38 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 39 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 40 | 41 | 2. Create a Lambda Python function by using the [sample](LambdaFunction.py) provided and choose the IAM role created in step 1. Update the configuration section of the Lambda function with webhook URL from the Slack setup above and update the Slack channel that you want AWS Health messages posted in. 42 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 43 | More information about Slack integration with Lambda is available here: https://aws.amazon.com/blogs/aws/new-slack-integration-blueprints-for-aws-lambda/ 44 | 45 | 3. Create a CloudWatch Events rule to trigger the Lambda function created in step 2 for AWS Health events. 46 | Documentation on how to create an AWS Health CloudWatch Events rule is available here: http://docs.aws.amazon.com/health/latest/ug/cloudwatch-events-health.html 47 | 48 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 49 | 50 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing the example and tailoring it to your environment before using it in your production environment. 51 | 52 | ### License 53 | AWS Health Tools are licensed under the Apache 2.0 License. 54 | 55 | 56 | -------------------------------------------------------------------------------- /slack-notifier/cfn-templates/slack-notifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | "SlackChannel": { 5 | "Type": "String", 6 | "Description": "Please enter your Slack channel name:" 7 | }, 8 | "HookURL": { 9 | "Type": "String", 10 | "Description": "Please enter the web hook url from Slack:", 11 | "NoEcho": true 12 | } 13 | }, 14 | "Resources": { 15 | "LambdaFunctionRole": { 16 | "Type": "AWS::IAM::Role", 17 | "Properties": { 18 | "AssumeRolePolicyDocument": { 19 | "Version": "2012-10-17", 20 | "Statement": [ 21 | { 22 | "Effect": "Allow", 23 | "Principal": { 24 | "Service": [ 25 | "lambda.amazonaws.com" 26 | ] 27 | }, 28 | "Action": [ 29 | "sts:AssumeRole" 30 | ] 31 | } 32 | ] 33 | }, 34 | "Path": "/" 35 | } 36 | }, 37 | "LambdaRolePolicies": { 38 | "Type": "AWS::IAM::Policy", 39 | "Properties": { 40 | "PolicyName": "LambdaPolicy", 41 | "PolicyDocument": { 42 | "Version": "2012-10-17", 43 | "Statement": [ 44 | { 45 | "Sid": "Stmt12349896368829", 46 | "Action": [ 47 | "logs:CreateLogGroup", 48 | "logs:CreateLogStream", 49 | "logs:PutLogEvents" 50 | ], 51 | "Effect": "Allow", 52 | "Resource": "arn:aws:logs:*:*:*" 53 | } 54 | ] 55 | }, 56 | "Roles": [ 57 | { 58 | "Ref": "LambdaFunctionRole" 59 | } 60 | ] 61 | } 62 | }, 63 | "SlackNotifierLambdaFn": { 64 | "Type": "AWS::Lambda::Function", 65 | "Properties": { 66 | "Description": "AWS PHD Slack Notifier", 67 | "Handler": "index.handler", 68 | "Role": { 69 | "Fn::GetAtt": [ 70 | "LambdaFunctionRole", 71 | "Arn" 72 | ] 73 | }, 74 | "Code": { 75 | "ZipFile": { 76 | "Fn::Sub": "# Sample Lambda Function to post notifications to a slack channel when an AWS Health event happens\nimport json\nimport logging\nfrom urllib.request import Request, urlopen, URLError, HTTPError\n# Setting up logging\nlogger = logging.getLogger()\nlogger.setLevel(logging.INFO)\n# main function\ndef handler(event, context):\n message = str(\n event['detail']['eventDescription'][0]['latestDescription'] +\n \"\\n\\n for details.\"\n )\n json.dumps(message)\n slack_message = {\n \"channel\": \"${SlackChannel}\",\n \"text\": message,\n \"username\": \"AWS - Personal Health Updates\"\n }\n logger.info(str(slack_message))\n req = Request(\n \"${HookURL}\",\n data=json.dumps(slack_message).encode(\"utf-8\"),\n headers={\"content-type\": \"application/json\"}\n )\n try:\n response = urlopen(req)\n response.read()\n logger.info(\"Message posted to: %s\", slack_message['channel'])\n except HTTPError as e:\n logger.error(\"Request failed : %d %s\", e.code, e.reason)\n except URLError as e:\n logger.error(\"Server connection failed: %s\", e.reason)\n" 77 | } 78 | }, 79 | "Runtime": "python3.11", 80 | "Timeout": "60" 81 | } 82 | }, 83 | "LambdaInvokePermission": { 84 | "Type": "AWS::Lambda::Permission", 85 | "Properties": { 86 | "FunctionName": { 87 | "Fn::GetAtt": [ 88 | "SlackNotifierLambdaFn", 89 | "Arn" 90 | ] 91 | }, 92 | "Action": "lambda:InvokeFunction", 93 | "Principal": "events.amazonaws.com", 94 | "SourceArn": { 95 | "Fn::GetAtt": [ 96 | "CloudWatchEventRule", 97 | "Arn" 98 | ] 99 | } 100 | } 101 | }, 102 | "CloudWatchEventRule": { 103 | "Type": "AWS::Events::Rule", 104 | "Properties": { 105 | "Description": "EventRule", 106 | "EventPattern": { 107 | "source": [ 108 | "aws.health" 109 | ] 110 | }, 111 | "State": "ENABLED", 112 | "Targets": [ 113 | { 114 | "Arn": { 115 | "Fn::GetAtt": [ 116 | "SlackNotifierLambdaFn", 117 | "Arn" 118 | ] 119 | }, 120 | "Id": "SlackNotifierLambdaFn" 121 | } 122 | ] 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /slack-notifier/cfn-templates/slack-notifier.yml: -------------------------------------------------------------------------------- 1 | --- 2 | AWSTemplateFormatVersion: '2010-09-09' 3 | Parameters: 4 | SlackChannel: 5 | Type: String 6 | Description: 'Please enter your Slack channel name:' 7 | HookURL: 8 | Type: String 9 | Description: 'Please enter the web hook url from Slack:' 10 | NoEcho: true 11 | Resources: 12 | LambdaFunctionRole: 13 | Type: AWS::IAM::Role 14 | Properties: 15 | AssumeRolePolicyDocument: 16 | Version: '2012-10-17' 17 | Statement: 18 | - Effect: Allow 19 | Principal: 20 | Service: 21 | - lambda.amazonaws.com 22 | Action: 23 | - sts:AssumeRole 24 | Path: "/" 25 | LambdaRolePolicies: 26 | Type: AWS::IAM::Policy 27 | Properties: 28 | PolicyName: LambdaPolicy 29 | PolicyDocument: 30 | Version: '2012-10-17' 31 | Statement: 32 | - Sid: Stmt12349896368829 33 | Action: 34 | - logs:CreateLogGroup 35 | - logs:CreateLogStream 36 | - logs:PutLogEvents 37 | Effect: Allow 38 | Resource: arn:aws:logs:*:*:* 39 | Roles: 40 | - Ref: LambdaFunctionRole 41 | SlackNotifierLambdaFn: 42 | Type: AWS::Lambda::Function 43 | Properties: 44 | Description: 'AWS PHD Slack Notifier' 45 | Handler: index.handler 46 | Role: 47 | Fn::GetAtt: 48 | - LambdaFunctionRole 49 | - Arn 50 | Code: 51 | ZipFile: 52 | Fn::Sub: | 53 | # Sample Lambda Function to post notifications to a slack channel when an AWS Health event happens 54 | import json 55 | import logging 56 | from urllib.request import Request, urlopen, URLError, HTTPError 57 | # Setting up logging 58 | logger = logging.getLogger() 59 | logger.setLevel(logging.INFO) 60 | # main function 61 | def handler(event, context): 62 | message = str( 63 | event['detail']['eventDescription'][0]['latestDescription'] + 64 | "\n\n for details." 67 | ) 68 | json.dumps(message) 69 | slack_message = { 70 | "channel": "${SlackChannel}", 71 | "text": message, 72 | "username": "AWS - Personal Health Updates" 73 | } 74 | logger.info(str(slack_message)) 75 | req = Request( 76 | "${HookURL}", 77 | data=json.dumps(slack_message).encode("utf-8"), 78 | headers={"content-type": "application/json"} 79 | ) 80 | try: 81 | response = urlopen(req) 82 | response.read() 83 | logger.info("Message posted to: %s", slack_message['channel']) 84 | except HTTPError as e: 85 | logger.error("Request failed : %d %s", e.code, e.reason) 86 | except URLError as e: 87 | logger.error("Server connection failed: %s", e.reason) 88 | Runtime: python3.11 89 | Timeout: '60' 90 | LambdaInvokePermission: 91 | Type: AWS::Lambda::Permission 92 | Properties: 93 | FunctionName: 94 | Fn::GetAtt: 95 | - SlackNotifierLambdaFn 96 | - Arn 97 | Action: lambda:InvokeFunction 98 | Principal: events.amazonaws.com 99 | SourceArn: 100 | Fn::GetAtt: 101 | - CloudWatchEventRule 102 | - Arn 103 | CloudWatchEventRule: 104 | Type: AWS::Events::Rule 105 | Properties: 106 | Description: EventRule 107 | EventPattern: 108 | source: 109 | - aws.health 110 | State: ENABLED 111 | Targets: 112 | - Arn: 113 | Fn::GetAtt: 114 | - SlackNotifierLambdaFn 115 | - Arn 116 | Id: SlackNotifierLambdaFn 117 | -------------------------------------------------------------------------------- /sms-notifier/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1477516473539", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "arn:aws:logs:*:*:*" 13 | }, 14 | { 15 | "Sid": "Stmt1484080345748", 16 | "Action": [ 17 | "sns:Publish" 18 | ], 19 | "Effect": "Allow", 20 | "Resource": "*" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /sms-notifier/LambdaFunction.js: -------------------------------------------------------------------------------- 1 | // Sample Lambda Function to send notifications via text when an AWS Health event happens 2 | 'use strict'; 3 | 4 | let AWS = require('aws-sdk'); 5 | let sns = new AWS.SNS(); 6 | 7 | //main function which gets AWS Health data from Cloudwatch event 8 | exports.handler = (event, context, callback) => { 9 | //get phone number from Env Variable 10 | let phoneNumber = process.env.PHONE_NUMBER; 11 | //extract details from Cloudwatch event 12 | let eventName = event.detail.eventTypeCode 13 | let healthMessage = `The following AWS Health event type has occured: ${eventName} For more details, please see https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues`; 14 | //prepare message for SNS to publish 15 | let snsPublishParams = { 16 | Message: healthMessage, 17 | PhoneNumber: phoneNumber, 18 | }; 19 | sns.publish(snsPublishParams,(err,data) => { 20 | if (err) { 21 | const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`; 22 | console.log(snsPublishErrorMessage, err, err.stack); // adding the err.stack 23 | callback(snsPublishErrorMessage); 24 | } 25 | 26 | const snsPublishSuccessMessage = `Successfully got details from AWS Health event, ${eventName} and sent SMS via SNS.`; 27 | console.log(snsPublishSuccessMessage, data); 28 | callback(null, snsPublishSuccessMessage); //return success 29 | }); 30 | }; 31 | -------------------------------------------------------------------------------- /sms-notifier/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health SMS Notifier 2 | 3 | ### Description 4 | 5 | This tool can be used to send custom text or SMS notifications via Amazon SNS when an AWS Health event happens by using AWS Lambda and Amazon CloudWatch Events. 6 | 7 | ### Setup and Usage 8 | 9 | Choose **Launch Stack** to launch the template in the US East (N. Virginia) Region in your account: 10 | 11 | [![Launch AWS Health SMS Notifier](../images/cloudformation-launch-stack.png)](https://console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/new?stackName=SmsNotifier&templateURL=https://s3.amazonaws.com/aws-health-tools-assets/cloudformation-templates/sms-notifier.yml) 12 | 13 | The CloudFormation template requires the following parameters: 14 | 15 | - AWS Health Tool configuration 16 | - **Phone number**: The phone number to send notifications to. 17 | 18 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 19 | 20 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing this example and tailoring it to your environment before using it in your production environment. 21 | 22 | ### License 23 | AWS Health Tools are licensed under the Apache 2.0 License. 24 | -------------------------------------------------------------------------------- /sms-notifier/sms-notifier.yml: -------------------------------------------------------------------------------- 1 | Description: > 2 | This template sets up AWS Health Tool to send custom text or SMS notifications via Amazon SNS when an AWS Health event happens by using AWS Lambda and Amazon CloudWatch Events. 3 | 4 | 5 | Parameters: 6 | PhoneNumber: 7 | Type: String 8 | Default: +1XXX5550100 9 | Description: The phone number to send notifications to. 10 | 11 | 12 | Metadata: 13 | AWS::CloudFormation::Interface: 14 | ParameterLabels: 15 | PhoneNumber: 16 | default: "Phone number" 17 | ParameterGroups: 18 | - Label: 19 | default: AWS Health Tool Configuration 20 | Parameters: 21 | - PhoneNumber 22 | 23 | 24 | Resources: 25 | LambdaExecutionRole: 26 | Type: AWS::IAM::Role 27 | Properties: 28 | AssumeRolePolicyDocument: 29 | Version: '2012-10-17' 30 | Statement: 31 | - 32 | Effect: Allow 33 | Principal: 34 | Service: 35 | - lambda.amazonaws.com 36 | Action: 37 | - sts:AssumeRole 38 | Path: "/" 39 | 40 | LambdaRolePolicies: 41 | Type: AWS::IAM::Policy 42 | Properties: 43 | PolicyName: sms-notifier 44 | PolicyDocument: 45 | Version: '2012-10-17' 46 | Statement: 47 | - 48 | Effect: Allow 49 | Action: sns:Publish 50 | Resource: '*' 51 | - 52 | Effect: Allow 53 | Action: 54 | - logs:CreateLogGroup 55 | - logs:CreateLogStream 56 | - logs:PutLogEvents 57 | Resource: arn:aws:logs:*:*:* 58 | Roles: 59 | - 60 | Ref: LambdaExecutionRole 61 | 62 | SmsNotifierFunction: 63 | Type: AWS::Lambda::Function 64 | Properties: 65 | Handler: index.handler 66 | Role: !GetAtt LambdaExecutionRole.Arn 67 | Environment: 68 | Variables: 69 | PHONE_NUMBER: !Ref PhoneNumber 70 | Code: 71 | ZipFile: > 72 | // Sample Lambda Function to send notifications via text when an AWS Health event happens 73 | 74 | 'use strict'; 75 | 76 | let AWS = require('aws-sdk'); 77 | 78 | let sns = new AWS.SNS(); 79 | 80 | //main function which gets AWS Health data from Cloudwatch event 81 | 82 | exports.handler = (event, context, callback) => { 83 | //get phone number from Env Variable 84 | let phoneNumber = process.env.PHONE_NUMBER; 85 | //extract details from Cloudwatch event 86 | let eventName = event.detail.eventTypeCode 87 | let healthMessage = `The following AWS Health event type has occured: ${eventName} For more details, please see https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues`; 88 | //prepare message for SNS to publish 89 | let snsPublishParams = { 90 | Message: healthMessage, 91 | PhoneNumber: phoneNumber, 92 | }; 93 | sns.publish(snsPublishParams,(err,data) => { 94 | if (err) { 95 | const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`; 96 | console.log(snsPublishErrorMessage, err, err.stack); // adding the err.stack 97 | callback(snsPublishErrorMessage); 98 | } 99 | 100 | const snsPublishSuccessMessage = `Successfully got details from AWS Health event, ${eventName} and sent SMS via SNS.`; 101 | console.log(snsPublishSuccessMessage, data); 102 | callback(null, snsPublishSuccessMessage); //return success 103 | }); 104 | }; 105 | Runtime: nodejs12.x 106 | 107 | AwsHealthEventRule: 108 | Type: AWS::Events::Rule 109 | Properties: 110 | Description: AWSHealthEventRule 111 | EventPattern: 112 | source: 113 | - aws.health 114 | State: ENABLED 115 | Targets: 116 | - 117 | Arn: !GetAtt SmsNotifierFunction.Arn 118 | Id: SmsNotifierLambdaFunction 119 | 120 | PermissionForEventsToInvokeLambda: 121 | Type: AWS::Lambda::Permission 122 | Properties: 123 | FunctionName: !Ref SmsNotifierFunction 124 | Action: lambda:InvokeFunction 125 | Principal: events.amazonaws.com 126 | SourceArn: !GetAtt AwsHealthEventRule.Arn 127 | -------------------------------------------------------------------------------- /sms-notifier/testHeatlhEvent.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0", 3 | "id": "7bf73129-1428-4cd3-a780-95db273d1602", 4 | "detail-type": "AWS Health Event", 5 | "source": "aws.health", 6 | "account": "123456789012", 7 | "time": "2016-06-05T06:27:57Z", 8 | "region": "us-east-1", 9 | "resources": [], 10 | "detail": { 11 | "eventArn": "arn:aws:health:us-east-1::event/AWS_HEALTH_TEST_ALERT", 12 | "service": "AWS_EC2", 13 | "eventTypeCode": "AWS_EC2_HEALTH_TEST_EVENT", 14 | "eventTypeCategory": "category", 15 | "startTime": "Sun, 05 Jun 2016 05:01:10 GMT", 16 | "endTime": "Sun, 05 Jun 2016 05:30:57 GMT", 17 | "eventDescription": [{ 18 | "language": "en_US", 19 | "latestDescription": "something is wrong" 20 | }] 21 | } 22 | } -------------------------------------------------------------------------------- /sns-topic-publisher/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Sid": "Stmt1477516473539", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Effect": "Allow", 12 | "Resource": "arn:aws:logs:*:*:*" 13 | }, 14 | { 15 | "Sid": "Stmt1484080345748", 16 | "Action": [ 17 | "sns:Publish" 18 | ], 19 | "Effect": "Allow", 20 | "Resource": "*" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /sns-topic-publisher/LambdaFunction.js: -------------------------------------------------------------------------------- 1 | // Sample Lambda Function to send notifications to a SNS topic when an AWS Health event happens 2 | var AWS = require('aws-sdk'); 3 | var sns = new AWS.SNS(); 4 | 5 | // define configuration 6 | const snsTopic ='arn:aws:sns:us-east-1:083010608567:Test_Topic'; //use ARN 7 | 8 | //main function which gets AWS Health data from Cloudwatch event 9 | exports.handler = (event, context, callback) => { 10 | //extract details from Cloudwatch event 11 | healthMessage = event.detail.eventDescription[0].latestDescription + ' For more details, please see https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues'; 12 | eventName = event.detail.eventTypeCode 13 | //prepare message for SNS to publish 14 | var snsPublishParams = { 15 | Message: healthMessage, 16 | Subject: eventName, 17 | TopicArn: snsTopic 18 | }; 19 | sns.publish(snsPublishParams, function(err, data) { 20 | if (err) { 21 | const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`; 22 | console.log(snsPublishErrorMessage, err); 23 | callback(snsPublishErrorMessage); 24 | } 25 | else { 26 | const snsPublishSuccessMessage = `Successfully got details from AWS Health event, ${eventName} and published to SNS topic.`; 27 | console.log(snsPublishSuccessMessage, data); 28 | callback(null, snsPublishSuccessMessage); //return success 29 | } 30 | }); 31 | }; 32 | 33 | -------------------------------------------------------------------------------- /sns-topic-publisher/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health Amazon Simple Notification Service (SNS) Topic Publisher 2 | 3 | ### Description 4 | This tool can be used to send custom notifications to a SNS topic when an AWS Health event happens by using AWS Lambda and Amazon CloudWatch Events. SNS topic subscribers (for example, web servers, email addresses, Amazon SQS queues, or AWS Lambda functions) can consume or receive the message or notification over one of the supported protocols (Amazon SQS, HTTP/S, email, SMS, Lambda) when they are subscribed to the topic. More information about SNS is available here: http://docs.aws.amazon.com/sns/latest/dg/welcome.html 5 | 6 | ### Setup and Usage 7 | 8 | #### Setup using CloudFormation 9 | 10 | Choose **Launch Stack** to launch the AWS Health SNS Topic Publisher template in the US East (N. Virginia) Region in your account: 11 | 12 | Launch Stack 13 | 14 | Please update the region and the SNS topic name according to your requirements. 15 | 16 | #### Manual setup 17 | 18 | 1. Create an IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 19 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 20 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 21 | 22 | 2. Create a Lambda JavaScript function by using the [sample](LambdaFunction.js) provided and choose the IAM role created in step 1. Update the configuration section of the script with the SNS topic ARN. 23 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 24 | 25 | 3. Create a CloudWatch Events rule to trigger the Lambda function created in step 2 for AWS Health events. 26 | Documentation on how to create AWS Health CloudWatch Events rules is available here: http://docs.aws.amazon.com/health/latest/ug/cloudwatch-events-health.html 27 | 28 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 29 | 30 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing the example and tailoring it to your environment before using it in your production environment. 31 | 32 | ### License 33 | AWS Health Tools are licensed under the Apache 2.0 License. 34 | 35 | 36 | -------------------------------------------------------------------------------- /sns-topic-publisher/cfn-templates/sns-topic-publisher.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | "SNSTopicName": { 5 | "Type": "String", 6 | "Description": "Please enter your SNS Topic Name. (SNS Topic must exist in the same region where this stack is launched)." 7 | } 8 | }, 9 | "Resources": { 10 | "LambdaFunctionRole": { 11 | "Type": "AWS::IAM::Role", 12 | "Properties": { 13 | "AssumeRolePolicyDocument": { 14 | "Version": "2012-10-17", 15 | "Statement": [ 16 | { 17 | "Effect": "Allow", 18 | "Principal": { 19 | "Service": [ 20 | "lambda.amazonaws.com" 21 | ] 22 | }, 23 | "Action": [ 24 | "sts:AssumeRole" 25 | ] 26 | } 27 | ] 28 | }, 29 | "Path": "/" 30 | } 31 | }, 32 | "LambdaRolePolicies": { 33 | "Type": "AWS::IAM::Policy", 34 | "Properties": { 35 | "PolicyName": "LambdaPolicy", 36 | "PolicyDocument": { 37 | "Version": "2012-10-17", 38 | "Statement": [ 39 | { 40 | "Sid": "Stmt1477516473539", 41 | "Action": [ 42 | "logs:CreateLogGroup", 43 | "logs:CreateLogStream", 44 | "logs:PutLogEvents" 45 | ], 46 | "Effect": "Allow", 47 | "Resource": "arn:aws:logs:*:*:*" 48 | }, 49 | { 50 | "Sid": "Stmt1484080345748", 51 | "Action": [ 52 | "sns:Publish" 53 | ], 54 | "Effect": "Allow", 55 | "Resource": { 56 | "Fn::Join": [ 57 | "", 58 | [ 59 | "arn:aws:sns:", 60 | { 61 | "Ref": "AWS::Region" 62 | }, 63 | ":", 64 | { 65 | "Ref": "AWS::AccountId" 66 | }, 67 | ":", 68 | { 69 | "Ref": "SNSTopicName" 70 | } 71 | ] 72 | ] 73 | } 74 | } 75 | ] 76 | }, 77 | "Roles": [ 78 | { 79 | "Ref": "LambdaFunctionRole" 80 | } 81 | ] 82 | } 83 | }, 84 | "SNSPublishFunction": { 85 | "Type": "AWS::Lambda::Function", 86 | "Properties": { 87 | "Handler": "index.handler", 88 | "Role": { 89 | "Fn::GetAtt": [ 90 | "LambdaFunctionRole", 91 | "Arn" 92 | ] 93 | }, 94 | "Environment": { 95 | "Variables": { 96 | "SNSARN": { 97 | "Fn::Join": [ 98 | "", 99 | [ 100 | "arn:aws:sns:", 101 | { 102 | "Ref": "AWS::Region" 103 | }, 104 | ":", 105 | { 106 | "Ref": "AWS::AccountId" 107 | }, 108 | ":", 109 | { 110 | "Ref": "SNSTopicName" 111 | } 112 | ] 113 | ] 114 | } 115 | } 116 | }, 117 | "Code": { 118 | "ZipFile": { 119 | "Fn::Join": [ 120 | "", 121 | [ 122 | "// Sample Lambda Function to send notifications to a SNS topic when an AWS Health event happens\n", 123 | "var AWS = require('aws-sdk');\n", 124 | "var sns = new AWS.SNS();\n", 125 | "\n", 126 | "// define configuration\n", 127 | "const snsTopic =process.env.SNSARN; //use ARN", 128 | "\n", 129 | "//main function which gets AWS Health data from Cloudwatch event\n", 130 | "exports.handler = (event, context, callback) => {\n", 131 | " //extract details from Cloudwatch event\n", 132 | " healthMessage = event.detail.eventDescription[0].latestDescription + ' For more details, please see https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues';\n", 133 | " eventName = event.detail.eventTypeCode\n", 134 | " //prepare message for SNS to publish\n", 135 | " var snsPublishParams = {\n", 136 | " Message: healthMessage, \n", 137 | " Subject: eventName,\n", 138 | " TopicArn: snsTopic\n", 139 | " };\n", 140 | " sns.publish(snsPublishParams, function(err, data) {\n", 141 | " if (err) {\n", 142 | " const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`;\n", 143 | " console.log(snsPublishErrorMessage, err);\n", 144 | " callback(snsPublishErrorMessage);\n", 145 | " } \n", 146 | " else {\n", 147 | " const snsPublishSuccessMessage = `Successfully got details from AWS Health event, ${eventName} and published to SNS topic.`;\n", 148 | " console.log(snsPublishSuccessMessage, data);\n", 149 | " callback(null, snsPublishSuccessMessage); //return success\n", 150 | " }\n", 151 | " });\n", 152 | "};" 153 | ] 154 | ] 155 | } 156 | }, 157 | "Runtime": "nodejs14.x", 158 | "Timeout": "25" 159 | } 160 | }, 161 | "LambdaInvokePermission": { 162 | "Type": "AWS::Lambda::Permission", 163 | "Properties": { 164 | "FunctionName": { 165 | "Fn::GetAtt": [ 166 | "SNSPublishFunction", 167 | "Arn" 168 | ] 169 | }, 170 | "Action": "lambda:InvokeFunction", 171 | "Principal": "events.amazonaws.com", 172 | "SourceArn": { 173 | "Fn::GetAtt": [ 174 | "CloudWatchEventRule", 175 | "Arn" 176 | ] 177 | } 178 | } 179 | }, 180 | "CloudWatchEventRule": { 181 | "Type": "AWS::Events::Rule", 182 | "Properties": { 183 | "Description": "EventRule", 184 | "EventPattern": { 185 | "source": [ 186 | "aws.health" 187 | ] 188 | }, 189 | "State": "ENABLED", 190 | "Targets": [ 191 | { 192 | "Arn": { 193 | "Fn::GetAtt": [ 194 | "SNSPublishFunction", 195 | "Arn" 196 | ] 197 | }, 198 | "Id": "SNSPublishFunction" 199 | } 200 | ] 201 | } 202 | } 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /sns-topic-publisher/cfn-templates/sns-topic-publisher.yml: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: "2010-09-09" 2 | Parameters: 3 | SNSTopicName: 4 | Type: String 5 | Description: Please enter your SNS Topic Name. (SNS Topic must exist in the same region where this stack is launched). 6 | Resources: 7 | LambdaFunctionRole: 8 | Type: "AWS::IAM::Role" 9 | Properties: 10 | AssumeRolePolicyDocument: 11 | Version: "2012-10-17" 12 | Statement: 13 | - 14 | Effect: "Allow" 15 | Principal: 16 | Service: 17 | - "lambda.amazonaws.com" 18 | Action: 19 | - "sts:AssumeRole" 20 | Path: "/" 21 | LambdaRolePolicies: 22 | Type: "AWS::IAM::Policy" 23 | Properties: 24 | PolicyName: "LambdaPolicy" 25 | PolicyDocument: 26 | Version: "2012-10-17" 27 | Statement: 28 | - 29 | Sid: Stmt1477516473539 30 | Action: 31 | - logs:CreateLogGroup 32 | - logs:CreateLogStream 33 | - logs:PutLogEvents 34 | Effect: Allow 35 | Resource: arn:aws:logs:*:*:* 36 | - 37 | Sid: Stmt1484080345748 38 | Action: 39 | - sns:Publish 40 | Effect: Allow 41 | Resource: 42 | Fn::Join: 43 | - "" 44 | - - "arn:aws:sns:" 45 | - !Ref "AWS::Region" 46 | - ":" 47 | - !Ref "AWS::AccountId" 48 | - ":" 49 | - !Ref "SNSTopicName" 50 | Roles: 51 | - 52 | Ref: "LambdaFunctionRole" 53 | SNSPublishFunction: 54 | Type: "AWS::Lambda::Function" 55 | Properties: 56 | Handler: "index.handler" 57 | Role: 58 | Fn::GetAtt: 59 | - "LambdaFunctionRole" 60 | - "Arn" 61 | Environment: 62 | Variables: 63 | SNSARN: 64 | Fn::Join: 65 | - "" 66 | - - "arn:aws:sns:" 67 | - !Ref "AWS::Region" 68 | - ":" 69 | - !Ref "AWS::AccountId" 70 | - ":" 71 | - !Ref "SNSTopicName" 72 | Code: 73 | ZipFile: !Sub | 74 | // Sample Lambda Function to send notifications to a SNS topic when an AWS Health event happens 75 | var AWS = require('aws-sdk'); 76 | var sns = new AWS.SNS(); 77 | 78 | // define configuration 79 | const snsTopic =process.env.SNSARN; //use ARN 80 | 81 | //main function which gets AWS Health data from Cloudwatch event 82 | exports.handler = (event, context, callback) => { 83 | //extract details from Cloudwatch event 84 | healthMessage = event.detail.eventDescription[0].latestDescription + ' For more details, please see https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues'; 85 | eventName = event.detail.eventTypeCode 86 | //prepare message for SNS to publish 87 | var snsPublishParams = { 88 | Message: healthMessage, 89 | Subject: eventName, 90 | TopicArn: snsTopic 91 | }; 92 | sns.publish(snsPublishParams, function(err, data) { 93 | if (err) { 94 | const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`; 95 | console.log(snsPublishErrorMessage, err); 96 | callback(snsPublishErrorMessage); 97 | } 98 | else { 99 | const snsPublishSuccessMessage = `Successfully got details from AWS Health event, ${!eventName} and published to SNS topic.`; 100 | console.log(snsPublishSuccessMessage, data); 101 | callback(null, snsPublishSuccessMessage); //return success 102 | } 103 | }); 104 | }; 105 | Runtime: "nodejs14.x" 106 | Timeout: "25" 107 | LambdaInvokePermission: 108 | Type: "AWS::Lambda::Permission" 109 | Properties: 110 | FunctionName: 111 | Fn::GetAtt: 112 | - "SNSPublishFunction" 113 | - "Arn" 114 | Action: "lambda:InvokeFunction" 115 | Principal: "events.amazonaws.com" 116 | SourceArn: 117 | !GetAtt CloudWatchEventRule.Arn 118 | CloudWatchEventRule: 119 | Type: "AWS::Events::Rule" 120 | Properties: 121 | Description: "EventRule" 122 | EventPattern: 123 | source: 124 | - "aws.health" 125 | State: "ENABLED" 126 | Targets: 127 | - 128 | Arn: 129 | Fn::GetAtt: 130 | - "SNSPublishFunction" 131 | - "Arn" 132 | Id: "SNSPublishFunction" 133 | -------------------------------------------------------------------------------- /teams-notifier/IAMPolicy: -------------------------------------------------------------------------------- 1 | { 2 | "Version": "2012-10-17", 3 | "Statement": [ 4 | { 5 | "Effect": "Allow", 6 | "Action": [ 7 | "logs:CreateLogGroup", 8 | "logs:CreateLogStream", 9 | "logs:PutLogEvents" 10 | ], 11 | "Resource": "*" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /teams-notifier/LambdaFunction.py: -------------------------------------------------------------------------------- 1 | #Sample Lambda Function to post notifications to a Teams room when an AWS Health event happens 2 | import json 3 | import logging 4 | import os 5 | 6 | from urllib.request import Request, urlopen 7 | from urllib.error import URLError, HTTPError 8 | 9 | TEAMSWEBHOOK = "" 10 | 11 | # Setting up logging 12 | logger = logging.getLogger() 13 | logger.setLevel(logging.INFO) 14 | 15 | # main function 16 | def lambda_handler(event, context): 17 | """Post a message to the Teams Room when a new AWS Health event is generated""" 18 | message = str(event['detail']['eventDescription'][0]['latestDescription'] + " https://phd.aws.amazon.com/phd/home?region=us-east-1#/event-log?eventID=" + event['detail']['eventArn']) 19 | json.dumps(message) 20 | 21 | teams_message = { 22 | "@context": "https://schema.org/extensions", 23 | "@type": "MessageCard", 24 | "title": "AWS - Personal Health Updates", 25 | "text": message 26 | } 27 | 28 | logger.info(str(teams_message)) 29 | req = Request(TEAMSWEBHOOK, json.dumps(teams_message).encode('utf-8')) 30 | 31 | try: 32 | response = urlopen(req) 33 | response.read() 34 | logger.info("Message posted") 35 | return {"status": "200 OK"} 36 | except HTTPError as e: 37 | logger.error("Request failed : %d %s", e.code, e.reason) 38 | except URLError as e: 39 | logger.error("Server connection failed: %s", e.reason) 40 | -------------------------------------------------------------------------------- /teams-notifier/README.md: -------------------------------------------------------------------------------- 1 | ## AWS Health Teams Notifier 2 | 3 | ### Description 4 | 5 | This tool can be used to post alerts to a MS Teams channel when AWS Health events are generated by using AWS Lambda and Amazon CloudWatch Events. 6 | The message will contain the latest event description and a link to the AWS Health Console; e.g. "Event Description https://phd.aws.amazon.com/phd/home?region=us-east-1#/event-log?eventID=arn:aws:health:us-west-2::event/AWS_EVENT_ID". 7 | 8 | ### MS Teams Setup 9 | Follow these steps to configure the webhook in Teams: 10 | 11 | 1. Click the three dots from your Teams channel 12 | 13 | 2. Select "Connectors" 14 | 15 | 3. Create Incoming Webhook 16 | 17 | 4. Copy the webhook URL and use it as the value for the *HookURL* parameter in the Cloudformation template. 18 | 19 | Security Note: WebHooks should be treated like passwords and should not be shared publicily. 20 | 21 | ### AWS setup using CloudFormation 22 | 23 | #### CloudFormation 24 | 25 | The CloudFormation template requires the following parameter: 26 | 27 | *HookURL* - Incoming web hook url from MS Teams setup. 28 | 29 | 30 | ### AWS Manual Setup 31 | 32 | 1. Create an IAM role for the Lambda function to use. Attach the [IAM policy](IAMPolicy) to the role in the IAM console. 33 | 34 | Documentation on how to create an IAM policy is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_create.html 35 | Documentation on how to create an IAM role for Lambda is available here: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_for-service.html#roles-creatingrole-service-console 36 | 37 | 2. Create a Lambda Python function by using the [sample](LambdaFunction.py) provided and choose the IAM role created in step 1. Add an enviroment variable with key TEAMSWEBHOOK and the webhook URL from the Chime setup above as value. 38 | 39 | More information about Lambda is available here: http://docs.aws.amazon.com/lambda/latest/dg/getting-started.html 40 | 41 | 3. Create a CloudWatch Events rule to trigger the Lambda function created in step 2 for AWS Health events. 42 | 43 | Documentation on how to create an AWS Health CloudWatch Events rule is available here: http://docs.aws.amazon.com/health/latest/ug/cloudwatch-events-health.html 44 | 45 | More information about AWS Health is available here: http://docs.aws.amazon.com/health/latest/ug/what-is-aws-health.html 46 | 47 | Note that this is a just an example of how to set up automation with AWS Health, Amazon CloudWatch Events, and AWS Lambda. We recommend testing the example and tailoring it to your environment before using it in your production environment. 48 | 49 | You can also test the Lambda function by invoking it manually and using a [sample AWS Health event data.](http://docs.aws.amazon.com/AmazonCloudWatch/latest/events/EventTypes.html#health-event-types) 50 | 51 | ### License 52 | AWS Health Tools are licensed under the Apache 2.0 License 53 | -------------------------------------------------------------------------------- /teams-notifier/cfn-templates/teams-notifier.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Parameters": { 4 | "HookURL": { 5 | "Type": "String", 6 | "Description": "Please enter the web hook url from Teams:", 7 | "NoEcho": true 8 | } 9 | }, 10 | "Resources": { 11 | "LambdaFunctionRole": { 12 | "Type": "AWS::IAM::Role", 13 | "Properties": { 14 | "AssumeRolePolicyDocument": { 15 | "Version": "2012-10-17", 16 | "Statement": [ 17 | { 18 | "Effect": "Allow", 19 | "Principal": { 20 | "Service": [ 21 | "lambda.amazonaws.com" 22 | ] 23 | }, 24 | "Action": [ 25 | "sts:AssumeRole" 26 | ] 27 | } 28 | ] 29 | }, 30 | "Path": "/" 31 | } 32 | }, 33 | "LambdaRolePolicies": { 34 | "Type": "AWS::IAM::Policy", 35 | "Properties": { 36 | "PolicyName": "LambdaPolicy", 37 | "PolicyDocument": { 38 | "Version": "2012-10-17", 39 | "Statement": [ 40 | { 41 | "Sid": "Stmt12349896368829", 42 | "Action": [ 43 | "logs:CreateLogGroup", 44 | "logs:CreateLogStream", 45 | "logs:PutLogEvents" 46 | ], 47 | "Effect": "Allow", 48 | "Resource": "arn:aws:logs:*:*:*" 49 | } 50 | ] 51 | }, 52 | "Roles": [ 53 | { 54 | "Ref": "LambdaFunctionRole" 55 | } 56 | ] 57 | } 58 | }, 59 | "TeamsNotifierLambdaFn": { 60 | "Type": "AWS::Lambda::Function", 61 | "Properties": { 62 | "Handler": "index.lambda_handler", 63 | "Role": { 64 | "Fn::GetAtt": [ 65 | "LambdaFunctionRole", 66 | "Arn" 67 | ] 68 | }, 69 | "Environment": { 70 | "Variables": { 71 | "TEAMSWEBHOOK": { 72 | "Ref": "HookURL" 73 | } 74 | } 75 | }, 76 | "Code": { 77 | "ZipFile": { 78 | "Fn::Sub": "#Sample Lambda Function to post notifications to a Teams room when an AWS Health event happens\nimport json \nimport logging \nimport os \nfrom urllib.request import Request, urlopen\nfrom urllib.error import URLError, HTTPError \n# Setting up logging \nlogger = logging.getLogger() \nlogger.setLevel(logging.INFO) \n# main function \ndef lambda_handler(event, context): \n message = str(event['detail']['eventDescription'][0]['latestDescription'] + \" https://phd.aws.amazon.com/phd/home?region=us-east-1#/event-log?eventID=\" + event['detail']['eventArn'])\n json.dumps(message) \n teams_message = {\"@context\": \"https://schema.org/extensions\",\"@type\": \"MessageCard\",\"title\": \"AWS - Personal Health Update\",\"text\": message} \n logger.info(str(teams_message))\n webhookurl = str(os.environ['TEAMSWEBHOOK'])\n req = Request(webhookurl, json.dumps(teams_message).encode('utf-8')) \n try: \n response = urlopen(req) \n response.read() \n logger.info(\"Message posted\") \n return {\"status\": \"200 OK\"} \n except HTTPError as e: \n logger.error(\"Request failed : %d %s\", e.code, e.reason) \n except URLError as e: \n logger.error(\"Server connection failed: %s\", e.reason) \n" 79 | } 80 | }, 81 | "Runtime": "python3.7", 82 | "Timeout": "60" 83 | } 84 | }, 85 | "LambdaInvokePermission": { 86 | "Type": "AWS::Lambda::Permission", 87 | "Properties": { 88 | "FunctionName": { 89 | "Fn::GetAtt": [ 90 | "TeamsNotifierLambdaFn", 91 | "Arn" 92 | ] 93 | }, 94 | "Action": "lambda:InvokeFunction", 95 | "Principal": "events.amazonaws.com", 96 | "SourceArn": { 97 | "Fn::GetAtt": [ 98 | "CloudWatchEventRule", 99 | "Arn" 100 | ] 101 | } 102 | } 103 | }, 104 | "CloudWatchEventRule": { 105 | "Type": "AWS::Events::Rule", 106 | "Properties": { 107 | "Description": "EventRule", 108 | "EventPattern": { 109 | "source": [ 110 | "aws.health" 111 | ] 112 | }, 113 | "State": "ENABLED", 114 | "Targets": [ 115 | { 116 | "Arn": { 117 | "Fn::GetAtt": [ 118 | "TeamsNotifierLambdaFn", 119 | "Arn" 120 | ] 121 | }, 122 | "Id": "TeamsNotifierLambdaFn" 123 | } 124 | ] 125 | } 126 | } 127 | } 128 | } 129 | --------------------------------------------------------------------------------