Provisioning AWS CloudTrail using Terraform (Step-by-Step)
Tags: aws, cloudtrail, terraform, beginners
Published: 2024-03-31 05:09:00

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](https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html).

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:

![terraform init](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8un6z5ernysl9c7xxeou.png)

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:

![terraform plan](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qysw45mfysquvdjx2amt.png)

##### Step 4: 

```
terraform apply
```
A successful plan should look like the image below:

![terraform apply](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k5a5z6tbmwaztghwq88o.png)

##### Conclusion
You have successfully created an AWS CloudTrail service.
Do you have any question? Please send it my way. Kindly follow me on [LinkedIn](https://www.linkedin.com/in/esther-ninyo).