4. Create Lambda Function

In this step, we will be creating an AWS Lambda function to parse the AWS abuse event, publish a notification to the SNS topic created in Step 1, and stop/terminate the non-production EC2 instances created in Step 2 that are reported as part of the Abuse event.

  1. From the AWS Management Console, navigate to the N. Virginia (us-east-1) region.

  2. Navigate to the AWS Lambda console by clicking on the Find Services search bar, typing Lambda in the search bar, and pressing Enter.

    Open Lambda Console

  3. In the Navigation pane, click on Functions

  4. Click on Create function.

  5. Use the default selection Author from scratch

  6. Enter a Name for the Lambda function. Example: aws_health_dos_abuse_report_handler_lambda_reinvent

  7. In the Runtime drop-down, select Node.js 12.x

  8. Click on Choose or create an execution role

  9. Select Use an existing role

  10. Select the role created in Step 2, aws_health_dos_lambda_role_reinvent if you used the suggested name

  11. Click on Create function

  12. Scroll down to Function Code and paste the code below in the editor:

    // Sample Lambda Function to stop/terminate non-Prod EC2 instances that are
    // reported as part of a Denial of Service AWS Health event. Also send
    // notifications to an SNS topic.
    var AWS = require('aws-sdk');
    var sns = new AWS.SNS();
        
    // define configuration
    const snsTopic = process.env.SNSARN;
    const tagKey = process.env.EC2_STAGE_TAG_KEY;
    const tagValue = process.env.EC2_PROD_STAGE_TAG_VALUE;
    const action = process.env.EC2_ACTION;
    const dryRun = process.env.DRY_RUN;
        
    function setupClient(region) {
        // set the region for the sdk
        AWS.config.update({ region: region });
        //create the ec2 client
        return new AWS.EC2();
    }
        
    function getParams(instances, dryRun) {
        // setup parameters
        var instancesParams = {
            InstanceIds: instances,
            DryRun: false
        };
        // enable DryRun if set in environment variables
        if (dryRun == 'true') {
            instancesParams.DryRun = true;
            console.log()
        }
        return instancesParams
    }
        
    // Main function which gets AWS Health data from CloudWatch event
    exports.handler = (event, context, callback) => {
        
        // function to handle ec2 API response
        function handleResponse(err, data) {
            if (err) {
                // an error occurred
                if (err.code == 'DryRunOperation') {
                    console.log(instances, region, err.message);
                    callback(null, awsHealthSuccessMessage);
                }
                else {
                    console.log(instances, region, err, err.stack);
                    throw err;
                }
        
            }
            else {
                // successful response
                console.log(`Instance ${action}: `, instances, region);
        
                snsPublishParams = {
                    Message: `Instance ${action} invoked on Non-Prod EC2 instance(s) part of DoS event.`,
                    Subject: eventName,
                    TopicArn: snsTopic
                };
                sns.publish(snsPublishParams, function(err, data) {
                    if (err) {
                        const snsPublishErrorMessage = `Error publishing confirmation of automation action taken on the EC2 instance(s) to SNS`;
                        console.log(snsPublishErrorMessage, err);
                    }
                    else {
                        const snsPublishSuccessMessage = `Successfully actioned the EC2 instance(s) and published to SNS topic.`;
                        console.log(snsPublishSuccessMessage, data);
                    }
                });
        
                //return success
                callback(null, awsHealthSuccessMessage);
            }
        }
        
        //extract details from CloudWatch event
        var healthMessage = event.detail.eventDescription[0].latestDescription + ' Non-Prod EC2 instances part of DoS report will be attempted to be stopped/terminated. For more details, please see https://phd.aws.amazon.com/phd/home?region=us-east-1#/dashboard/open-issues';
        var eventName = event.detail.eventTypeCode;
        var affectedEntities = event.detail.affectedEntities;
        var region = 'us-east-1'; // Setting to us-east-1 for demo. Region will have to be determined based on the region of each instance.
        
        const awsHealthSuccessMessage = `Successfully parsed details from AWS Health event ${eventName}, and executed automated action.`;
        
        //prepare message for SNS to publish
        var snsPublishParams = {
            Message: healthMessage,
            Subject: eventName,
            TopicArn: snsTopic
        };
        sns.publish(snsPublishParams, function(err, data) {
            if (err) {
                const snsPublishErrorMessage = `Error publishing AWS Health event to SNS`;
                console.log(snsPublishErrorMessage, err);
            }
            else {
                const snsPublishSuccessMessage = `Successfully actioned EC2 instances, and published to SNS topic.`;
                console.log(snsPublishSuccessMessage, data);
            }
        });
        
        // Get a list of all the EC2 instances reported as part of the event.
        var instances = [];
        for (var i = 0; i < affectedEntities.length; i++) {
            if (affectedEntities[i].entityValue.split(":")[2] === "ec2") {
                // Check if the entity is an EC2 instance.
                var instanceArn = affectedEntities[i].entityValue;
                // Extract the ID from ARN.
                instances.push(instanceArn.split("/")[instanceArn.split("/").length - 1]);
            }
        }
        
        if (instances.length > 0) {
            //there are some instances to take action on
           //create an ec2 api client in the event's region
            var ec2 = setupClient(region);
        
            // setup parameters
            var instancesParams = getParams(instances, dryRun);
        
            // DecsribeInstances that are associated with this event.
            ec2.describeInstances(instancesParams, function(err, data) {
                if (err) {
                    console.log("Error", err.stack);
                }
                else {
                        
                    var allInstancesDescribed = data.Reservations.map(x => x.Instances).flat();
                        
                    // Filter the list of instances described to select only those 
                    // instances that have Stage!=Prod key:value pair.
                    var nonProdInstances = allInstancesDescribed.filter(function(instance) {
                                               
                        for (var j = 0; j < instance.Tags.length; j++) {
                            if ((instance.Tags[j].Key == tagKey) && (instance.Tags[j].Value == tagValue)) {
                                return false;
                            }
                        }
                        console.log("Non-Prod instance found", instance.InstanceId);
                        return true;
                    });
        
                    instances = nonProdInstances.map(x=> x.InstanceId);
                    instancesParams = getParams(instances, dryRun);
                        
                    console.log(`attempting to ${action} the following instances: `, instances);
                        
                    // Call either the Terminate or the Stop API
                    if (action == 'Terminate') 
                        ec2.terminateInstances(instancesParams, handleResponse);
                    else 
                        ec2.stopInstances(instancesParams, handleResponse);
                }
            });
        }
        else {
            console.log('No instances in the event match the required tags, exiting without any action');
            callback(null, awsHealthSuccessMessage);
        }
    };
    
  13. Scroll down to the Environment variables panel and create the following:

Be sure to replace <<ARN_of_SNS_Topic>> with the topic ARN you created as part of Step 1.

  • Key=SNSARN; Value=<<ARN_of_SNS_Topic>>
  • Key=DRY_RUN; Value=false
  • Key=EC2_ACTION; Value=Stop
  • Key=EC2_STAGE_TAG_KEY; Value=Stage
  • Key=EC2_PROD_STAGE_TAG_VALUE; Value=Prod
  1. Under Basic settings, set timeout to 25 sec.

  2. Click on Save to save changes to the Lambda function.