AWS Compute Blog
Automating notifications when AMI permissions change
This post is courtesy of Ernes Taljic, Solutions Architect and Sudhanshu Malhotra, Solutions Architect
This post demonstrates how to automate alert notifications when users modify the permissions of an Amazon Machine Image (AMI). You can use it as a blueprint for a wide variety of alert notifications by making simple modifications to the events that you want to receive alerts about. For example, updating the specific operation in Amazon CloudWatch allows you to receive alerts on any activity that AWS CloudTrail captures.
This post walks you through on how to configure an event rule in CloudWatch that triggers an AWS Lambda function. The Lambda function uses Amazon SNS to send an email when an AMI changes to public, private, shared, or unshared with one or more AWS accounts.
Solution overview
The following diagram describes the solution at a high level:
- A user changes an attribute of an AMI.
- CloudTrail logs the change as a
ModifyImageAttribute
API event. - A CloudWatch Events rule captures this event.
- The CloudWatch Events rule triggers a Lambda function.
- The Lambda function publishes a message to the defined SNS topic.
- SNS sends an email alert to the topic’s subscribers.
Deployment walkthrough
To implement this solution, you must create:
- An SNS topic
- An IAM role
- A Lambda function
- A CloudWatch Events rule
Step 1: Creating an SNS topic
To create an SNS topic, complete the following steps:
- Open the SNS console.
- Under Create topic, for Topic name, enter a name and choose Create topic. You can now see the MySNSTopic page. The Details section displays the topic’s Name, ARN, Display name (optional), and the AWS account ID of the Topic owner.
- In the Details section, copy the topic ARN to the clipboard, for example:
arn:aws:sns:us-east-1:123456789012:MySNSTopic
- On the left navigation pane, choose Subscriptions, Create subscription.
- On the Create subscription page, do the following:
- Enter the topic ARN of the topic you created earlier:
arn:aws:sns:us-east-1:123456789012:MySNSTopic
- For Protocol, select Email.
- For Endpoint, enter an email address that can receive notifications.
- Choose Create subscription.
- Enter the topic ARN of the topic you created earlier:
Step 2: Creating an IAM role
To create an IAM role, complete the following steps. For more information, see Creating an IAM Role.
- In the IAM console, choose Policies, Create Policy.
- On the JSON tab, enter the following IAM policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": [
"arn:aws:logs:*:*:*"
],
"Effect": "Allow",
"Sid": "LogStreamAccess"
},
{
"Action": [
"sns:Publish"
],
"Resource": [
"arn:aws:sns:*:*:*"
],
"Effect": "Allow",
"Sid": "SNSPublishAllow"
},
{
"Action": [
"iam:ListAccountAliases"
],
"Resource": "*",
"Effect": "Allow",
"Sid": "ListAccountAlias"
}
]
}
3. Choose Review policy.
4. Enter a name (MyCloudWatchRole) for this policy and choose Create policy. Note the name of this policy for later steps.
5. In the left navigation pane, choose Roles, Create role.
6. On the Select role type page, choose Lambda and the Lambda use case.
7. Choose Next: Permissions.
8. Filter policies by the policy name that you just created, and select the check box.
9. Choose Next: Tags, and give it an appropriate tag.
10. Choose Next: Review. Give this IAM role an appropriate name, and note it for future use.
11. Choose Create role.
Step 3: Creating a Lambda function
To create a Lambda function, complete the following steps. For more information, see Create a Lambda Function with the Console.
- In the Lambda console, choose Author from scratch.
- For Function Name, enter the name of your function.
- For Runtime, choose Python 3.7.
- For Execution role, select Use an existing role, then select the IAM role created in the previous step.
- Choose Create Function, remove the default function, and copy the following code into the Function Code window:
# Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License").
# You may not use this file except in compliance with the License.
# A copy of the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file.
# This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
# either express or implied. See the License for the specific language governing permissions
# and limitations under the License.
#
# Description: This Lambda function sends an SNS notification to a given AWS SNS topic when an API event of \"Modify Image Attribute\" is detected.
# The SNS subject is- "API call-<insert call event> by < insert user name> detected in Account-<insert account alias>, see message for further details".
# The JSON message body of the SNS notification contains the full event details.
#
#
# Author: Sudhanshu Malhotra
import json
import boto3
import logging
import os
import botocore.session
from botocore.exceptions import ClientError
session = botocore.session.get_session()
logging.basicConfig(level=logging.DEBUG)
logger=logging.getLogger(__name__)
import ipaddress
import traceback
def lambda_handler(event, context):
logger.setLevel(logging.DEBUG)
eventname = event['detail']['eventName']
snsARN = os.environ['snsARN'] #Getting the SNS Topic ARN passed in by the environment variables.
user = event['detail']['userIdentity']['type']
srcIP = event['detail']['sourceIPAddress']
imageId = event['detail']['requestParameters']['imageId']
launchPermission = event['detail']['requestParameters']['launchPermission']
imageAction = list(launchPermission.keys())[0]
accnt_num =[]
if imageAction == "add":
if "userId" in launchPermission['add']['items'][0].keys():
accnt_num = [li['userId'] for li in launchPermission['add']['items']] # Get the AWS account numbers that the image was shared with
imageAction = "Image shared with AWS account: " + str(accnt_num)[1:-1]
else:
imageAction = "Image made Public"
elif imageAction == "remove":
if "userId" in launchPermission['remove']['items'][0].keys():
accnt_num = [li['userId'] for li in launchPermission['remove']['items']]
imageAction = "Image Unshared with AWS account: " + str(accnt_num)[1:-1] # Get the AWS account numbers that the image was unshared with
else:
imageAction = "Image made Private"
logger.debug("Event is --- %s" %event)
logger.debug("Event Name is--- %s" %eventname)
logger.debug("SNSARN is-- %s" %snsARN)
logger.debug("User Name is -- %s" %user)
logger.debug("Source IP Address is -- %s" %srcIP)
client = boto3.client('iam')
snsclient = boto3.client('sns')
response = client.list_account_aliases()
logger.debug("List Account Alias response --- %s" %response)
# Check if the source IP is a valid IP or AWS service DNS name.
# If DNS name then we ignore the API activity as this is internal AWS operation
# For more information check - https://aws.amazon.com/premiumsupport/knowledge-center/cloudtrail-root-action-logs/
try:
validIP = ipaddress.ip_address(srcIP)
logger.debug("IP addr is-- %s" %validIP)
except Exception as e:
logger.error("Catching the traceback error: %s" %traceback.format_exc())
logger.debug("Seems like the root API activity was caused by an internal operation. IP address is internal service DNS name")
return
try:
if not response['AccountAliases']:
accntAliase = (boto3.client('sts').get_caller_identity()['Account'])
logger.info("Account Aliase is not defined. Account ID is %s" %accntAliase)
else:
accntAliase = response['AccountAliases'][0]
logger.info("Account Aliase is : %s" %accntAliase)
except ClientError as e:
logger.error("Client Error occured")
try:
publish_message = ""
publish_message += "\nImage Attribute change summary" + "\n\n"
publish_message += "##########################################################\n"
publish_message += "# Event Name- " +str(eventname) + "\n"
publish_message += "# Account- " +str(accntAliase) + "\n"
publish_message += "# AMI ID- " +str(imageId) + "\n"
publish_message += "# Image Action- " +str(imageAction) + "\n"
publish_message += "# Source IP- " +str(srcIP) + "\n"
publish_message += "##########################################################\n"
publish_message += "\n\n\nThe full event is as below:- \n\n" +str(event) +"\n"
logger.debug("MESSAGE- %s" %publish_message)
#Sending the notification...
snspublish = snsclient.publish(
TargetArn= snsARN,
Subject=(("Image Attribute change API call-\"%s\" detected in Account-\"%s\"" %(eventname,accntAliase))[:100]),
Message=publish_message
)
except ClientError as e:
logger.error("An error occured: %s" %e)
6. In the Environment variables section, enter the following key-value pair:
- Key=
snsARN
- Value= the ARN of the MySNSTopic created earlier
7. Choose Save.
Step 4: Creating a CloudWatch Events rule
To create a CloudWatch Events rule, complete the following steps. This rule catches when a user performs a ModifyImageAttribute
API event and triggers the Lambda function (set as a target).
1. In the CloudWatch console, choose Rules, Create rule.
- On the Step 1: Create rule page, under Event Source, select Event Pattern.
- Copy the following event into the preview pane:
{
"source": [
"aws.ec2"
],
"detail-type": [
"AWS API Call via CloudTrail"
],
"detail": {
"eventSource": [
"ec2.amazonaws.com"
],
"eventName": [
"ModifyImageAttribute"
]
}
}
- For Targets, select Lambda function, and select the Lambda function created in Step 2.
- Choose Configure details.
2. On the Step 2: Configure rule details page, enter a name and description for the rule.
3. For State, select Enabled.
4. Choose Create rule.
Solution validation
Confirm that the solution works by changing an AMI:
- Open the Amazon EC2 console. From the menu, select AMIs under the Images heading.
- Select one of the AMIs, and choose Actions, Modify Image Permissions. To create an AMI, see How do I create an AMI that is based on my EBS-backed EC2 instance?
- Choose Private.
- For AWS Account Number, choose an account with which to share the AMI.
- Choose Add Permission, Save.
- Check your inbox to verify that you received an email from SNS. The email contains a summary message with the following information, followed by the full event:
- Event Name
- Account
- AMI ID
- Image Action
- Source IP
For Image Action
, the email lists one of the following events:
The message also includes the account ID of any shared or unshared accounts.
Creating other alert notifications
This post shows you how to automate notifications when AMI permissions change, but the solution is a blueprint for a wide variety of use cases that require alert notifications. You can use the CloudTrail event history to find the API associated with the event that you want to receive notifications about, and create a new CloudWatch Events rule for that event.
- Open the CloudTrail console and choose Event history.
- Explore the CloudTrail event history and locate the event name associated with the actions that you performed in your AWS account.
- Make a note of the event name and modify the
eventName
parameter in Step 4, 1.b to configure alerts for that particular event.
Conclusion
This post demonstrated how you can use CloudWatch to create automated notifications when AMI permissions change. Additionally, you can use the CloudTrail event history to find the API for other events, and use the preceding walkthrough to create other event alerts.
For further reading, see the following posts:
- How do I share an Amazon Machine Image (AMI) privately with another AWS account?
- Monitor and Notify on AWS Account Root User Activity