Skip to content

Commit cbba2ef

Browse files
authored
Merge branch 'main' into feature/ELI-702-code-signing
2 parents 4e61c0c + a9885b7 commit cbba2ef

File tree

7 files changed

+268
-0
lines changed

7 files changed

+268
-0
lines changed
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
resource "aws_cloudtrail" "data_events_trail" {
2+
#checkov:skip=CKV_AWS_67: Ensure CloudTrail is enabled in all Regions
3+
#checkov:skip=CKV_AWS_252: Ensure CloudTrail defines an SNS Topic
4+
name = "${var.project_name}-${var.environment}-data-events-trail"
5+
s3_bucket_name = module.s3_cloudtrail_bucket.storage_bucket_name
6+
kms_key_id = aws_kms_key.cloudtrail_kms_key.arn
7+
include_global_service_events = true
8+
is_multi_region_trail = false
9+
enable_log_file_validation = true
10+
11+
cloud_watch_logs_role_arn = aws_iam_role.cloudtrail_cloudwatch_role.arn
12+
cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail_log_group.arn}:*"
13+
14+
event_selector {
15+
read_write_type = "All"
16+
include_management_events = false
17+
18+
data_resource {
19+
type = "AWS::DynamoDB::Table"
20+
values = [module.eligibility_status_table.arn]
21+
}
22+
}
23+
}
24+
25+
resource "aws_kms_key" "cloudtrail_kms_key" {
26+
description = "KMS key for CloudTrail log file encryption"
27+
deletion_window_in_days = 14
28+
enable_key_rotation = true
29+
30+
policy = jsonencode({
31+
Version = "2012-10-17"
32+
Statement = [
33+
{
34+
Sid = "EnableRootPermissions"
35+
Effect = "Allow"
36+
Principal = {
37+
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
38+
}
39+
Action = "kms:*"
40+
Resource = "*"
41+
},
42+
{
43+
Sid = "AllowCloudTrailEncryptLogs"
44+
Effect = "Allow"
45+
Principal = {
46+
Service = "cloudtrail.amazonaws.com"
47+
}
48+
Action = [
49+
"kms:GenerateDataKey*",
50+
"kms:DescribeKey",
51+
"kms:Encrypt"
52+
]
53+
Resource = "*"
54+
}
55+
]
56+
})
57+
58+
tags = {
59+
environment = var.environment
60+
project_name = var.project_name
61+
stack_name = local.stack_name
62+
workspace = terraform.workspace
63+
}
64+
65+
}
66+
67+
# KMS key alias
68+
resource "aws_kms_alias" "cloudtrail_kms_alias" {
69+
name = "alias/${var.project_name}-${var.environment}-cloudtrail-cmk"
70+
target_key_id = aws_kms_key.cloudtrail_kms_key.key_id
71+
}
72+
73+
# KMS key policy to allow CloudTrail and CloudWatch Logs to use the key for encryption and decryption
74+
resource "aws_kms_key_policy" "cloudtrail_kms_key_policy" {
75+
key_id = aws_kms_key.cloudtrail_kms_key.id
76+
policy = jsonencode({
77+
Version = "2012-10-17"
78+
Statement = [
79+
{
80+
Effect = "Allow"
81+
Principal = {
82+
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
83+
}
84+
Action = "kms:*"
85+
Resource = "*"
86+
},
87+
{
88+
Sid = "AllowCloudTrailAndLogsKMS"
89+
Effect = "Allow"
90+
Principal = {
91+
Service = [
92+
"cloudtrail.amazonaws.com",
93+
"logs.amazonaws.com"
94+
]
95+
}
96+
Action = [
97+
"kms:Encrypt",
98+
"kms:Decrypt",
99+
"kms:ReEncrypt*",
100+
"kms:GenerateDataKey*",
101+
"kms:DescribeKey"
102+
]
103+
Resource = "*"
104+
}
105+
]
106+
})
107+
}

infrastructure/stacks/api-layer/cloudwatch.tf

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,10 @@ resource "aws_cloudwatch_log_group" "rotation_sfn_logs" {
4040
kms_key_id = module.secrets_manager.rotation_sns_key_arn
4141
retention_in_days = 365
4242
}
43+
44+
# CloudWatch Log Group for CloudTrail
45+
resource "aws_cloudwatch_log_group" "cloudtrail_log_group" {
46+
name = "${terraform.workspace == "default" ? "" : "${terraform.workspace}-"}elid-aws-cloudtrail-logs"
47+
retention_in_days = 365
48+
kms_key_id = aws_kms_alias.cloudtrail_kms_alias.arn
49+
}

infrastructure/stacks/api-layer/cloudwatch_alarms.tf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ locals {
172172
alarm_description = "Multiple Lambda function changes detected within 10 minutes"
173173
actions_enabled = true
174174
}
175+
DynamoDBTableReadOutsideLambdaRole = {
176+
threshold = 1
177+
comparison_operator = "GreaterThanOrEqualToThreshold"
178+
evaluation_periods = 1
179+
period = 300
180+
statistic = "Sum"
181+
alarm_description = "DynamoDB table read detected from non-Lambda execution role"
182+
actions_enabled = true
183+
}
175184
}
176185

177186
# API Gateway alarm configuration

infrastructure/stacks/api-layer/cloudwatch_metrics.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,12 @@ locals {
114114
filter = "{($.eventSource=lambda.amazonaws.com) && (($.eventName=CreateFunction20150331) || ($.eventName=DeleteFunction20150331) || ($.eventName=UpdateFunctionCode20150331) || ($.eventName=UpdateFunctionConfiguration20150331))}"
115115
log_group_name = "NHSDAudit_trail_log_group"
116116
},
117+
{
118+
name = "DynamoDBTableReadOutsideLambdaRole"
119+
namespace = "security"
120+
filter = "{($.eventSource=dynamodb.amazonaws.com) && (($.eventName=GetItem) || ($.eventName=Query) || ($.eventName=Scan) || ($.eventName=BatchGetItem) || ($.eventName=BatchWriteItem)) && (($.requestParameters.tableName=\"${module.eligibility_status_table.table_name}\") || ($.requestParameters.requestItems[0].tableName=\"${module.eligibility_status_table.table_name}\")) && ($.userIdentity.sessionContext.sessionIssuer.arn != \"${aws_iam_role.eligibility_lambda_role.arn}\")}"
121+
log_group_name = aws_cloudwatch_log_group.cloudtrail_log_group.name
122+
},
117123
]
118124
}
119125

infrastructure/stacks/api-layer/iam_policies.tf

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,3 +916,108 @@ resource "aws_iam_role_policy" "external_s3_kms_access_policy" {
916916
role = aws_iam_role.write_access_role[count.index].id
917917
policy = data.aws_iam_policy_document.s3_dq_kms_access_policy.json
918918
}
919+
920+
921+
##################################
922+
# Cloudtrail Bucket & KMS Policies
923+
##################################
924+
925+
# S3 Cloudtrail bucket policy
926+
data "aws_iam_policy_document" "s3_cloudtrail_bucket_policy" {
927+
statement {
928+
sid = "AllowS3SSLRequestsOnly"
929+
actions = [
930+
"s3:ListBucket",
931+
"s3:GetBucketLocation",
932+
"s3:GetObject",
933+
"s3:PutObject",
934+
"s3:GetBucketAcl"
935+
]
936+
resources = [
937+
module.s3_cloudtrail_bucket.storage_bucket_arn,
938+
"${module.s3_cloudtrail_bucket.storage_bucket_arn}/*",
939+
]
940+
principals {
941+
type = "Service"
942+
identifiers = ["cloudtrail.amazonaws.com"]
943+
}
944+
condition {
945+
test = "Bool"
946+
values = ["true"]
947+
variable = "aws:SecureTransport"
948+
}
949+
}
950+
statement {
951+
sid = "DenyS3NonSSLRequests"
952+
actions = [
953+
"s3:*"
954+
]
955+
effect = "Deny"
956+
resources = [
957+
module.s3_cloudtrail_bucket.storage_bucket_arn,
958+
"${module.s3_cloudtrail_bucket.storage_bucket_arn}/*",
959+
]
960+
principals {
961+
type = "*"
962+
identifiers = ["*"]
963+
}
964+
condition {
965+
test = "Bool"
966+
values = ["false"]
967+
variable = "aws:SecureTransport"
968+
}
969+
}
970+
}
971+
972+
# Attach s3 Cloudtrail bucket policy to Cloudtrail role
973+
resource "aws_s3_bucket_policy" "s3_cloudtrail_bucket_policy" {
974+
bucket = module.s3_cloudtrail_bucket.storage_bucket_id
975+
policy = data.aws_iam_policy_document.s3_cloudtrail_bucket_policy.json
976+
}
977+
978+
# S3 Cloudtrail bucket KMS access policy
979+
data "aws_iam_policy_document" "s3_cloudtrail_kms_access_policy" {
980+
statement {
981+
actions = [
982+
"kms:Encrypt",
983+
"kms:Decrypt",
984+
"kms:ReEncrypt*",
985+
"kms:GenerateDataKey*",
986+
"kms:DescribeKey"
987+
]
988+
resources = [
989+
module.s3_cloudtrail_bucket.storage_bucket_kms_key_arn
990+
]
991+
}
992+
}
993+
994+
# Attach S3 Cloudtrail bucket KMS policy to Cloudtrail role
995+
resource "aws_iam_role_policy" "s3_cloudtrail_kms_access_policy" {
996+
name = "S3CloudTrailKMSAccess"
997+
role = aws_iam_role.cloudtrail_cloudwatch_role.id
998+
policy = data.aws_iam_policy_document.s3_cloudtrail_kms_access_policy.json
999+
}
1000+
1001+
# CloudWatch Logs permissions policy for CloudTrail
1002+
data "aws_iam_policy_document" "cloudtrail_cloudwatch_policy" {
1003+
statement {
1004+
effect = "Allow"
1005+
actions = [
1006+
"logs:PutLogEvents",
1007+
"logs:CreateLogGroup",
1008+
"logs:CreateLogStream"
1009+
]
1010+
resources = [
1011+
aws_cloudwatch_log_group.cloudtrail_log_group.arn,
1012+
"${aws_cloudwatch_log_group.cloudtrail_log_group.arn}:*"
1013+
]
1014+
1015+
}
1016+
}
1017+
1018+
# Attach CloudTrail CloudWatch Logs policy to CloudTrail role
1019+
resource "aws_iam_role_policy" "cloudtrail_cloudwatch_policy" {
1020+
name = "CloudTrailCloudWatchLogsAccess"
1021+
role = aws_iam_role.cloudtrail_cloudwatch_role.id
1022+
policy = data.aws_iam_policy_document.cloudtrail_cloudwatch_policy.json
1023+
}

infrastructure/stacks/api-layer/iam_roles.tf

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,3 +142,28 @@ resource "aws_iam_role_policy_attachment" "rotation_vpc_access" {
142142
role = aws_iam_role.rotation_lambda_role.name
143143
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
144144
}
145+
146+
# IAM role for CloudTrail to write to CloudWatch Logs
147+
resource "aws_iam_role" "cloudtrail_cloudwatch_role" {
148+
name = "cloudtrail-cloudwatch-role"
149+
150+
assume_role_policy = jsonencode({
151+
Version = "2012-10-17"
152+
Statement = [
153+
{
154+
Action = "sts:AssumeRole"
155+
Effect = "Allow"
156+
Principal = {
157+
Service = "cloudtrail.amazonaws.com"
158+
}
159+
}
160+
]
161+
})
162+
permissions_boundary = aws_iam_policy.assumed_role_permissions_boundary.arn
163+
164+
tags = {
165+
Environment = var.environment
166+
Purpose = "cloudtrail-service-role"
167+
ManagedBy = "terraform"
168+
}
169+
}

infrastructure/stacks/api-layer/s3_buckets.tf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,12 @@ module "s3_dq_metrics_bucket" {
5757
stack_name = local.stack_name
5858
workspace = terraform.workspace
5959
}
60+
61+
module "s3_cloudtrail_bucket" {
62+
source = "../../modules/s3"
63+
bucket_name = "eli-cloudwatch-logs"
64+
environment = var.environment
65+
project_name = var.project_name
66+
stack_name = local.stack_name
67+
workspace = terraform.workspace
68+
}

0 commit comments

Comments
 (0)