Take an action based on AWS API calls
Don’t lose your energy marbles in this crazy building game. Gizmos puts players in the role of inventors. By using the four types of energy marbles, they will create their own personal Gizmo, adding on one Machine card at a time. Machines award you victory points and enable additional actions upon meeting specific conditions. As you build, new attachments can trigger chain reactions, letting you do even more on your turn. For more information, visit: boardgamegeek
Overview; Detect AWS console login
This blog post explores the integration of AWS CloudTrail and EventBridge, forming a winning combination
. The objective is to detect and notify logins to my AWS account. So, whenever someone successfully logs into my AWS account, I will receive an email notification containing the login details.
While this blog post may seem straightforward, it serves as an insightful initiative for developing more intricate integrations. Towards the end of the post, I will present some additional ideas. Before diving into the implementation, let's first explore the concept of event-driven architecture. After gaining a solid understanding of this concept, we'll proceed to examine AWS EventBridge and CloudTrail.
Event-Driven Architecture
Event-driven architecture relies on events to initiate communication among loosely connected services, a common feature in modern microservices-based applications. But what exactly constitutes an event? An event can be defined as a change in state or an update, for example in this blog post example, login status. Another example could be altering the state of an EC2 machine. Or consider an e-commerce application, where events include actions like adding or removing items from the shopping cart.
Within this architecture, events are generated by a producer, and actions are taken by a consumer in response to these events. The system may feature multiple consumers, necessitating the routing of events from the producer to these consumers. Event brokers play a crucial role in facilitating the seamless flow of events between producers and consumers.
Let's delve into the various roles in this example. Returning to the project's objective, I aim to identify user console logins and receive an email for each successful login. In this architecture, my intention is to leverage native AWS services for login detection and email notifications (Zero-Code
approach). The following section provides a brief overview of each party involved.
AWS CloudTrail
AWS CloudTrail is a service that allows users to monitor, log, and retain account activity within their AWS infrastructure. This service captures detailed information about actions taken through the AWS Management Console, AWS Command Line Interface (CLI), AWS SDKs, and other AWS services. By providing a transparent and comprehensive trail of events, AWS CloudTrail enhances security and compliance by enabling users to track changes, detect potential security threats, and troubleshoot operational issues. With its ability to deliver a detailed chronological record of API calls and related events, AWS CloudTrail is a valuable tool to detect user console login. According to the event-driven architecture, AWS CloudTrail acts as an event producer.
AWS SNS
Amazon Simple Notification Service (Amazon SNS) is a fully managed messaging service to facilitate the building of distributed, scalable, and reliable applications by enabling the decoupling of microservices, systems, and distributed components. With SNS, users can send messages, or notifications, to a distributed set of subscribers or endpoints via various protocols, including HTTP, HTTPS, email, SMS, and more. AWS SNS is widely used for building event-driven systems, managing workflows, and sending alerts in a scalable and efficient manner within the AWS cloud environment. I utilize AWS SNS to send notifications based on the events received from AWS CloudTrail.
AWS EventBridge
And finally, there's AWS EventBridge, which serves as the heart of this integration. Amazon EventBridge is a serverless event bus service designed to connect various applications and AWS services seamlessly. It plays a crucial role in decoupling the architecture and simplifying event routing. I highly recommend checking out the AWS tutorial on AWS EventBridge for more in-depth insights: AWS EventBridge Learning
Now, let's enhance the event-driven architecture diagram to reflect the changes made in this project:
Implementation
Finally, I've reached the point of detailing the implementation steps, which happens to be the most straightforward section for me to write. As indicated by the weblog's title, the implementation is zero-code, eliminating the necessity for any custom scripting. I opt for Terraform for the entire deployment process. However, if Terraform isn't your preference, you can effortlessly utilize the AWS console instead.
The below diagram shows all the services utilized in this project:
Firstly, we need to enable AWS CloudTrail, and for this, an AWS S3 bucket is required to store all the logs. Therefore, the initial step involves creating a bucket:
# data blocks gather information
data "aws_caller_identity" "gizmos" {}
data "aws_partition" "gizmos" {}
data "aws_region" "gizmos" {}
# data block of S3 bucket iam policy document
data "aws_iam_policy_document" "gizmos" {
statement {
sid = "AWSCloudTrailAclCheck"
effect = "Allow"
principals {
type = "Service"
identifiers = ["cloudtrail.amazonaws.com"]
}
actions = ["s3:GetBucketAcl"]
resources = [aws_s3_bucket.gizmos.arn]
condition {
test = "StringEquals"
variable = "aws:SourceArn"
values = ["arn:${data.aws_partition.gizmos.partition}:cloudtrail:${data.aws_region.gizmos.name}:${data.aws_caller_identity.gizmos.account_id}:trail/cloudynotes-gizmos-trial"]
}
}
statement {
sid = "AWSCloudTrailWrite"
effect = "Allow"
principals {
type = "Service"
identifiers = ["cloudtrail.amazonaws.com"]
}
actions = ["s3:PutObject"]
resources = ["${aws_s3_bucket.gizmos.arn}/AWSLogs/${data.aws_caller_identity.gizmos.account_id}/*"]
condition {
test = "StringEquals"
variable = "s3:x-amz-acl"
values = ["bucket-owner-full-control"]
}
condition {
test = "StringEquals"
variable = "aws:SourceArn"
values = ["arn:${data.aws_partition.gizmos.partition}:cloudtrail:${data.aws_region.gizmos.name}:${data.aws_caller_identity.gizmos.account_id}:trail/cloudynotes-gizmos-trial"]
}
}
}
# Create S3 bucket
resource "aws_s3_bucket" "gizmos" {
bucket = "cloudynotes-gizmos-trial"
force_destroy = true
}
# Add S3 bucket policy
resource "aws_s3_bucket_policy" "gizmos" {
bucket = aws_s3_bucket.gizmos.id
policy = data.aws_iam_policy_document.gizmos.json
}
And then we can enable AWS CloudTrail:
# Enable AWS CloudTrial
resource "aws_cloudtrail" "gizmos" {
depends_on = [aws_s3_bucket_policy.gizmos]
name = "cloudynotes-gizmos-trial"
s3_bucket_name = aws_s3_bucket.gizmos.id
include_global_service_events = true
is_multi_region_trail = true
}
Note
- For a multi-region trail, this resource must be in the home region of the trail.
- Console login log needs capturing events from IAM, soinclude_global_service_events
must be enabled.
With the producer configuration complete, the next step involves creating the consumer, which is an SNS topic. Utilize the code blocks below to create an SNS topic for sending emails upon receiving an event:
# Create AWS SNS topic
resource "aws_sns_topic" "gizmos_sns_topic" {
name = "gizmos-aws-sign-in"
display_name = "Gizmos login"
}
# Add SNS policy
resource "aws_sns_topic_policy" "gizmos_topic_policy" {
arn = aws_sns_topic.gizmos_sns_topic.arn
policy = data.aws_iam_policy_document.gizmos_topic_policy.json
}
# data block of SNS policy document
data "aws_iam_policy_document" "gizmos_topic_policy" {
statement {
effect = "Allow"
actions = ["SNS:Publish"]
principals {
type = "Service"
identifiers = ["events.amazonaws.com"]
}
resources = [aws_sns_topic.gizmos_sns_topic.arn]
}
}
Now add an email topic subscription:
resource "aws_sns_topic_subscription" "gizmos_sqs_subscription" {
topic_arn = aws_sns_topic.gizmos_sns_topic.arn
protocol = "email"
endpoint = var.subscription_email
}
You can add
subscription_email
to the Terraform varaibles or simply replace your email address in the above block:
endpoint = "gizmos@cloudynotes.io"
The important and final step is to set up AWS EventBridge to capture events from AWS CloudTrail and send them to AWS SNS. Use the below code block to create event rule:
resource "aws_cloudwatch_event_rule" "gizmos_event_rule" {
name = "gizmos-aws-sign-in"
description = "Capture each AWS Console Sign In"
event_pattern = <<PATTERN
{
"source": ["aws.signin"],
"detail-type": ["AWS Console Sign In via CloudTrail"],
"detail": {
"eventSource": ["signin.amazonaws.com"],
"eventName": ["ConsoleLogin"]
}
}
PATTERN
}
As you can see the Terraform resource is CloudWatch event rule, EventBridge was formerly known as CloudWatch Events. The functionality is identical.
A helpful tip for finding the correct pattern for your event is to utilize the AWS EventBridge console. This tool assists in generating a sample event, allowing you to test your pattern before creating your rule. Navigate to your AWS Console, go to the EventBridge service, and select "Rule" from the left side to create a sample rule. On the next page, choose "AWS events" as sample events and then filter your sample event. In this example, you might filter for "AWS Console Sign In via CloudTrail":
Then, at the bottom of the page, you can select your method and define your event pattern.
We are approaching the final step of the configuration; adding a target to the event rule. Since we have created an SNS topic as the target, we need to include it in the event rule target. The event rule includes an excellent feature that allows for the transformation of input data, enabling the sending of custom event data to the target. This functionality proves valuable in eliminating unnecessary information from the event or customizing the event data. Read more about it here.
resource "aws_cloudwatch_event_target" "gizmos_event_target" {
rule = aws_cloudwatch_event_rule.gizmos_event_rule.name
target_id = "gizmosLoginSNS"
arn = aws_sns_topic.gizmos_sns_topic.arn
input_transformer {
input_paths = {
aws_account = "$.detail.awsRegion",
time = "$.time",
user = "$.detail.userIdentity.accountId"
}
input_template = <<EOF
"Warning! Successfully loggin to <aws_account> at <time> with user: <user>!"
EOF
}
}
Apply the Terraform resources and then you can test the setup by logout and loggin to your AWS account:
Next step
Utilize this winning combination to implement various integrations and effortlessly solve problems within your projects. For instance, I discovered the following article that demonstrates using the same solution to detect a stopped EC2 instance and initiate its restart using Terraform. Read more about this solution in this blog post: Automating EC2 Stop Protection