Provisioning AWS CloudTrail using Terraform (Step-by-Step)

Provisioning AWS CloudTrail using Terraform (Step-by-Step)

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.

Leave a Reply

Your email address will not be published. Required fields are marked *