Provisioning AWS CloudTrail using Terraform (Step-by-Step)
Esther Ninyo
Posted on March 31, 2024
CloudTrail is an AWS service that enables governance, compliance, operational and risk auditing of your AWS account. It captures all the events that happens within your AWS account and it is recorded as events in CloudTrail. The trail will be stored in an s3 bucket of your choice.
To read more on CloudTrail, please visit the documentation page.
In this technical post, we'll walk through the steps to provision CloudTrail using Terraform.
Resources to be created:
S3 bucket
KMS (Key Management System) for S3 objects encryption
CloudTrail
Prerequisite:
An AWS account with permissions to create CloudTrail resources
AWS cli configured
Terraform installed
Folder Structure
cloudtrail #this is a folder
---> providers.tf
---> s3.tf
---> main.tf
---> kms.tf
Step 1:
Create a new directory (cloudtrail) and navigate into it
mkdir cloudtrail
cd cloudtrail
providers.tf
terraform {
required_version = "~> 1.6"
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "eu-west-1"
default_tags {
tags = {
Environment = terraform.workspace,
ManagedBy = "Terraform"
}
}
}
Step 2:
Initialize your project
terraform init
A successful initialization should look like the image below:
Copy the code below to your main.tf file
main.tf
resource "aws_cloudtrail" "cloudtrail" {
name = "cloudtrail-tutorial"
s3_bucket_name = aws_s3_bucket.cloudtrail_s3.id
kms_key_id = aws_kms_key.cloudtrail_kms_key.arn
enable_log_file_validation = true
is_multi_region_trail = true
enable_logging = true
depends_on = [
aws_s3_bucket.cloudtrail_s3,
data.aws_iam_policy_document.cloudtrail_s3_policy,
aws_kms_key.cloudtrail_kms_key
]
}
Copy the code below to your s3.tf file
s3.tf
resource "aws_s3_bucket" "cloudtrail_s3" {
bucket = "cloudtrail-bucket"
force_destroy = true
}
resource "aws_s3_bucket_acl" "s3_bucket" {
bucket = aws_s3_bucket.cloudtrail_s3.id
acl = "private"
}
resource "aws_s3_bucket_public_access_block" "pub_access" {
bucket = aws_s3_bucket.cloudtrail_s3.id
block_public_acls = true
block_public_policy = true
ignore_public_acls = true
restrict_public_buckets = true
}
resource "aws_s3_bucket_policy" "cloudtrail_s3_policy" {
bucket = aws_s3_bucket.cloudtrail_s3.id
policy = data.aws_iam_policy_document.cloudtrail_s3_policy.json
}
data "aws_iam_policy_document" "cloudtrail_s3_policy" {
statement {
sid = "AWSCloudTrailAclCheck"
effect = "Allow"
resources = ["${aws_s3_bucket.cloudtrail_s3.arn}"]
actions = ["s3:GetBucketAcl"]
condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = ["arn:aws:cloudtrail:eu-west-1:${data.aws_caller_identity.current.account_id}:trail/cloudtrail-${terraform.workspace}"]
}
principals {
type = "Service"
identifiers = ["cloudtrail.amazonaws.com"]
}
}
statement {
sid = "AWSCloudTrailWrite"
effect = "Allow"
resources = ["${aws_s3_bucket.cloudtrail_s3.arn}/AWSLogs/${data.aws_caller_identity.current.account_id}/*"]
actions = ["s3:PutObject"]
condition {
test = "StringEquals"
variable = "s3:x-amz-acl"
values = ["bucket-owner-full-control"]
}
condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = ["arn:aws:cloudtrail:eu-west-1:${data.aws_caller_identity.current.account_id}:trail/cloudtrail-${terraform.workspace}"]
}
principals {
type = "Service"
identifiers = ["cloudtrail.amazonaws.com"]
}
}
}
Copy the code below to your kms.tf file
kms.tf
data "aws_caller_identity" "current" {}
resource "aws_kms_key" "cloudtrail_kms_key" {
description = "KMS key for cloudtrail"
enable_key_rotation = true
policy = data.aws_iam_policy_document.kms_policy.json
depends_on = [
data.aws_iam_policy_document.kms_policy
]
}
resource "aws_kms_alias" "kms_alias" {
name = "alias/cloudtrail_kms"
target_key_id = aws_kms_key.cloudtrail_kms_key.key_id
}
# KMS KEY POLICY
data "aws_iam_policy_document" "kms_policy" {
# Allow root users full management access to key
statement {
sid = "Enable IAM User Permissions"
effect = "Allow"
actions = ["kms:*"]
resources = ["*"]
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"]
}
}
statement {
sid = "Allow CloudTrail to encrypt logs"
effect = "Allow"
actions = ["kms:GenerateDataKey*"]
resources = ["*"]
principals {
type = "Service"
identifiers = ["cloudtrail.amazonaws.com"]
}
condition {
test = "StringEquals"
variable = "AWS:SourceArn"
values = ["arn:aws:cloudtrail:eu-west-1:${data.aws_caller_identity.current.account_id}:trail/cloudtrail-${terraform.workspace}"]
}
condition {
test = "StringLike"
variable = "kms:EncryptionContext:aws:cloudtrail:arn"
values = ["arn:aws:cloudtrail:*:${data.aws_caller_identity.current.account_id}:trail/*"]
}
}
statement {
sid = "Allow CloudTrail to describe key"
effect = "Allow"
actions = ["kms:DescribeKey"]
resources = ["*"]
principals {
type = "Service"
identifiers = ["cloudtrail.amazonaws.com"]
}
}
statement {
sid = "Allow principals in the account to decrypt log files"
effect = "Allow"
actions = [
"kms:Decrypt",
"kms:ReEncryptFrom"
]
resources = ["*"]
principals {
type = "AWS"
identifiers = ["*"]
}
condition {
test = "StringEquals"
variable = "kms:CallerAccount"
values = ["${data.aws_caller_identity.current.account_id}"]
}
condition {
test = "StringLike"
variable = "kms:EncryptionContext:aws:cloudtrail:arn"
values = ["arn:aws:cloudtrail:*:${data.aws_caller_identity.current.account_id}:trail/*"]
}
}
statement {
sid = "Allow alias creation during setup"
effect = "Allow"
actions = ["kms:CreateAlias"]
resources = ["*"]
principals {
type = "AWS"
identifiers = ["*"]
}
condition {
test = "StringEquals"
variable = "kms:CallerAccount"
values = ["${data.aws_caller_identity.current.account_id}"]
}
condition {
test = "StringEquals"
variable = "kms:ViaService"
values = ["ec2.eu-west-1.amazonaws.com"]
}
}
statement {
sid = "Enable cross account log decryption"
effect = "Allow"
actions = [
"kms:Decrypt",
"kms:ReEncryptFrom"
]
resources = ["*"]
principals {
type = "AWS"
identifiers = ["*"]
}
condition {
test = "StringEquals"
variable = "kms:CallerAccount"
values = ["${data.aws_caller_identity.current.account_id}"]
}
}
}
Step 3:
terraform plan
A successful plan should look like the image below:
Step 4:
terraform apply
A successful plan should look like the image below:
Conclusion
You have successfully created an AWS CloudTrail service.
Do you have any question? Please send it my way. Kindly follow me on LinkedIn.
Posted on March 31, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.