Security Monitoring System with Terraform
Deploy AWS GuardDuty threat detection and alerts using Terraform IaC.
Introduction
30 Second Summary
Imagine your house had a security system that could detect break-in attempts, suspicious activity, and unusual behaviour. Now imagine it could text you the moment something happened. That's what you're building for your cloud account.
In this project, you will build a security monitoring system on AWS that automatically detects threats and emails you alerts. You will define the entire system as code using Terraform, so you can deploy, update, and tear down your infrastructure with a single command.
What You'll Build
A complete security monitoring pipeline on AWS, defined entirely as Infrastructure as Code with Terraform.
By the end of this project, you'll have:
- A Terraform project with properly structured configuration files that define your entire security system as code.
- An AWS GuardDuty detector that monitors your account for unauthorized access attempts, compromised credentials, and other threats.
- An automated alert pipeline using EventBridge and SNS that emails you when high-severity threats are detected.
- Secret Mission: Add a CloudWatch dashboard that visualizes your GuardDuty findings over time.
Are there any prerequisites?
None. This project includes full setup instructions for Terraform, the AWS CLI, and VS Code. You will need an AWS account, and this guide walks you through creating one if you do not have one yet.
Install Terraform and Configure AWS
Before you can define infrastructure as code, you need the tools that make it possible. Terraform is the tool that translates your code into real cloud resources, and the AWS CLI is what connects your local machine to your AWS account.
In this step, you will install both tools, configure your AWS credentials, and set up VS Code for writing Terraform code.
In this step, get ready to:
- Install Terraform and verify it is working.
- Install and configure the AWS CLI with your credentials.
- Set up VS Code with the Terraform extension.
Install Terraform
Terraform is an Infrastructure as Code tool that lets you define cloud resources in plain text files. You will use it throughout this project to create and manage your security monitoring system.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Terminal and press Enter to open it.
- Add the HashiCorp tap to Homebrew by running this command:
brew tap hashicorp/tap
- Install Terraform by running:
brew install hashicorp/tap/terraform
What do these commands do?
Homebrew is a package manager for macOS that installs software from the command line. The brew tap command adds HashiCorp's official repository so Homebrew knows where to find Terraform.
The brew install command then downloads and installs the latest version of Terraform from that repository.
- Verify the installation by running:
terraform version
Check the output against the tabs below to confirm everything is working.
✔️ I see version 1.15 or higher
You should see output starting with Terraform v1.15 followed by your platform. Terraform is installed and ready to go.
ⓧ I see an older version
If you see a version number lower than 1.15, you have an older installation of Terraform on your system. Upgrade it by running:
brew upgrade hashicorp/tap/terraform
After the upgrade completes, run terraform version again to confirm you now see v1.15 or higher.
ⓧ Command not found
If your terminal says the command was not found, Homebrew may not be installed on your system. Install Homebrew first by running:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
After Homebrew installs, close and reopen your terminal, then run the two Terraform install commands again:
brew tap hashicorp/tap
brew install hashicorp/tap/terraform
Run terraform version to confirm it shows v1.15 or higher.
Install and configure the AWS CLI
The AWS CLI is the command-line tool that lets Terraform communicate with your AWS account. Without it, Terraform cannot create any cloud resources.
- Install the AWS CLI by running:
brew install awscli
What if I do not have an AWS account yet?
If you do not have an AWS account, create one at https://aws.amazon.com. You will need a credit card on file, but this project stays within the free tier.
After creating your account, you need an IAM user with programmatic access to generate the Access Key ID and Secret Access Key that the next command requires. Search for IAM in the AWS Console, click Users, then Create user. Attach the AdministratorAccess policy for this learning project.
Under Security credentials, click Create access key and select Command Line Interface (CLI) as the use case. Copy both the Access Key ID and Secret Access Key for the next step.
- Configure your AWS credentials by running:
aws configure
What will this command ask me?
The aws configure command will prompt you for four values, one at a time. Enter the following:
- AWS Access Key ID: paste your Access Key ID from IAM.
- AWS Secret Access Key: paste your Secret Access Key from IAM.
- Default region name: enter us-east-1.
- Default output format: enter json.
- Verify your credentials are working by running:
aws sts get-caller-identity
You should see a JSON response containing your Account ID, User ARN, and User ID. This confirms Terraform will be able to reach your AWS account.
Seeing an error about credentials?
If you see a message about invalid credentials or expired tokens, run aws configure again and double-check that you pasted the full Access Key ID and Secret Access Key without extra spaces.
Make sure you copied from the IAM console's access key creation page, not from a different section.
help me fix my AWS CLI credentials configuration
Set up VS Code with the Terraform extension
VS Code will be your code editor for writing Terraform files. The Terraform extension adds syntax highlighting and autocomplete that will help you write correct configurations.
- If you do not have VS Code installed, download it from https://code.visualstudio.com and follow the installer.
- Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
- Type Visual Studio Code and press Enter to open it.
- Open the Extensions panel by pressing Cmd+Shift+X (macOS) or Ctrl+Shift+X (Windows).
- Type HashiCorp Terraform in the search bar at the top of the panel.
- Find the extension published by HashiCorp and click Install.
You should see the extension listed in your installed extensions. Files ending in .tf will now get syntax highlighting and autocomplete.
Extension not appearing in search?
Make sure you are searching in the Extensions panel (the square icon in the left sidebar), not the file search. The extension is published by HashiCorp with the ID HashiCorp.terraform.
help me find and install the HashiCorp Terraform extension in VS Code
Your tools are installed and your AWS connection is live. Next up, you will create the Terraform project structure and write your first configuration files.
Set Up the Terraform Project Structure
Every Terraform project follows a predictable file structure. Separating your configuration into dedicated files keeps things organized as your infrastructure grows.
In this step, you will create a project directory with three standard Terraform files and configure the AWS provider so Terraform knows which cloud platform to talk to.
In this step, get ready to:
- Create a Terraform project directory with three configuration files.
- Define project variables for region, naming, and email alerts.
- Configure the AWS provider and initialize the project.
Create the project directory and files
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir security-monitor
cd security-monitor
- Create the three Terraform configuration files by running:
touch main.tf variables.tf outputs.tf
- Confirm the files exist by running:
ls
You should see main.tf, outputs.tf, and variables.tf in the output.
Don't see the files?
Make sure you ran cd security-monitor before running touch. If you skipped that step, the files were created in the wrong directory.
Help me find my missing Terraform files
- In VS Code, click File in the top menu bar.
- Click Open Folder.
- Select the security-monitor folder on your Desktop, then click Open.
You should see the three files listed in the Explorer sidebar on the left.
What are these three files for?
Terraform projects split configuration across files by purpose. main.tf holds your resource definitions. variables.tf declares input values that make your code reusable. outputs.tf defines values Terraform prints after deploying resources.
- Click on variables.tf in the Explorer sidebar to open it.
- Add the following code to define your project variables:
variable "aws_region" {
description = "AWS region to deploy resources"
type = string
default = "us-east-1"
}
variable "project_name" {
description = "Name prefix for all resources"
type = string
default = "security-monitor"
}
variable "alert_email" {
description = "Email address to receive security alerts"
type = string
}
What does this code do?
Each variable block declares an input that your other Terraform files can reference.
- aws_region sets which AWS region your resources deploy to. It defaults to us-east-1 so you do not have to specify it every time.
- project_name provides a naming prefix for resources, making them easy to identify in your AWS account.
- alert_email has no default because Terraform will prompt you for your email when you deploy. This is where security alerts get sent.
- Save variables.tf by pressing Cmd+S (macOS) or Ctrl+S (Windows).
Seeing red squiggles in the editor?
If the HashiCorp Terraform extension shows syntax errors, check that every opening brace { has a matching closing brace }. Also confirm that string values are wrapped in double quotes, not single quotes.
Help me fix a syntax error in my variables.tf file
✔️ Awesome, I've got everything!
Great. Make sure you have saved variables.tf before moving on.
ⓧ I'd like to double check the full code
variable "aws_region" {
description = "AWS region to deploy resources"
type = string
default = "us-east-1"
}
variable "project_name" {
description = "Name prefix for all resources"
type = string
default = "security-monitor"
}
variable "alert_email" {
description = "Email address to receive security alerts"
type = string
}
Configure the AWS provider
Terraform uses providers to interact with cloud platforms. The AWS provider translates your Terraform code into actual AWS API calls.
- Click on main.tf in the Explorer sidebar to open it.
- Add the following code to configure the AWS provider:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = var.aws_region
}
What does this code do?
- The terraform block tells Terraform which providers it needs to download. Here you are specifying the AWS provider from HashiCorp's registry.
- version = "~> 6.0" means any version in the 6.x range is acceptable. This keeps your project compatible with future patch releases.
- The provider "aws" block configures how Terraform connects to AWS. It pulls the region from your aws_region variable so you can change it in one place.
- Save main.tf by pressing Cmd+S (macOS) or Ctrl+S (Windows).
Seeing errors in main.tf?
Make sure the source value is exactly hashicorp/aws with a forward slash. Also confirm you have matching braces for every block level. The nesting goes three levels deep here.
Help me fix my main.tf provider configuration
✔️ Awesome, I've got everything!
Great. Make sure you have saved main.tf before moving on.
ⓧ I'd like to double check the full code
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = var.aws_region
}
- Initialize the Terraform project by running this command in your terminal:
terraform init
What does terraform init do?
This command reads your required_providers block, downloads the AWS provider plugin from HashiCorp's registry, and creates a .terraform.lock.hcl lock file that pins the exact provider version. You run it once when starting a project or whenever you add a new provider.
You should see the message Terraform has been successfully initialized! in your terminal output. This confirms the AWS provider was downloaded and your project is ready.
Seeing an error during init?
If you see a network or registry error, check your internet connection. Terraform needs to reach registry.terraform.io to download providers.
If the error mentions a version constraint, double check that your main.tf has version = "~> 6.0" with the tilde and angle bracket.
Help me fix a terraform init error
Your Terraform project is structured and initialized. Next up, you will deploy your first real AWS resource: a GuardDuty threat detector.
Deploy the GuardDuty Detector
Your Terraform project is initialized and ready to provision real cloud resources. Now it is time to define the first piece of your security infrastructure.
In this step, you will add an AWS GuardDuty detector to your Terraform configuration and deploy it to your AWS account. GuardDuty is a threat detection service that continuously monitors your account for malicious activity by analyzing CloudTrail events, VPC Flow Logs, and DNS query logs.
In this step, get ready to:
- Add the GuardDuty detector resource to your Terraform configuration.
- Preview the infrastructure changes with terraform plan.
- Deploy the detector to AWS with terraform apply.
Add the GuardDuty detector resource
GuardDuty starts analyzing your account's log data immediately after you enable it. You define this as a Terraform resource so you can version-control your security infrastructure and reproduce it reliably.
- In VS Code, open main.tf inside your security-monitor folder.
- Find the closing brace of the provider "aws" block at the bottom of the file.
- Add the following GuardDuty resource below the provider block:
# GuardDuty Detector
resource "aws_guardduty_detector" "main" {
enable = true
finding_publishing_frequency = "FIFTEEN_MINUTES"
datasources {
s3_logs {
enable = true
}
}
tags = {
Name = var.project_name
Environment = "dev"
ManagedBy = "terraform"
}
}
What does this code do?
This resource block tells Terraform to create a GuardDuty detector in your AWS account.
- enable = true activates the detector so it starts monitoring immediately.
- finding_publishing_frequency = "FIFTEEN_MINUTES" tells GuardDuty to publish findings every 15 minutes for faster alerting.
- The datasources block enables S3 log analysis, so GuardDuty can detect threats in your S3 bucket activity.
- The tags block labels the resource for easy identification in your AWS Console.
- Save main.tf.
Seeing a syntax error in VS Code?
- Make sure there is a blank line between the provider "aws" block and the new resource block.
- Check that every opening brace { has a matching closing brace }. The resource block has three levels of nesting.
- help me fix a syntax error in my Terraform GuardDuty resource
Preview and deploy with terraform plan and apply
Before Terraform makes any changes to your AWS account, it shows you a preview of what it plans to create. This preview step is a safety net that lets you catch mistakes before they reach production.
- Preview the resources Terraform will create by running this command:
terraform plan
What does terraform plan show you?
Terraform will prompt you to enter a value for alert_email since it has no default. Type any email address for now. You will use this email for real alerts in the next step.
The plan output shows a + symbol next to each resource it will create. You should see one resource: aws_guardduty_detector.main. The plan also lists the attributes Terraform will set, like enable = true and finding_publishing_frequency = "FIFTEEN_MINUTES".
- Deploy the GuardDuty detector to your AWS account by running:
terraform apply
What happens during terraform apply?
Terraform will prompt you for the alert_email variable again. Enter the same email address you used before.
After showing the plan, Terraform asks you to confirm by typing yes. Type yes and press Enter to proceed. Terraform then makes the API calls to AWS to create the GuardDuty detector.
- When prompted, enter your email address for the alert_email variable.
- Type yes when Terraform asks you to confirm.
You should see a message that says Apply complete! Resources: 1 added, 0 changed, 0 destroyed. in your terminal.
- Open the AWS Console in your browser at https://console.aws.amazon.com/guardduty/.
- Make sure the region selector in the top-right corner shows US East (N. Virginia) (us-east-1).
- You should see the GuardDuty Summary dashboard, confirming the detector is active.
Don't see GuardDuty enabled?
- Check that you are viewing the correct region (us-east-1) in the AWS Console. GuardDuty is region-specific.
- Confirm your terminal showed the "Apply complete" message. If it shows an error about credentials, run aws configure again to verify your access keys.
- If GuardDuty was already enabled in your account before this project, Terraform may show an error about an existing detector. You will need to import the existing detector or disable it first.
- help me troubleshoot why my GuardDuty detector did not deploy
✔️ Awesome, I've got everything!
Great. Double check you have saved main.tf and that GuardDuty shows as active in your console.
ⓧ I'd like to double check the full code
Here is the full main.tf file at this point in the project:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# GuardDuty Detector
resource "aws_guardduty_detector" "main" {
enable = true
finding_publishing_frequency = "FIFTEEN_MINUTES"
datasources {
s3_logs {
enable = true
}
}
tags = {
Name = var.project_name
Environment = "dev"
ManagedBy = "terraform"
}
}
Your GuardDuty detector is now live and monitoring your AWS account for threats. Next up, you will build the notification pipeline so that when GuardDuty finds something suspicious, you get an email alert automatically.
Create the SNS Notification Pipeline
GuardDuty is now running and scanning your AWS account for threats. The next challenge is making sure you actually hear about it when something suspicious happens.
In this step, you will build a notification pipeline using Amazon SNS (Simple Notification Service). SNS is a messaging service that can send emails, texts, or push notifications when triggered by other AWS services.
In this step, get ready to:
- Add an SNS topic and security policy to your Terraform configuration.
- Add an email subscription that delivers alerts to your inbox.
- Deploy the resources and confirm your email subscription.
Add the SNS resources to your configuration
Your notification pipeline needs three pieces: a topic (the channel alerts flow through), a policy (who is allowed to publish to that channel), and a subscription (where the alerts get delivered).
- In VS Code, open main.tf inside your security-monitor folder.
- Add the SNS topic and its access policy below your GuardDuty resource by pasting the following code at the end of the file:
# SNS Topic for GuardDuty Findings
resource "aws_sns_topic" "guardduty_findings" {
name = "${var.project_name}-guardduty-findings"
tags = {
Name = "${var.project_name}-guardduty-findings"
ManagedBy = "terraform"
}
}
# SNS Topic Policy to allow EventBridge to publish
resource "aws_sns_topic_policy" "guardduty_findings" {
arn = aws_sns_topic.guardduty_findings.arn
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowEventBridgePublish"
Effect = "Allow"
Principal = { Service = "events.amazonaws.com" }
Action = "sns:Publish"
Resource = aws_sns_topic.guardduty_findings.arn
}
]
})
}
What does this code do?
The aws_sns_topic creates a named messaging channel called security-monitor-guardduty-findings. Think of it as a mailbox that collects alert messages.
The aws_sns_topic_policy grants EventBridge (events.amazonaws.com) permission to drop messages into that mailbox. Without this policy, EventBridge would be blocked from publishing alerts to your topic.
- You won't see a change yet. The next chunk adds the email subscription that actually delivers messages from this topic.
- Now add the email subscription below the topic policy you just pasted:
# SNS Email Subscription
resource "aws_sns_topic_subscription" "email" {
topic_arn = aws_sns_topic.guardduty_findings.arn
protocol = "email"
endpoint = var.alert_email
}
What does this code do?
This resource subscribes your email address to the SNS topic. The protocol is set to email, and the endpoint uses the alert_email variable you defined in variables.tf earlier.
Terraform will ask you to enter your email when you run terraform apply because alert_email has no default value.
- Save main.tf.
Seeing red squiggly lines in VS Code?
Make sure you pasted the SNS resources outside and below the closing } of the aws_guardduty_detector resource. Each resource block must be at the top level of the file, not nested inside another block.
help me fix syntax errors in my main.tf file
✔️ Awesome, I've got everything!
Great. Double check you have saved main.tf before moving on.
ⓧ I'd like to double check the full code
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# GuardDuty Detector
resource "aws_guardduty_detector" "main" {
enable = true
finding_publishing_frequency = "FIFTEEN_MINUTES"
datasources {
s3_logs {
enable = true
}
}
tags = {
Name = var.project_name
Environment = "dev"
ManagedBy = "terraform"
}
}
# SNS Topic for GuardDuty Findings
resource "aws_sns_topic" "guardduty_findings" {
name = "${var.project_name}-guardduty-findings"
tags = {
Name = "${var.project_name}-guardduty-findings"
ManagedBy = "terraform"
}
}
# SNS Topic Policy to allow EventBridge to publish
resource "aws_sns_topic_policy" "guardduty_findings" {
arn = aws_sns_topic.guardduty_findings.arn
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowEventBridgePublish"
Effect = "Allow"
Principal = { Service = "events.amazonaws.com" }
Action = "sns:Publish"
Resource = aws_sns_topic.guardduty_findings.arn
}
]
})
}
# SNS Email Subscription
resource "aws_sns_topic_subscription" "email" {
topic_arn = aws_sns_topic.guardduty_findings.arn
protocol = "email"
endpoint = var.alert_email
}
Deploy and confirm your email subscription
Your Terraform code defines the resources, but they do not exist in AWS until you apply the configuration. After applying, AWS will send a confirmation email that you must click before alerts can be delivered.
- Deploy your SNS resources by running this command:
terraform apply
What will Terraform ask you?
Terraform will prompt you to enter a value for alert_email because this variable has no default. Type the email address where you want to receive security alerts, then press Enter.
After showing the plan, Terraform asks you to confirm the changes. Type yes and press Enter to create the resources.
- Enter your email address when prompted.
- Review the plan output. You should see 3 new resources to add (the SNS topic, topic policy, and email subscription).
- Type yes to confirm and press Enter.
- Wait for Terraform to finish. You should see a message saying 3 resources were added.
Why does Terraform ask for confirmation?
The plan/apply workflow is a safety net. Terraform shows you exactly what it will create, modify, or destroy before it touches your infrastructure. This prevents accidental changes to production systems.
- Check the email inbox for the address you entered.
- Open the email from AWS Notifications with the subject line about subscription confirmation.
- Click the Confirm subscription link inside the email.
Don't see the confirmation email?
- Check your spam or junk folder. AWS subscription emails often land there.
- Make sure you typed the correct email address when Terraform prompted you. If it was wrong, run terraform destroy and then terraform apply again with the correct email.
- Wait a few minutes. The email can take up to 5 minutes to arrive.
help me troubleshoot my SNS email subscription
Why do I need to confirm?
AWS requires email confirmation to prevent someone from subscribing your address to unwanted notifications without your permission. Until you click the link, the subscription stays in a pending state and no alerts will be delivered.
Your notification channel is live and confirmed. Next up, you will wire up EventBridge to route high-severity GuardDuty findings into this SNS topic so alerts actually trigger.
Wire Up EventBridge for Severity-Based Alerting
Your SNS topic is ready and your email subscription is confirmed. Now you need something to actually send findings to that topic.
EventBridge is AWS's event routing service. It listens for events from other AWS services and forwards them to targets you define. In this step, you will create an EventBridge rule that captures high-severity GuardDuty findings and routes them to your SNS topic as formatted email alerts.
In this step, get ready to:
- Create an EventBridge rule that filters for high-severity GuardDuty findings.
- Connect the rule to your SNS topic with a human-readable message template.
- Add Terraform outputs to surface your resource identifiers.
Create the EventBridge rule
GuardDuty automatically publishes findings as events to EventBridge. You need a rule that listens for those events and filters them by severity.
Why filter for severity >= 7?
GuardDuty classifies findings into three severity bands: Low (1-3.9), Medium (4-6.9), and High (7-8.9). Filtering for severity 7 and above means you only get notified about genuinely dangerous threats. This prevents alert fatigue during your learning phase while still demonstrating the full pattern.
- In main.tf, scroll to the bottom of the file (below the aws_sns_topic_subscription resource) and add the EventBridge rule by pasting this code:
# EventBridge Rule for High-Severity GuardDuty Findings
resource "aws_cloudwatch_event_rule" "guardduty_high_severity" {
name = "${var.project_name}-guardduty-high-severity"
description = "Capture high severity GuardDuty findings (severity >= 7)"
event_pattern = jsonencode({
source = ["aws.guardduty"]
detail-type = ["GuardDuty Finding"]
detail = {
severity = [{ numeric = [">=", 7] }]
}
})
tags = {
Name = "${var.project_name}-guardduty-high-severity"
ManagedBy = "terraform"
}
}
What does this code do?
- event_pattern defines which events this rule captures. It matches events from aws.guardduty with a detail-type of GuardDuty Finding.
- The numeric filter checks that detail.severity is greater than or equal to 7. Only high-severity findings pass through.
- jsonencode() converts the HCL map into the JSON string that EventBridge expects.
Why is the resource called aws_cloudwatch_event_rule?
EventBridge is the successor to CloudWatch Events. AWS now recommends EventBridge for all event routing. However, both services share the same underlying API, so Terraform still uses the aws_cloudwatch_event_rule and aws_cloudwatch_event_target resource names.
You are creating an EventBridge rule. The Terraform resource name is just a legacy naming artifact.
- Save main.tf.
- You won't see a change yet. The next substep adds the target, then you will deploy both together.
Add the EventBridge target with a formatted message
A rule without a target does nothing. You need to tell EventBridge where to send matching events and how to format them.
- Still in main.tf, add the EventBridge target directly below the rule you just added:
# EventBridge Target to SNS with formatted message
resource "aws_cloudwatch_event_target" "sns" {
rule = aws_cloudwatch_event_rule.guardduty_high_severity.name
target_id = "SendToSNS"
arn = aws_sns_topic.guardduty_findings.arn
input_transformer {
input_paths = {
severity = "$.detail.severity"
type = "$.detail.type"
description = "$.detail.description"
account = "$.detail.accountId"
region = "$.detail.region"
}
input_template = "\"SECURITY ALERT: Severity <severity> finding detected. Type: <type>. Account: <account>. Region: <region>. Description: <description>\""
}
}
What does this code do?
- rule connects this target to the EventBridge rule you created above.
- arn points to your SNS topic as the destination for matched events.
- input_paths extracts specific fields from the raw GuardDuty event JSON using JSONPath syntax.
- input_template formats the extracted values into a human-readable alert message. Without this, you would receive the entire raw JSON event in your email.
- Save main.tf.
- You won't see a change yet. The next substep deploys both resources with terraform apply.
Deploy the pipeline and add outputs
Your EventBridge rule and target are defined in code. Time to deploy them to AWS and then add Terraform outputs so you can easily reference your resource identifiers.
- Deploy the EventBridge resources by running:
terraform apply
- Terraform will show a plan to add 2 new resources. Type yes and press Enter to confirm.
- You should see a message ending with Apply complete! Resources: 2 added, 0 changed, 0 destroyed. in your terminal.
Seeing an error about the event pattern?
- Make sure the event_pattern is wrapped in jsonencode(). Terraform needs this to convert HCL to valid JSON.
- Check that detail-type uses a hyphen, not an underscore. This is the exact field name EventBridge expects.
- help me debug my EventBridge event pattern in Terraform
Now add Terraform outputs so you can quickly reference your deployed resource identifiers.
- Open outputs.tf and add the following three outputs:
output "guardduty_detector_id" {
description = "The ID of the GuardDuty detector"
value = aws_guardduty_detector.main.id
}
output "sns_topic_arn" {
description = "The ARN of the SNS topic for GuardDuty findings"
value = aws_sns_topic.guardduty_findings.arn
}
output "eventbridge_rule_arn" {
description = "The ARN of the EventBridge rule"
value = aws_cloudwatch_event_rule.guardduty_high_severity.arn
}
What do Terraform outputs do?
Outputs expose key values after a successful apply. They make it easy to reference resource identifiers without digging through the AWS Console. Other Terraform configurations can also consume these outputs as inputs.
- Save outputs.tf.
- Run terraform apply one more time to register the outputs:
terraform apply
- Type yes when prompted.
- After the apply completes, you should see your three outputs printed in the terminal: guardduty_detector_id, sns_topic_arn, and eventbridge_rule_arn.
Outputs not showing?
- Make sure you saved outputs.tf before running the command.
- Check that the resource names in your value fields match exactly: aws_guardduty_detector.main, aws_sns_topic.guardduty_findings, and aws_cloudwatch_event_rule.guardduty_high_severity.
- help me fix my Terraform outputs that aren't displaying after apply
✔️ Awesome, I've got everything!
Great. Double check you have saved both main.tf and outputs.tf.
ⓧ I'd like to double check the full code
Here is the complete main.tf at this point:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
provider "aws" {
region = var.aws_region
}
# GuardDuty Detector
resource "aws_guardduty_detector" "main" {
enable = true
finding_publishing_frequency = "FIFTEEN_MINUTES"
datasources {
s3_logs {
enable = true
}
}
tags = {
Name = var.project_name
Environment = "dev"
ManagedBy = "terraform"
}
}
# SNS Topic for GuardDuty Findings
resource "aws_sns_topic" "guardduty_findings" {
name = "${var.project_name}-guardduty-findings"
tags = {
Name = "${var.project_name}-guardduty-findings"
ManagedBy = "terraform"
}
}
# SNS Topic Policy to allow EventBridge to publish
resource "aws_sns_topic_policy" "guardduty_findings" {
arn = aws_sns_topic.guardduty_findings.arn
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "AllowEventBridgePublish"
Effect = "Allow"
Principal = { Service = "events.amazonaws.com" }
Action = "sns:Publish"
Resource = aws_sns_topic.guardduty_findings.arn
}
]
})
}
# SNS Email Subscription
resource "aws_sns_topic_subscription" "email" {
topic_arn = aws_sns_topic.guardduty_findings.arn
protocol = "email"
endpoint = var.alert_email
}
# EventBridge Rule for High-Severity GuardDuty Findings
resource "aws_cloudwatch_event_rule" "guardduty_high_severity" {
name = "${var.project_name}-guardduty-high-severity"
description = "Capture high severity GuardDuty findings (severity >= 7)"
event_pattern = jsonencode({
source = ["aws.guardduty"]
detail-type = ["GuardDuty Finding"]
detail = {
severity = [{ numeric = [">=", 7] }]
}
})
tags = {
Name = "${var.project_name}-guardduty-high-severity"
ManagedBy = "terraform"
}
}
# EventBridge Target to SNS with formatted message
resource "aws_cloudwatch_event_target" "sns" {
rule = aws_cloudwatch_event_rule.guardduty_high_severity.name
target_id = "SendToSNS"
arn = aws_sns_topic.guardduty_findings.arn
input_transformer {
input_paths = {
severity = "$.detail.severity"
type = "$.detail.type"
description = "$.detail.description"
account = "$.detail.accountId"
region = "$.detail.region"
}
input_template = "\"SECURITY ALERT: Severity <severity> finding detected. Type: <type>. Account: <account>. Region: <region>. Description: <description>\""
}
}
And here is the complete outputs.tf:
output "guardduty_detector_id" {
description = "The ID of the GuardDuty detector"
value = aws_guardduty_detector.main.id
}
output "sns_topic_arn" {
description = "The ARN of the SNS topic for GuardDuty findings"
value = aws_sns_topic.guardduty_findings.arn
}
output "eventbridge_rule_arn" {
description = "The ARN of the EventBridge rule"
value = aws_cloudwatch_event_rule.guardduty_high_severity.arn
}
Your security monitoring pipeline is fully wired. GuardDuty detects threats, EventBridge filters for the dangerous ones, and SNS emails you the details. All of it is defined as code you can version, review, and redeploy anywhere.
Secret mission
Add a CloudWatch Dashboard
You've built a security monitoring pipeline that detects threats and sends alerts. But what if you want to visualize your security posture at a glance? In this secret mission, you'll add a CloudWatch dashboard to your Terraform configuration that charts your GuardDuty findings over time.
Clean Up Your Resources
Clean Up Your Resources
This project created AWS resources that are free during the 30-day GuardDuty trial and within SNS/EventBridge free tier limits. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.
Cost warning
GuardDuty is free for the first 30 days per AWS account per Region. After the trial ends, you will be charged based on log volume analyzed.
If you do not plan to continue using GuardDuty, run the teardown before your 30-day trial expires. Set a calendar reminder so you do not forget.
Resources you used:
- GuardDuty Detector (free for 30 days, then pay-per-use based on log volume).
- SNS Topic and Subscription (free tier covers 1,000 email notifications per month).
- EventBridge Rule (free for AWS service events).
- CloudWatch Dashboard (free tier covers 3 dashboards with up to 50 metrics each).
- Local project files (the security-monitor folder on your Desktop).
- Terraform and AWS CLI (installed via Homebrew).
Keep everything running
No action needed. Choose this if you are still actively using GuardDuty to monitor your AWS account during the 30-day free trial.
- GuardDuty will continue monitoring CloudTrail events, VPC Flow Logs, and DNS query logs.
- You will receive email alerts if any high-severity findings are detected.
- Set a calendar reminder 30 days from today to review whether you want to keep GuardDuty enabled or destroy your resources.
- Terraform, AWS CLI, and your local project files will remain on your machine.
Pause - I'll come back to this later
Destroy the AWS resources to stop any potential charges, but keep your local Terraform files so you can redeploy later with a single command.
- Open your terminal and navigate to your security-monitor folder.
- Run the following command to destroy all AWS resources:
terraform destroy
- Type yes when Terraform asks you to confirm.
- Your local files (main.tf, variables.tf, outputs.tf) remain on your machine.
- When you are ready to redeploy, run terraform apply from the same folder.
Delete - I don't want to use this again
Remove all AWS resources and delete your local project files. This is a complete teardown.
Destroy all AWS resources first:
- Open your terminal and navigate to your security-monitor folder.
- Run this command to destroy all AWS resources:
terraform destroy
- Type yes when Terraform asks you to confirm.
- Wait for Terraform to finish removing your GuardDuty detector, SNS topic, EventBridge rule, and any CloudWatch dashboard.
Delete local project files:
- Remove the project folder by running this command (macOS/Linux):
rm -rf ~/Desktop/security-monitor
- On Windows, run this instead:
rmdir /s /q %USERPROFILE%\Desktop\security-monitor
Uninstall Terraform and AWS CLI (optional):
- If you do not plan to use Terraform or the AWS CLI for other projects, uninstall them by running:
brew uninstall hashicorp/tap/terraform
brew uninstall awscli
- If you want to keep them for future projects, skip this step.
That's a Wrap!
That's a Wrap!
Nice work. You just built a security monitoring system entirely as code. That is real DevSecOps.
You've learned how to:
- Deployed AWS GuardDuty to continuously monitor for threats across CloudTrail, VPC Flow Logs, and DNS.
- Created an automated alert pipeline with EventBridge and SNS that emails you when high-severity findings are detected.
- Mastered the core Terraform lifecycle: init, plan, apply, and destroy.
- Secret Mission: Built a CloudWatch dashboard to visualize security findings over time.
Ready to quiz yourself?