Skip to content
Open
103 changes: 103 additions & 0 deletions infrastructure/stacks/api-layer/cloudtrail.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
resource "aws_cloudtrail" "data_events_trail" {
#checkov:skip=CKV_AWS_67: Ensure CloudTrail is enabled in all Regions
#checkov:skip=CKV_AWS_252: Ensure CloudTrail defines an SNS Topic
name = "${var.project_name}-${var.environment}-data-events-trail"
s3_bucket_name = module.s3_cloudtrail_bucket.storage_bucket_name
kms_key_id = aws_kms_key.cloudtrail_kms_key.arn
include_global_service_events = true
is_multi_region_trail = false
enable_log_file_validation = true

cloud_watch_logs_role_arn = aws_iam_role.cloudtrail_cloudwatch_role.arn
cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail_log_group.arn}:*"

event_selector {
read_write_type = "All"
include_management_events = false

data_resource {
type = "AWS::DynamoDB::Table"
values = [module.eligibility_status_table.arn]
}
}
}

resource "aws_kms_key" "cloudtrail_kms_key" {
description = "KMS key for CloudTrail log file encryption"
deletion_window_in_days = 14
enable_key_rotation = true

policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "EnableRootPermissions"
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
},
{
Sid = "AllowCloudTrailEncryptLogs"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
Action = [
"kms:GenerateDataKey*",
"kms:DescribeKey",
"kms:Encrypt"
]
Resource = "*"
}
]
})

tags = {
environment = var.environment
project_name = var.project_name
stack_name = local.stack_name
workspace = terraform.workspace
}

}

# KMS key alias
resource "aws_kms_alias" "cloudtrail_kms_alias" {
name = "alias/${var.project_name}-${var.environment}-cloudtrail-cmk"
target_key_id = aws_kms_key.cloudtrail_kms_key.key_id
}

# KMS key policy to allow CloudTrail and CloudWatch Logs to use the key for encryption and decryption
resource "aws_kms_key_policy" "cloudtrail_kms_key_policy" {
key_id = aws_kms_key.cloudtrail_kms_key.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
AWS = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:root"
}
Action = "kms:*"
Resource = "*"
},
{
Effect = "Allow"
Principal = {
Service = "logs.amazonaws.com"
}
Action = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
Resource = "*"
}
]
})
}
7 changes: 7 additions & 0 deletions infrastructure/stacks/api-layer/cloudwatch.tf
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,10 @@ resource "aws_cloudwatch_log_group" "rotation_sfn_logs" {
kms_key_id = module.secrets_manager.rotation_sns_key_arn
retention_in_days = 365
}

# CloudWatch Log Group for CloudTrail
resource "aws_cloudwatch_log_group" "cloudtrail_log_group" {
name = "${terraform.workspace == "default" ? "" : "${terraform.workspace}-"}elid-aws-cloudtrail-logs"
retention_in_days = 365
kms_key_id = aws_kms_alias.cloudtrail_kms_alias.arn
}
9 changes: 9 additions & 0 deletions infrastructure/stacks/api-layer/cloudwatch_alarms.tf
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ locals {
alarm_description = "Multiple Lambda function changes detected within 10 minutes"
actions_enabled = true
}
DynamoDBTableReadOutsideLambdaRole = {
threshold = 1
comparison_operator = "GreaterThanOrEqualToThreshold"
evaluation_periods = 1
period = 300
statistic = "Sum"
alarm_description = "DynamoDB table read detected from non-Lambda execution role"
actions_enabled = true
}
}

# API Gateway alarm configuration
Expand Down
6 changes: 6 additions & 0 deletions infrastructure/stacks/api-layer/cloudwatch_metrics.tf
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ locals {
filter = "{($.eventSource=lambda.amazonaws.com) && (($.eventName=CreateFunction20150331) || ($.eventName=DeleteFunction20150331) || ($.eventName=UpdateFunctionCode20150331) || ($.eventName=UpdateFunctionConfiguration20150331))}"
log_group_name = "NHSDAudit_trail_log_group"
},
{
name = "DynamoDBTableReadOutsideLambdaRole"
namespace = "security"
filter = "{($.eventSource=dynamodb.amazonaws.com) && (($.eventName=GetItem) || ($.eventName=Query) || ($.eventName=Scan) || ($.eventName=BatchGetItem) || ($.eventName=BatchWriteItem)) && ($.requestParameters.tableName=\"${module.eligibility_status_table.table_name}\") && ($.userIdentity.sessionContext.sessionIssuer.arn != \"${aws_iam_role.eligibility_lambda_role.arn}\")}"
log_group_name = aws_cloudwatch_log_group.cloudtrail_log_group.name
},
]
}

Expand Down
105 changes: 105 additions & 0 deletions infrastructure/stacks/api-layer/iam_policies.tf
Original file line number Diff line number Diff line change
Expand Up @@ -813,3 +813,108 @@ resource "aws_iam_role_policy" "external_s3_kms_access_policy" {
role = aws_iam_role.write_access_role[count.index].id
policy = data.aws_iam_policy_document.s3_dq_kms_access_policy.json
}


##################################
# Cloudtrail Bucket & KMS Policies
##################################

# S3 Cloudtrail bucket policy
data "aws_iam_policy_document" "s3_cloudtrail_bucket_policy" {
statement {
sid = "AllowS3SSLRequestsOnly"
actions = [
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:GetObject",
"s3:PutObject",
"s3:GetBucketAcl"
]
resources = [
module.s3_cloudtrail_bucket.storage_bucket_arn,
"${module.s3_cloudtrail_bucket.storage_bucket_arn}/*",
]
principals {
type = "Service"
identifiers = ["cloudtrail.amazonaws.com"]
}
condition {
test = "Bool"
values = ["true"]
variable = "aws:SecureTransport"
}
}
statement {
sid = "DenyS3NonSSLRequests"
actions = [
"s3:*"
]
effect = "Deny"
resources = [
module.s3_cloudtrail_bucket.storage_bucket_arn,
"${module.s3_cloudtrail_bucket.storage_bucket_arn}/*",
]
principals {
type = "*"
identifiers = ["*"]
}
condition {
test = "Bool"
values = ["false"]
variable = "aws:SecureTransport"
}
}
}

# Attach s3 Cloudtrail bucket policy to Cloudtrail role
resource "aws_s3_bucket_policy" "s3_cloudtrail_bucket_policy" {
bucket = module.s3_cloudtrail_bucket.storage_bucket_id
policy = data.aws_iam_policy_document.s3_cloudtrail_bucket_policy.json
}

# S3 Cloudtrail bucket KMS access policy
data "aws_iam_policy_document" "s3_cloudtrail_kms_access_policy" {
statement {
actions = [
"kms:Encrypt",
"kms:Decrypt",
"kms:ReEncrypt*",
"kms:GenerateDataKey*",
"kms:DescribeKey"
]
resources = [
module.s3_cloudtrail_bucket.storage_bucket_kms_key_arn
]
}
}

# Attach S3 Cloudtrail bucket KMS policy to Cloudtrail role
resource "aws_iam_role_policy" "s3_cloudtrail_kms_access_policy" {
name = "S3CloudTrailKMSAccess"
role = aws_iam_role.cloudtrail_cloudwatch_role.id
policy = data.aws_iam_policy_document.s3_cloudtrail_kms_access_policy.json
}

# CloudWatch Logs permissions policy for CloudTrail
data "aws_iam_policy_document" "cloudtrail_cloudwatch_policy" {
statement {
effect = "Allow"
actions = [
"logs:PutLogEvents",
"logs:CreateLogGroup",
"logs:CreateLogStream"
]
resources = [
aws_cloudwatch_log_group.cloudtrail_log_group.arn,
"${aws_cloudwatch_log_group.cloudtrail_log_group.arn}:*"
]

}
}

# Attach CloudTrail CloudWatch Logs policy to CloudTrail role
resource "aws_iam_role_policy" "cloudtrail_cloudwatch_policy" {
name = "CloudTrailCloudWatchLogsAccess"
role = aws_iam_role.cloudtrail_cloudwatch_role.id
policy = data.aws_iam_policy_document.cloudtrail_cloudwatch_policy.json
}
26 changes: 26 additions & 0 deletions infrastructure/stacks/api-layer/iam_roles.tf
Original file line number Diff line number Diff line change
Expand Up @@ -142,3 +142,29 @@ resource "aws_iam_role_policy_attachment" "rotation_vpc_access" {
role = aws_iam_role.rotation_lambda_role.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole"
}

# IAM role for CloudTrail to write to CloudWatch Logs
resource "aws_iam_role" "cloudtrail_cloudwatch_role" {
name = "cloudtrail-cloudwatch-role"

assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "cloudtrail.amazonaws.com"
}
}
]
})
permissions_boundary = aws_iam_policy.assumed_role_permissions_boundary.arn

tags = {
environment = var.environment
project_name = var.project_name
stack_name = local.stack_name
workspace = terraform.workspace
}
}
9 changes: 9 additions & 0 deletions infrastructure/stacks/api-layer/s3_buckets.tf
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,12 @@ module "s3_dq_metrics_bucket" {
stack_name = local.stack_name
workspace = terraform.workspace
}

module "s3_cloudtrail_bucket" {
source = "../../modules/s3"
bucket_name = "eli-cloudwatch-logs"
environment = var.environment
project_name = var.project_name
stack_name = local.stack_name
workspace = terraform.workspace
}
Loading