Encrypt CloudTrail logs with multi-region Key with Terraform
Tags: aws, cloudtrail, encrypted, terraform
Published: 2022-03-23 02:20:03


**Who did what, where and when?** in my AWS account(s) through the AWS Management Console, AWS Command Line Interface, and AWS SDKs and APIs is what AWS CloudTrail answerable to you as account owner. It enables auditing, security monitoring, operational troubleshooting, records user activity and API usage across AWS services as Events. These events can be viewed from the `Event History` page in the AWS CloudTrail console and are available for up to 90 days after they occur. 

CloudTrail records three types of events: 
1. ***Management events*** capturing control plane actions on resources such as creating or deleting Amazon Simple Storage Service (Amazon S3) buckets.
2. ***Data events*** capturing data plane actions within a resource, such as reading or writing an Amazon S3 object.
3. ***Insight events*** shows unusual API activities in your AWS account compared to historical API usage.

By default, CloudTrail event log files are encrypted using Amazon S3 server-side encryption (SSE). You can also choose to encrypt your log files with an AWS KMS key. In the previous [blog](https://dev.to/aws-builders/creating-a-multi-region-key-using-terraform-51o4) we saw how to build a multi-region key using terraform. We will make use of the same MRK to encrypt the CloudTrail log files and store it in an S3 bucket here.

Resource: `aws_cloudtrail` is used to create a trail for your account/organization. 
```
resource "aws_cloudtrail" "default" {
  name                          = var.trailName
  s3_bucket_name                = var.trailBucket
  is_organization_trail         = true
  is_multi_region_trail         = true
  include_global_service_events = true
  kms_key_id                    = aws_kms_key.primary.arn
  depends_on = [
    aws_s3_bucket.cloudtrailbucket,
    aws_kms_key.primary
  ]
}
```
As you can see we are enabling the trail for multi-region and at organization level, which you can do it for a single-region and single account too.

For the CloudTrail to write the log-files it needs an S3 bucket and for the same reason we have `depends_on` added into the resource block to create the S3 bucket first.

Resource: `aws_s3_bucket` is used to create an S3 bucket using terraform.
```
resource "aws_s3_bucket" "cloudtrailbucket" {
  bucket = var.trailBucket
  depends_on = [
    aws_kms_key.primary
  ]
  force_destroy = true
  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        kms_master_key_id = aws_kms_key.primary.id
        sse_algorithm     = "aws:kms"
      }
      bucket_key_enabled = "false"
    }
  }
  object_lock_configuration {
    object_lock_enabled = "Enabled"
  }

  policy = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "AWSCloudTrailAclCheck",
            "Effect": "Allow",
            "Principal": {
              "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:GetBucketAcl",
            "Resource": [
                "arn:aws:s3:::${var.trailBucket}"
            ]
        },
        {
            "Sid": "AWSCloudTrailWriteAccount",
            "Effect": "Allow",
            "Principal": {
              "Service": "cloudtrail.amazonaws.com"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::${var.trailBucket}/AWSLogs/${data.aws_caller_identity.current.account_id}/*",
            "Condition": {
                "StringEquals": {
                    "s3:x-amz-acl": "bucket-owner-full-control",
                    "AWS:SourceArn" : "arn:aws:cloudtrail:ap-southeast-1:${data.aws_caller_identity.current.account_id}:trail/${var.trailName}"
                }
            }
        },
        {
            "Sid" : "AWSCloudTrailWriteOrganization",
            "Effect" : "Allow",
            "Principal" : {
                "Service" : "cloudtrail.amazonaws.com"
            },
            "Action" : "s3:PutObject",
            "Resource" : "arn:aws:s3:::${var.trailBucket}/AWSLogs/${data.aws_organizations_organization.myorg.id}/*",
            "Condition" : {
                "StringEquals" : {
                    "s3:x-amz-acl" : "bucket-owner-full-control",
                    "AWS:SourceArn" : "arn:aws:cloudtrail:ap-southeast-1:${data.aws_caller_identity.current.account_id}:trail/${var.trailName}"
                }
            }
        }
    ]
}
POLICY
}
```

If you just add the first statement in the bucket policy and configure your cloudtrail to write to this bucket you can see how CloudTrail logs would be beneficial in getting some valuable information. The below screenshot shows how the CloudTrail service reported `InsufficientS3BucketPolicyException` error while trying to create trail.

![InsufficientBucketPolicyException](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w6jin5zbuju1fywin1wc.png)

Also the CloudTrail would need explicit permission to use the KMS key to encrypt logs on behalf of specific accounts. If KMS key policy is not correctly configured for CloudTrail, CloudTrail cannot deliver logs. The IAM global condition key `aws:SourceArn` helps ensure that CloudTrail uses the KMS key only for this specific organizational trail what we are configuring. We would have to update the KMS policy for the key that we previously created with the below statement:
```
  statement {
    sid       = "Allow CloudTrail to encrypt logs"
    effect    = "Allow"
    actions   = ["kms:GenerateDataKey*"]
    resources = ["*"]
    principals {
      type        = "Service"
      identifiers = ["cloudtrail.amazonaws.com"]
    }
    condition {
      test     = "StringLike"
      variable = "kms:EncryptionContext:aws:cloudtrail:arn"
      values   = ["arn:aws:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/${var.trailName}"]
    }
    condition {
      test     = "StringEquals"
      variable = "aws:SourceArn"
      values   = ["arn:aws:cloudtrail:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:trail/${var.trailName}"]
    }
  }
```

If you run the `terraform apply` command with all the above configuration you will have the Trail created as shown below:

![EncryptedTrail](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/94nziz0kreg6vksekn46.png)

The terraform source code for above setup can be found [here](https://github.com/sujaypillai/awscb001/tree/cloudtrail)