Skip to content

Commit 2ecefc5

Browse files
johncblandiiclaude
andauthored
feat: add KMS encryption support for CloudTrail logs (#89)
* feat(datadog-logs-archive): add KMS encryption support for CloudTrail logs This change addresses Drata compliance requirements by adding KMS encryption to the CloudTrail logs created by the datadog-logs-archive component. The implementation provides flexible configuration: encryption is enabled by default with automatic key creation, but users can also bring their own KMS key or disable encryption if needed. The KMS resources are organized in a dedicated kms.tf file for better maintainability. 🤖 Generated with Claude Code Co-Authored-By: Claude <[email protected]> * docs: readme update Signed-off-by: John C. Bland II <[email protected]> * fix: remove ViaService condition from KMS alias creation policy The ViaService condition restricting alias creation to EC2 would prevent Terraform from creating the KMS alias. Keeping only the CallerAccount condition allows principals in the account to create aliases while maintaining proper account scoping. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]> --------- Signed-off-by: John C. Bland II <[email protected]> Co-authored-by: Claude <[email protected]>
1 parent e9d2ef7 commit 2ecefc5

File tree

6 files changed

+223
-4
lines changed

6 files changed

+223
-4
lines changed

README.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,18 @@ description: |-
2222
1) Set `s3_force_destroy` to `true` and apply
2323
2) Set `enabled` to `false` and apply, or run `terraform destroy`
2424
25+
## CloudTrail KMS Encryption
26+
27+
By default, this component creates a KMS key to encrypt CloudTrail logs for compliance and security. The KMS encryption can be configured using these variables:
28+
29+
- `cloudtrail_enable_kms_encryption` (default: `true`) - Enable/disable KMS encryption for CloudTrail logs
30+
- `cloudtrail_kms_key_arn` (default: `null`) - Provide an existing KMS key ARN to use instead of creating a new one
31+
- `cloudtrail_create_kms_key` (default: `true`) - Create a new KMS key when `cloudtrail_kms_key_arn` is not provided
32+
- `cloudtrail_kms_key_deletion_window_in_days` (default: `10`) - KMS key deletion window (7-30 days)
33+
- `cloudtrail_kms_key_enable_rotation` (default: `true`) - Enable automatic KMS key rotation
34+
35+
The created KMS key includes the required policy statements for CloudTrail to encrypt logs and for authorized principals to decrypt them.
36+
2537
## Sponsorship
2638
2739
<picture>

src/README.md

Lines changed: 26 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/kms.tf

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# KMS key policy document for CloudTrail encryption
2+
data "aws_iam_policy_document" "cloudtrail_kms_key_policy" {
3+
count = local.enabled && var.cloudtrail_enable_kms_encryption && var.cloudtrail_create_kms_key && var.cloudtrail_kms_key_arn == null ? 1 : 0
4+
5+
# Enable IAM User Permissions
6+
statement {
7+
sid = "Enable IAM User Permissions"
8+
effect = "Allow"
9+
principals {
10+
type = "AWS"
11+
identifiers = ["arn:${local.aws_partition}:iam::${local.aws_account_id}:root"]
12+
}
13+
actions = [
14+
"kms:*"
15+
]
16+
resources = ["*"]
17+
}
18+
19+
# Allow CloudTrail to encrypt logs
20+
statement {
21+
sid = "Allow CloudTrail to encrypt logs"
22+
effect = "Allow"
23+
principals {
24+
type = "Service"
25+
identifiers = ["cloudtrail.amazonaws.com"]
26+
}
27+
actions = [
28+
"kms:GenerateDataKey*"
29+
]
30+
resources = ["*"]
31+
condition {
32+
test = "StringLike"
33+
variable = "aws:SourceArn"
34+
values = ["arn:${local.aws_partition}:cloudtrail:*:${local.aws_account_id}:trail/${module.this.id}"]
35+
}
36+
condition {
37+
test = "StringEquals"
38+
variable = "kms:EncryptionContext:aws:cloudtrail:arn"
39+
values = ["arn:${local.aws_partition}:cloudtrail:${var.region}:${local.aws_account_id}:trail/${module.this.id}"]
40+
}
41+
}
42+
43+
# Allow CloudTrail to describe key
44+
statement {
45+
sid = "Allow CloudTrail to describe key"
46+
effect = "Allow"
47+
principals {
48+
type = "Service"
49+
identifiers = ["cloudtrail.amazonaws.com"]
50+
}
51+
actions = [
52+
"kms:DescribeKey"
53+
]
54+
resources = ["*"]
55+
}
56+
57+
# Allow principals in the account to decrypt log files
58+
statement {
59+
sid = "Allow principals in the account to decrypt log files"
60+
effect = "Allow"
61+
principals {
62+
type = "AWS"
63+
identifiers = ["arn:${local.aws_partition}:iam::${local.aws_account_id}:root"]
64+
}
65+
actions = [
66+
"kms:Decrypt",
67+
"kms:ReEncryptFrom"
68+
]
69+
resources = ["*"]
70+
condition {
71+
test = "StringEquals"
72+
variable = "kms:CallerAccount"
73+
values = [local.aws_account_id]
74+
}
75+
condition {
76+
test = "StringLike"
77+
variable = "kms:EncryptionContext:aws:cloudtrail:arn"
78+
values = ["arn:${local.aws_partition}:cloudtrail:*:${local.aws_account_id}:trail/*"]
79+
}
80+
}
81+
82+
# Allow alias creation during resource creation
83+
statement {
84+
sid = "Allow alias creation during resource creation"
85+
effect = "Allow"
86+
principals {
87+
type = "AWS"
88+
identifiers = ["arn:${local.aws_partition}:iam::${local.aws_account_id}:root"]
89+
}
90+
actions = [
91+
"kms:CreateAlias"
92+
]
93+
resources = ["*"]
94+
condition {
95+
test = "StringEquals"
96+
variable = "kms:CallerAccount"
97+
values = [local.aws_account_id]
98+
}
99+
}
100+
}
101+
102+
# KMS key for CloudTrail encryption
103+
resource "aws_kms_key" "cloudtrail" {
104+
count = local.enabled && var.cloudtrail_enable_kms_encryption && var.cloudtrail_create_kms_key && var.cloudtrail_kms_key_arn == null ? 1 : 0
105+
106+
description = "KMS key for CloudTrail log encryption - ${module.this.id}"
107+
deletion_window_in_days = var.cloudtrail_kms_key_deletion_window_in_days
108+
enable_key_rotation = var.cloudtrail_kms_key_enable_rotation
109+
policy = data.aws_iam_policy_document.cloudtrail_kms_key_policy[0].json
110+
111+
tags = merge(
112+
module.this.tags,
113+
{
114+
Name = "${module.this.id}-cloudtrail"
115+
managed-by = "terraform"
116+
env = var.stage
117+
service = "datadog-logs-archive"
118+
part-of = "observability"
119+
}
120+
)
121+
}
122+
123+
# KMS key alias for easier identification
124+
resource "aws_kms_alias" "cloudtrail" {
125+
count = local.enabled && var.cloudtrail_enable_kms_encryption && var.cloudtrail_create_kms_key && var.cloudtrail_kms_key_arn == null ? 1 : 0
126+
127+
name = "alias/${module.this.id}-cloudtrail"
128+
target_key_id = aws_kms_key.cloudtrail[0].key_id
129+
}

src/main.tf

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,14 @@ locals {
3030
# default datadog_logs_archive query.
3131
default_query = join(" OR ", concat([join(":", ["env", var.stage]), join(":", ["account", local.aws_account_id])], var.additional_query_tags))
3232
query = var.query_override == null ? local.default_query : var.query_override
33+
34+
# CloudTrail KMS key ARN selection
35+
cloudtrail_kms_key_arn = (
36+
var.cloudtrail_enable_kms_encryption == false ? null :
37+
var.cloudtrail_kms_key_arn != null ? var.cloudtrail_kms_key_arn :
38+
var.cloudtrail_create_kms_key && local.enabled ? try(aws_kms_key.cloudtrail[0].arn, null) :
39+
null
40+
)
3341
}
3442

3543
# We use the http data source due to lack of a data source for datadog_logs_archive_order
@@ -304,7 +312,7 @@ module "cloudtrail" {
304312
# dependency on the attachment of the bucket policy, leading to
305313
# insufficient permissions issues on cloudtrail creation if it
306314
# happens to be attempted prior to completion of the policy attachment.
307-
depends_on = [module.cloudtrail_s3_bucket]
315+
depends_on = [module.cloudtrail_s3_bucket, aws_kms_key.cloudtrail]
308316
source = "cloudposse/cloudtrail/aws"
309317
version = "0.24.0"
310318

@@ -314,6 +322,7 @@ module "cloudtrail" {
314322
enabled = local.enabled
315323
enable_logging = true
316324
s3_bucket_name = module.cloudtrail_s3_bucket[0].bucket_id
325+
kms_key_arn = local.cloudtrail_kms_key_arn
317326

318327
event_selector = [
319328
{

src/outputs.tf

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,18 @@ output "catchall_id" {
4242
value = local.enabled && var.catchall_enabled ? datadog_logs_archive.catchall_archive[0].id : ""
4343
description = "The ID of the catchall log archive"
4444
}
45+
46+
output "cloudtrail_kms_key_arn" {
47+
value = local.cloudtrail_kms_key_arn
48+
description = "The ARN of the KMS key used for CloudTrail log encryption"
49+
}
50+
51+
output "cloudtrail_kms_key_id" {
52+
value = local.enabled && var.cloudtrail_enable_kms_encryption && var.cloudtrail_create_kms_key && var.cloudtrail_kms_key_arn == null ? aws_kms_key.cloudtrail[0].key_id : ""
53+
description = "The ID of the KMS key used for CloudTrail log encryption (only if created by this module)"
54+
}
55+
56+
output "cloudtrail_kms_key_alias" {
57+
value = local.enabled && var.cloudtrail_enable_kms_encryption && var.cloudtrail_create_kms_key && var.cloudtrail_kms_key_arn == null ? aws_kms_alias.cloudtrail[0].name : ""
58+
description = "The alias of the KMS key used for CloudTrail log encryption (only if created by this module)"
59+
}

src/variables.tf

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,35 @@ variable "s3_force_destroy" {
9595
default = false
9696
}
9797

98+
variable "cloudtrail_enable_kms_encryption" {
99+
type = bool
100+
description = "Enable KMS encryption for CloudTrail logs"
101+
default = true
102+
}
103+
104+
variable "cloudtrail_kms_key_arn" {
105+
type = string
106+
description = "ARN of an existing KMS key to use for CloudTrail log encryption. If not provided and cloudtrail_enable_kms_encryption is true, a new key will be created"
107+
default = null
108+
nullable = true
109+
}
110+
111+
variable "cloudtrail_create_kms_key" {
112+
type = bool
113+
description = "Create a new KMS key for CloudTrail encryption. Only used if cloudtrail_kms_key_arn is not provided and cloudtrail_enable_kms_encryption is true"
114+
default = true
115+
}
116+
117+
variable "cloudtrail_kms_key_deletion_window_in_days" {
118+
type = number
119+
description = "Duration in days after which the KMS key is deleted after destruction of the resource, must be between 7 and 30 days"
120+
default = 10
121+
}
122+
123+
variable "cloudtrail_kms_key_enable_rotation" {
124+
type = bool
125+
description = "Enable automatic rotation of the KMS key"
126+
default = true
127+
}
128+
98129

0 commit comments

Comments
 (0)