diff --git a/aws_sra_examples/terraform/common/dynamodb/main.tf b/aws_sra_examples/terraform/common/dynamodb/main.tf index 2b13f467d..0324d84f2 100644 --- a/aws_sra_examples/terraform/common/dynamodb/main.tf +++ b/aws_sra_examples/terraform/common/dynamodb/main.tf @@ -8,7 +8,7 @@ resource "aws_dynamodb_table" "terraform_locks" { #checkov:skip=CKV_AWS_119: Ensure DynamoDB Tables are encrypted using a KMS Customer Managed CMK name = var.dynamodb_name billing_mode = "PAY_PER_REQUEST" - hash_key = "LockID" + hash_key = "LockID" attribute { name = "LockID" type = "S" diff --git a/aws_sra_examples/terraform/common/dynamodb/output.tf b/aws_sra_examples/terraform/common/dynamodb/output.tf index a86de848a..6a8b68cb3 100644 --- a/aws_sra_examples/terraform/common/dynamodb/output.tf +++ b/aws_sra_examples/terraform/common/dynamodb/output.tf @@ -3,6 +3,6 @@ # SPDX-License-Identifier: MIT-0 ######################################################################## -output dynamo_db_table_name { - value = aws_dynamodb_table.terraform_locks.name +output "dynamo_db_table_name" { + value = aws_dynamodb_table.terraform_locks.name } \ No newline at end of file diff --git a/aws_sra_examples/terraform/common/dynamodb/variables.tf b/aws_sra_examples/terraform/common/dynamodb/variables.tf index b9a21bf4e..ca77c21b3 100644 --- a/aws_sra_examples/terraform/common/dynamodb/variables.tf +++ b/aws_sra_examples/terraform/common/dynamodb/variables.tf @@ -4,9 +4,9 @@ ######################################################################## variable "dynamodb_name" { - description = "DynamoDB Table Name for state locking" - type = string - default = "sra-tfstate-lock" + description = "DynamoDB Table Name for state locking" + type = string + default = "sra-tfstate-lock" } variable "sra_solution_name" { diff --git a/aws_sra_examples/terraform/common/providers.tf b/aws_sra_examples/terraform/common/providers.tf index f44b58d66..635d4c78d 100644 --- a/aws_sra_examples/terraform/common/providers.tf +++ b/aws_sra_examples/terraform/common/providers.tf @@ -4,8 +4,12 @@ ######################################################################## terraform { + required_version = ">= 1.0.0" required_providers { - aws = ">= 5.1.0" + aws = { + source = "hashicorp/aws" + version = ">= 5.31.0" + } } } @@ -15,7 +19,9 @@ provider "aws" { default_tags { tags = { - Owner = "Security" + Owner = "Security" + Environment = "SRA" + Terraform = "true" } } } \ No newline at end of file diff --git a/aws_sra_examples/terraform/common/s3/main.tf b/aws_sra_examples/terraform/common/s3/main.tf index 579f22a63..2ab4f8a65 100644 --- a/aws_sra_examples/terraform/common/s3/main.tf +++ b/aws_sra_examples/terraform/common/s3/main.tf @@ -4,11 +4,6 @@ ######################################################################## resource "aws_s3_bucket" "sra_state_bucket" { - #checkov:skip=CKV2_AWS_61: Ensure that an S3 bucket has a lifecycle configuration - #checkov:skip=CKV_AWS_18: Ensure the S3 bucket has access logging enabled - #checkov:skip=CKV2_AWS_62: Ensure S3 buckets should have event notifications enabled - #checkov:skip=CKV_AWS_144: Ensure that S3 bucket has cross-region replication enabled - bucket = "${var.sra_state_bucket_prefix}-${data.aws_region.current.name}-${data.aws_caller_identity.current.account_id}" force_destroy = true @@ -18,7 +13,6 @@ resource "aws_s3_bucket" "sra_state_bucket" { } resource "aws_s3_bucket_server_side_encryption_configuration" "sra_state_bucket_see" { - #checkov:skip=CKV2_AWS_67: Ensure AWS S3 bucket encrypted with Customer Managed Key (CMK) has regular rotation bucket = aws_s3_bucket.sra_state_bucket.id rule { @@ -26,6 +20,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "sra_state_bucket_ kms_master_key_id = var.kms_key_id sse_algorithm = "aws:kms" } + bucket_key_enabled = true } } @@ -37,10 +32,9 @@ resource "aws_s3_bucket_versioning" "sra_state_bucket_versioning" { } resource "aws_s3_bucket_ownership_controls" "sra_state_bucket_ownership_control" { - #checkov:skip=CKV2_AWS_65: Ensure access control lists for S3 buckets are disabled bucket = aws_s3_bucket.sra_state_bucket.id rule { - object_ownership = "BucketOwnerPreferred" + object_ownership = "BucketOwnerEnforced" } } @@ -52,3 +46,132 @@ resource "aws_s3_bucket_public_access_block" "sra_state_bucket_public_access_blo ignore_public_acls = true restrict_public_buckets = true } + +resource "aws_s3_bucket_lifecycle_configuration" "sra_state_bucket_lifecycle" { + bucket = aws_s3_bucket.sra_state_bucket.id + + rule { + id = "cleanup-old-versions" + status = "Enabled" + + noncurrent_version_expiration { + noncurrent_days = 90 + } + + abort_incomplete_multipart_upload { + days_after_initiation = 7 + } + } +} + +resource "aws_s3_bucket_logging" "sra_state_bucket_logging" { + bucket = aws_s3_bucket.sra_state_bucket.id + + target_bucket = aws_s3_bucket.sra_state_bucket_logs.id + target_prefix = "access-logs/" +} + +resource "aws_s3_bucket" "sra_state_bucket_logs" { + bucket = "${var.sra_state_bucket_prefix}-logs-${data.aws_region.current.name}-${data.aws_caller_identity.current.account_id}" + force_destroy = true + + tags = { + "sra-solution" = var.sra_solution_name + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "sra_state_bucket_logs_see" { + bucket = aws_s3_bucket.sra_state_bucket_logs.id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = var.kms_key_id + sse_algorithm = "aws:kms" + } + bucket_key_enabled = true + } +} + +resource "aws_s3_bucket_versioning" "sra_state_bucket_logs_versioning" { + bucket = aws_s3_bucket.sra_state_bucket_logs.id + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_ownership_controls" "sra_state_bucket_logs_ownership_control" { + bucket = aws_s3_bucket.sra_state_bucket_logs.id + rule { + object_ownership = "BucketOwnerEnforced" + } +} + +resource "aws_s3_bucket_public_access_block" "sra_state_bucket_logs_public_access_block" { + bucket = aws_s3_bucket.sra_state_bucket_logs.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_lifecycle_configuration" "sra_state_bucket_logs_lifecycle" { + bucket = aws_s3_bucket.sra_state_bucket_logs.id + + rule { + id = "logs-cleanup" + status = "Enabled" + + expiration { + days = 365 + } + } +} + +resource "aws_s3_bucket_policy" "sra_state_bucket_policy" { + bucket = aws_s3_bucket.sra_state_bucket.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "DenyInsecureTransport" + Effect = "Deny" + Principal = "*" + Action = "s3:*" + Resource = [ + aws_s3_bucket.sra_state_bucket.arn, + "${aws_s3_bucket.sra_state_bucket.arn}/*" + ] + Condition = { + Bool = { + "aws:SecureTransport" = "false" + } + } + } + ] + }) +} + +resource "aws_s3_bucket_policy" "sra_state_bucket_logs_policy" { + bucket = aws_s3_bucket.sra_state_bucket_logs.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "DenyInsecureTransport" + Effect = "Deny" + Principal = "*" + Action = "s3:*" + Resource = [ + aws_s3_bucket.sra_state_bucket_logs.arn, + "${aws_s3_bucket.sra_state_bucket_logs.arn}/*" + ] + Condition = { + Bool = { + "aws:SecureTransport" = "false" + } + } + } + ] + }) +} diff --git a/aws_sra_examples/terraform/common/s3/output.tf b/aws_sra_examples/terraform/common/s3/output.tf index 40a2d2abf..1ee4ae4ef 100644 --- a/aws_sra_examples/terraform/common/s3/output.tf +++ b/aws_sra_examples/terraform/common/s3/output.tf @@ -3,6 +3,6 @@ # SPDX-License-Identifier: MIT-0 ######################################################################## -output bucket_name { - value = aws_s3_bucket.sra_state_bucket.id +output "bucket_name" { + value = aws_s3_bucket.sra_state_bucket.id } \ No newline at end of file diff --git a/aws_sra_examples/terraform/common/s3/variables.tf b/aws_sra_examples/terraform/common/s3/variables.tf index db8d7a989..00d5d797f 100644 --- a/aws_sra_examples/terraform/common/s3/variables.tf +++ b/aws_sra_examples/terraform/common/s3/variables.tf @@ -9,7 +9,7 @@ variable "sra_state_bucket_prefix" { default = "sra-tfstate-files" } -variable kms_key_id { +variable "kms_key_id" { description = "KMS Key ID" type = string } diff --git a/aws_sra_examples/terraform/common/secrets_kms/main.tf b/aws_sra_examples/terraform/common/secrets_kms/main.tf index 99be1b510..22d1ddedd 100644 --- a/aws_sra_examples/terraform/common/secrets_kms/main.tf +++ b/aws_sra_examples/terraform/common/secrets_kms/main.tf @@ -13,7 +13,7 @@ data "aws_iam_policy_document" "sra_secrets_key_policy" { #checkov:skip=CKV_AWS_109: Ensure IAM policies does not allow permissions management without constraints #checkov:skip=CKV_AWS_111: Ensure IAM policies does not allow write access without constraints #checkov:skip=CKV_AWS_356: Ensure no IAM policies documents allow "*" as a statement's resource for restrictable actions - + statement { sid = "Enable IAM User Permissions" effect = "Allow" diff --git a/aws_sra_examples/terraform/common/secrets_kms/output.tf b/aws_sra_examples/terraform/common/secrets_kms/output.tf index 20e0fc8f7..c91b66d96 100644 --- a/aws_sra_examples/terraform/common/secrets_kms/output.tf +++ b/aws_sra_examples/terraform/common/secrets_kms/output.tf @@ -3,6 +3,6 @@ # SPDX-License-Identifier: MIT-0 ######################################################################## -output kms_key_arn { - value = aws_kms_key.sra_secrets_key.arn +output "kms_key_arn" { + value = aws_kms_key.sra_secrets_key.arn } \ No newline at end of file diff --git a/aws_sra_examples/terraform/common/ssm_parameters/main.tf b/aws_sra_examples/terraform/common/ssm_parameters/main.tf index 0c88e54aa..d9ea24f0f 100644 --- a/aws_sra_examples/terraform/common/ssm_parameters/main.tf +++ b/aws_sra_examples/terraform/common/ssm_parameters/main.tf @@ -137,7 +137,7 @@ data "aws_iam_policy_document" "cloudwatch_policy" { data "aws_iam_policy_document" "management_account_parameters_lambda_ssm_policy" { #checkov:skip=CKV_AWS_356: Ensure no IAM policies documents allow "*" as a statement's resource for restrictable actions - + statement { sid = "STSOrganizationRead" effect = "Allow" diff --git a/aws_sra_examples/terraform/solutions/cloudtrail_org/org/main.tf b/aws_sra_examples/terraform/solutions/cloudtrail_org/org/main.tf index 50e76c05e..5386c375e 100644 --- a/aws_sra_examples/terraform/solutions/cloudtrail_org/org/main.tf +++ b/aws_sra_examples/terraform/solutions/cloudtrail_org/org/main.tf @@ -289,7 +289,7 @@ resource "aws_lambda_function" "cloudtrail_org_lambda_function" { #checkov:skip=CKV_AWS_115: Ensure that AWS Lambda function is configured for function-level concurrent execution limit #checkov:skip=CKV_AWS_117: Ensure that AWS Lambda function is configured inside a VPC #checkov:skip=CKV_AWS_50: X-Ray tracing is enabled for Lambda - + description = "Creates an Organization CloudTrail" function_name = var.cloudtrail_lambda_function_name role = aws_iam_role.cloudtrail_lambda_role.arn diff --git a/aws_sra_examples/terraform/solutions/cloudtrail_org/s3/main.tf b/aws_sra_examples/terraform/solutions/cloudtrail_org/s3/main.tf index 38ff44887..f2eea5937 100644 --- a/aws_sra_examples/terraform/solutions/cloudtrail_org/s3/main.tf +++ b/aws_sra_examples/terraform/solutions/cloudtrail_org/s3/main.tf @@ -3,10 +3,6 @@ # SPDX-License-Identifier: MIT-0 ######################################################################## resource "aws_s3_bucket" "org_trail_bucket" { - #checkov:skip=CKV2_AWS_61: Ensure that an S3 bucket has a lifecycle configuration - #checkov:skip=CKV_AWS_18: Ensure the S3 bucket has access logging enabled - #checkov:skip=CKV2_AWS_62: Ensure S3 buckets should have event notifications enabled - #checkov:skip=CKV_AWS_144: Ensure that S3 bucket has cross-region replication enabled bucket = "${var.bucket_name_prefix}-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}" tags = { @@ -18,7 +14,6 @@ resource "aws_s3_bucket" "org_trail_bucket" { } resource "aws_s3_bucket_server_side_encryption_configuration" "this" { - #checkov:skip=CKV2_AWS_67: Ensure AWS S3 bucket encrypted with Customer Managed Key (CMK) has regular rotation bucket = aws_s3_bucket.org_trail_bucket.id rule { @@ -26,6 +21,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "this" { kms_master_key_id = var.organization_cloudtrail_kms_key_id sse_algorithm = "aws:kms" } + bucket_key_enabled = true } } @@ -46,14 +42,124 @@ resource "aws_s3_bucket_public_access_block" "this" { } resource "aws_s3_bucket_ownership_controls" "this" { - #checkov:skip=CKV2_AWS_65: Ensure access control lists for S3 buckets are disabled bucket = aws_s3_bucket.org_trail_bucket.id rule { - object_ownership = "BucketOwnerPreferred" + object_ownership = "BucketOwnerEnforced" } } +resource "aws_s3_bucket_lifecycle_configuration" "this" { + bucket = aws_s3_bucket.org_trail_bucket.id + + rule { + id = "cloudtrail-logs-lifecycle" + status = "Enabled" + + transition { + days = 90 + storage_class = "STANDARD_IA" + } + + transition { + days = 180 + storage_class = "GLACIER" + } + + expiration { + days = 2555 # 7 years for compliance + } + } +} + +resource "aws_s3_bucket_logging" "this" { + bucket = aws_s3_bucket.org_trail_bucket.id + + target_bucket = aws_s3_bucket.org_trail_logs_bucket.id + target_prefix = "access-logs/" +} + +resource "aws_s3_bucket" "org_trail_logs_bucket" { + bucket = "${var.bucket_name_prefix}-logs-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}" + + tags = { + "sra-solution" = var.sra_solution_name + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "logs" { + bucket = aws_s3_bucket.org_trail_logs_bucket.id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = var.organization_cloudtrail_kms_key_id + sse_algorithm = "aws:kms" + } + bucket_key_enabled = true + } +} + +resource "aws_s3_bucket_versioning" "logs" { + bucket = aws_s3_bucket.org_trail_logs_bucket.id + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_public_access_block" "logs" { + bucket = aws_s3_bucket.org_trail_logs_bucket.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_ownership_controls" "logs" { + bucket = aws_s3_bucket.org_trail_logs_bucket.id + + rule { + object_ownership = "BucketOwnerEnforced" + } +} + +resource "aws_s3_bucket_lifecycle_configuration" "logs" { + bucket = aws_s3_bucket.org_trail_logs_bucket.id + + rule { + id = "logs-cleanup" + status = "Enabled" + + expiration { + days = 365 + } + } +} + +resource "aws_s3_bucket_policy" "org_trail_logs_bucket_policy" { + bucket = aws_s3_bucket.org_trail_logs_bucket.id + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Sid = "DenyInsecureTransport", + Effect = "Deny", + Principal = "*", + Action = "s3:*", + Resource = [ + aws_s3_bucket.org_trail_logs_bucket.arn, + "${aws_s3_bucket.org_trail_logs_bucket.arn}/*" + ], + Condition = { + Bool = { + "aws:SecureTransport" = "false" + } + } + } + ] + }) +} + resource "aws_s3_bucket_policy" "org_trail_bucket_policy" { bucket = aws_s3_bucket.org_trail_bucket.id policy = jsonencode({ @@ -143,21 +249,103 @@ resource "aws_s3_bucket_policy" "org_trail_bucket_policy" { } resource "aws_secretsmanager_secret" "org_trail_s3_bucket_secret" { - #checkov:skip=CKV_AWS_149: Ensure that Secrets Manager secret is encrypted using KMS CMK - #checkov:skip=CKV2_AWS_57: Ensure Secrets Manager secrets should have automatic rotation enabled - count = var.sra_secrets_key_alias_arn != "" ? 1 : 0 - name = "sra/cloudtrail_org_s3_bucket" - description = "Organization CloudTrail S3 Bucket" - - kms_key_id = var.sra_secrets_key_alias_arn + name = "sra/cloudtrail_org_s3_bucket" + description = "Organization CloudTrail S3 Bucket" + kms_key_id = var.sra_secrets_key_alias_arn + recovery_window_in_days = 30 tags = { "sra-solution" = var.sra_solution_name } } +resource "aws_secretsmanager_secret_rotation" "org_trail_s3_bucket_rotation" { + count = var.sra_secrets_key_alias_arn != "" ? 1 : 0 + secret_id = aws_secretsmanager_secret.org_trail_s3_bucket_secret[0].id + rotation_lambda_arn = aws_lambda_function.rotation_lambda[0].arn + + rotation_rules { + automatically_after_days = 90 + } +} + +resource "aws_lambda_function" "rotation_lambda" { + count = var.sra_secrets_key_alias_arn != "" ? 1 : 0 + function_name = "sra-cloudtrail-secret-rotation" + role = aws_iam_role.lambda_role[0].arn + handler = "index.lambda_handler" + runtime = "python3.9" + timeout = 30 + + environment { + variables = { + SECRET_ARN = aws_secretsmanager_secret.org_trail_s3_bucket_secret[0].arn + } + } + + filename = "${path.module}/lambda_function.zip" + source_code_hash = filebase64sha256("${path.module}/lambda_function.zip") +} + +resource "aws_iam_role" "lambda_role" { + count = var.sra_secrets_key_alias_arn != "" ? 1 : 0 + name = "sra-cloudtrail-secret-rotation-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_role_policy" "lambda_policy" { + count = var.sra_secrets_key_alias_arn != "" ? 1 : 0 + name = "sra-cloudtrail-secret-rotation-policy" + role = aws_iam_role.lambda_role[0].id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "secretsmanager:GetSecretValue", + "secretsmanager:PutSecretValue", + "secretsmanager:UpdateSecretVersionStage" + ] + Effect = "Allow" + Resource = aws_secretsmanager_secret.org_trail_s3_bucket_secret[0].arn + }, + { + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Effect = "Allow" + Resource = "arn:aws:logs:*:*:*" + } + ] + }) +} + +resource "aws_secretsmanager_secret_version" "org_trail_s3_bucket_secret_version" { + count = var.sra_secrets_key_alias_arn != "" ? 1 : 0 + + secret_id = aws_secretsmanager_secret.org_trail_s3_bucket_secret[0].id + secret_string = jsonencode({ + "CloudTrailS3BucketArn" : aws_s3_bucket.org_trail_bucket.arn + }) +} + resource "aws_secretsmanager_secret_policy" "org_trail_s3_bucket_secret_policy" { count = var.sra_secrets_key_alias_arn != "" ? 1 : 0 @@ -170,7 +358,7 @@ resource "aws_secretsmanager_secret_policy" "org_trail_s3_bucket_secret_policy" Action = "secretsmanager:GetSecretValue", Effect = "Allow", Principal = { - AWS = "${data.aws_partition.current.partition}:iam::${var.management_account_id}:root", + AWS = "arn:${data.aws_partition.current.partition}:iam::${var.management_account_id}:root" }, Resource = "*", Condition = { diff --git a/aws_sra_examples/terraform/solutions/guard_duty/gd_configuration/invoke.tf b/aws_sra_examples/terraform/solutions/guard_duty/gd_configuration/invoke.tf index c16f74ca1..6a6b03497 100644 --- a/aws_sra_examples/terraform/solutions/guard_duty/gd_configuration/invoke.tf +++ b/aws_sra_examples/terraform/solutions/guard_duty/gd_configuration/invoke.tf @@ -27,8 +27,8 @@ resource "aws_lambda_invocation" "lambda_invoke" { "AUTO_ENABLE_MALWARE_PROTECTION" : "${var.enable_malware_protection}", "ENABLE_RDS_LOGIN_EVENTS" : "${var.enable_rds_login_events}", "ENABLE_RUNTIME_MONITORING" : "${var.enable_runtime_monitoring}", - "ENABLE_ECS_FARGATE_AGENT_MANAGEMENT": "${var.enable_ecs_fargate_agent_management}", - "ENABLE_EC2_AGENT_MANAGEMENT": "${var.enable_ec2_agent_management}", + "ENABLE_ECS_FARGATE_AGENT_MANAGEMENT" : "${var.enable_ecs_fargate_agent_management}", + "ENABLE_EC2_AGENT_MANAGEMENT" : "${var.enable_ec2_agent_management}", "ENABLE_EKS_ADDON_MANAGEMENT" : "${var.enable_eks_addon_management}", "ENABLE_LAMBDA_NETWORK_LOGS" : "${var.enable_lambda_network_logs}", } diff --git a/aws_sra_examples/terraform/solutions/guard_duty/kms_key/main.tf b/aws_sra_examples/terraform/solutions/guard_duty/kms_key/main.tf index 092a31f4a..9fdb9d63b 100644 --- a/aws_sra_examples/terraform/solutions/guard_duty/kms_key/main.tf +++ b/aws_sra_examples/terraform/solutions/guard_duty/kms_key/main.tf @@ -3,10 +3,6 @@ # SPDX-License-Identifier: MIT-0 ######################################################################## data "aws_iam_policy_document" "kms_policy" { - #checkov:skip=CKV_AWS_111: Ensure IAM policies does not allow write access without constraints - #checkov:skip=CKV_AWS_356: Ensure no IAM policies documents allow "*" as a statement's resource for restrictable actions - #checkov:skip=CKV_AWS_109: Ensure IAM policies does not allow permissions management / resource exposure without constraints - statement { sid = "EnableIAMUserPermissions" effect = "Allow" @@ -37,7 +33,7 @@ data "aws_iam_policy_document" "kms_policy" { condition { test = "StringEquals" variable = "kms:CallerAccount" - values = ["${data.aws_caller_identity.current.account_id}"] + values = [data.aws_caller_identity.current.account_id] } condition { test = "StringEquals" @@ -51,9 +47,12 @@ data "aws_iam_policy_document" "kms_policy" { } statement { - sid = "AllowLogArchiveAndManagementAccountAccess" - effect = "Allow" - actions = ["kms:Decrypt"] + sid = "AllowLogArchiveAndManagementAccountAccess" + effect = "Allow" + actions = [ + "kms:Decrypt", + "kms:DescribeKey" + ] resources = ["*"] principals { type = "AWS" @@ -65,9 +64,12 @@ data "aws_iam_policy_document" "kms_policy" { } statement { - sid = "AllowAccountAccess" - effect = "Allow" - actions = ["kms:DescribeKey", "kms:Decrypt"] + sid = "AllowAccountAccess" + effect = "Allow" + actions = [ + "kms:DescribeKey", + "kms:Decrypt" + ] resources = ["*"] principals { type = "AWS" @@ -77,9 +79,10 @@ data "aws_iam_policy_document" "kms_policy" { } resource "aws_kms_key" "guardduty_delivery_key" { - description = "SRA GuardDuty Delivery Key" - enable_key_rotation = true - policy = data.aws_iam_policy_document.kms_policy.json + description = "SRA GuardDuty Delivery Key" + enable_key_rotation = true + policy = data.aws_iam_policy_document.kms_policy.json + deletion_window_in_days = 30 tags = { "sra-solution" = var.sra_solution_name @@ -92,22 +95,95 @@ resource "aws_kms_alias" "guardduty_delivery_key_alias" { } resource "aws_secretsmanager_secret" "guardduty_delivery_key_secret" { - #checkov:skip=CKV2_AWS_57: Ensure Secrets Manager secrets should have automatic rotation enabled - - count = var.create_secret ? 1 : 0 - name = "sra/guardduty_org_delivery_key_arn" - description = "GuardDuty Delivery KMS Key ARN" - - kms_key_id = var.sra_secrets_key_alias_arn + count = var.create_secret ? 1 : 0 + name = "sra/guardduty_org_delivery_key_arn" + description = "GuardDuty Delivery KMS Key ARN" + kms_key_id = var.sra_secrets_key_alias_arn + recovery_window_in_days = 30 tags = { "sra-solution" = var.sra_solution_name } } -resource "aws_secretsmanager_secret_version" "secret_string" { +resource "aws_secretsmanager_secret_rotation" "guardduty_delivery_key_rotation" { + count = var.create_secret ? 1 : 0 + secret_id = aws_secretsmanager_secret.guardduty_delivery_key_secret[0].id + rotation_lambda_arn = aws_lambda_function.rotation_lambda[0].arn + + rotation_rules { + automatically_after_days = 90 + } +} + +resource "aws_lambda_function" "rotation_lambda" { + count = var.create_secret ? 1 : 0 + function_name = "sra-guardduty-key-rotation" + role = aws_iam_role.lambda_role[0].arn + handler = "index.lambda_handler" + runtime = "python3.9" + timeout = 30 + + environment { + variables = { + SECRET_ARN = aws_secretsmanager_secret.guardduty_delivery_key_secret[0].arn + } + } + + filename = "${path.module}/lambda_function.zip" + source_code_hash = filebase64sha256("${path.module}/lambda_function.zip") +} + +resource "aws_iam_role" "lambda_role" { + count = var.create_secret ? 1 : 0 + name = "sra-guardduty-key-rotation-role" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "lambda.amazonaws.com" + } + } + ] + }) +} + +resource "aws_iam_role_policy" "lambda_policy" { count = var.create_secret ? 1 : 0 + name = "sra-guardduty-key-rotation-policy" + role = aws_iam_role.lambda_role[0].id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = [ + "secretsmanager:GetSecretValue", + "secretsmanager:PutSecretValue", + "secretsmanager:UpdateSecretVersionStage" + ] + Effect = "Allow" + Resource = aws_secretsmanager_secret.guardduty_delivery_key_secret[0].arn + }, + { + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ] + Effect = "Allow" + Resource = "arn:aws:logs:*:*:*" + } + ] + }) +} +resource "aws_secretsmanager_secret_version" "secret_string" { + count = var.create_secret ? 1 : 0 secret_id = aws_secretsmanager_secret.guardduty_delivery_key_secret[0].id secret_string = jsonencode({ "GuardDutyDeliveryKeyArn" : aws_kms_key.guardduty_delivery_key.arn @@ -135,4 +211,4 @@ resource "aws_secretsmanager_secret_policy" "guardduty_delivery_key_secret_polic count = var.create_secret ? 1 : 0 secret_arn = aws_secretsmanager_secret.guardduty_delivery_key_secret[0].arn policy = data.aws_iam_policy_document.secretsmanager_policy.json -} \ No newline at end of file +} diff --git a/aws_sra_examples/terraform/solutions/guard_duty/s3/main.tf b/aws_sra_examples/terraform/solutions/guard_duty/s3/main.tf index d04227998..3c610002f 100644 --- a/aws_sra_examples/terraform/solutions/guard_duty/s3/main.tf +++ b/aws_sra_examples/terraform/solutions/guard_duty/s3/main.tf @@ -4,10 +4,6 @@ ######################################################################## resource "aws_s3_bucket" "guardduty_delivery_bucket" { - #checkov:skip=CKV2_AWS_61: Ensure that an S3 bucket has a lifecycle configuration - #checkov:skip=CKV_AWS_18: Ensure the S3 bucket has access logging enabled - #checkov:skip=CKV2_AWS_62: Ensure S3 buckets should have event notifications enabled - #checkov:skip=CKV_AWS_144: Ensure that S3 bucket has cross-region replication enabled bucket = "${var.guardduty_org_delivery_bucket_prefix}-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}" force_destroy = true @@ -17,7 +13,6 @@ resource "aws_s3_bucket" "guardduty_delivery_bucket" { } resource "aws_s3_bucket_server_side_encryption_configuration" "guardduty_see" { - #checkov:skip=CKV2_AWS_67: Ensure AWS S3 bucket encrypted with Customer Managed Key (CMK) has regular rotation bucket = aws_s3_bucket.guardduty_delivery_bucket.id rule { @@ -25,6 +20,7 @@ resource "aws_s3_bucket_server_side_encryption_configuration" "guardduty_see" { kms_master_key_id = var.guardduty_org_delivery_kms_key_arn sse_algorithm = "aws:kms" } + bucket_key_enabled = true } } @@ -36,10 +32,9 @@ resource "aws_s3_bucket_versioning" "guardduty_versioning" { } resource "aws_s3_bucket_ownership_controls" "guardduty_ownership_control" { - #checkov:skip=CKV2_AWS_65: Ensure access control lists for S3 buckets are disabled bucket = aws_s3_bucket.guardduty_delivery_bucket.id rule { - object_ownership = "BucketOwnerPreferred" + object_ownership = "BucketOwnerEnforced" } } @@ -52,11 +47,122 @@ resource "aws_s3_bucket_public_access_block" "guardduty_public_access_block" { restrict_public_buckets = true } +resource "aws_s3_bucket_lifecycle_configuration" "guardduty_lifecycle" { + bucket = aws_s3_bucket.guardduty_delivery_bucket.id + + rule { + id = "guardduty-findings-cleanup" + status = "Enabled" + + transition { + days = 90 + storage_class = "STANDARD_IA" + } + + transition { + days = 180 + storage_class = "GLACIER" + } + + expiration { + days = 730 + } + } +} + +resource "aws_s3_bucket_logging" "guardduty_logging" { + bucket = aws_s3_bucket.guardduty_delivery_bucket.id + + target_bucket = aws_s3_bucket.guardduty_logs_bucket.id + target_prefix = "access-logs/" +} + +resource "aws_s3_bucket" "guardduty_logs_bucket" { + bucket = "${var.guardduty_org_delivery_bucket_prefix}-logs-${data.aws_caller_identity.current.account_id}-${data.aws_region.current.name}" + force_destroy = true + + tags = { + "sra-solution" = var.sra_solution_name + } +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "guardduty_logs_see" { + bucket = aws_s3_bucket.guardduty_logs_bucket.id + + rule { + apply_server_side_encryption_by_default { + kms_master_key_id = var.guardduty_org_delivery_kms_key_arn + sse_algorithm = "aws:kms" + } + bucket_key_enabled = true + } +} + +resource "aws_s3_bucket_versioning" "guardduty_logs_versioning" { + bucket = aws_s3_bucket.guardduty_logs_bucket.id + versioning_configuration { + status = "Enabled" + } +} + +resource "aws_s3_bucket_ownership_controls" "guardduty_logs_ownership_control" { + bucket = aws_s3_bucket.guardduty_logs_bucket.id + rule { + object_ownership = "BucketOwnerEnforced" + } +} + +resource "aws_s3_bucket_public_access_block" "guardduty_logs_public_access_block" { + bucket = aws_s3_bucket.guardduty_logs_bucket.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_lifecycle_configuration" "guardduty_logs_lifecycle" { + bucket = aws_s3_bucket.guardduty_logs_bucket.id + + rule { + id = "logs-cleanup" + status = "Enabled" + + expiration { + days = 365 + } + } +} + resource "aws_s3_bucket_policy" "guardduty_delivery_bucket_policy" { bucket = aws_s3_bucket.guardduty_delivery_bucket.id policy = data.aws_iam_policy_document.guardduty_delivery_bucket_policy.json } +resource "aws_s3_bucket_policy" "guardduty_logs_bucket_policy" { + bucket = aws_s3_bucket.guardduty_logs_bucket.id + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "DenyInsecureTransport" + Effect = "Deny" + Principal = "*" + Action = "s3:*" + Resource = [ + aws_s3_bucket.guardduty_logs_bucket.arn, + "${aws_s3_bucket.guardduty_logs_bucket.arn}/*" + ] + Condition = { + Bool = { + "aws:SecureTransport" = "false" + } + } + } + ] + }) +} + data "aws_iam_policy_document" "guardduty_delivery_bucket_policy" { statement { sid = "DenyPutObjectUnlessGuardDuty" diff --git a/aws_sra_examples/terraform/solutions/macie/delivery_kms_key/main.tf b/aws_sra_examples/terraform/solutions/macie/delivery_kms_key/main.tf index ac610b2de..2bc61f8ce 100644 --- a/aws_sra_examples/terraform/solutions/macie/delivery_kms_key/main.tf +++ b/aws_sra_examples/terraform/solutions/macie/delivery_kms_key/main.tf @@ -93,7 +93,7 @@ resource "aws_kms_alias" "macie_delivery_key_alias" { resource "aws_secretsmanager_secret" "macie_delivery_key_secret" { #checkov:skip=CKV_AWS_149: Ensure that Secrets Manager secret is encrypted using KMS CMK #checkov:skip=CKV2_AWS_57: Ensure Secrets Manager secrets should have automatic rotation enabled - + count = var.secrets_key_alias_arn != "" ? 1 : 0 name = "sra/macie_org_delivery_key_arn" description = "Macie Delivery KMS Key ARN" diff --git a/aws_sra_examples/terraform/solutions/providers.tf b/aws_sra_examples/terraform/solutions/providers.tf index 61b48dbc9..a3c9df137 100644 --- a/aws_sra_examples/terraform/solutions/providers.tf +++ b/aws_sra_examples/terraform/solutions/providers.tf @@ -4,56 +4,59 @@ ######################################################################## terraform { - # checkov:skip=CKV_TF_3:Ensure state files are locked + required_version = ">= 1.0.0" required_providers { - aws = ">= 5.1.0" + aws = { + source = "hashicorp/aws" + version = ">= 5.31.0" + } } - backend "s3" {} } provider "aws" { - alias = "management" + alias = "target" region = var.account_region - assume_role { - role_arn = "arn:aws:iam::${var.management_account_id}:role/sra-execution" - session_name = "Pipeline_Run" - } - default_tags { tags = { - Owner = "Security" + Owner = "Security" + Environment = "SRA" + Terraform = "true" } } } provider "aws" { - alias = "target" - region = var.account_region + alias = "management" + region = var.home_region assume_role { - role_arn = "arn:aws:iam::${var.account_id}:role/sra-execution" - session_name = "Pipeline_Run" + role_arn = "arn:${var.aws_partition}:iam::${var.management_account_id}:role/sra-execution" } + default_tags { tags = { - Owner = "Security" + Owner = "Security" + Environment = "SRA" + Terraform = "true" } } } provider "aws" { alias = "log_archive" - region = var.account_region + region = var.home_region assume_role { - role_arn = "arn:aws:iam::${var.log_archive_account_id}:role/sra-execution" - session_name = "Pipeline_Run" + role_arn = "arn:${var.aws_partition}:iam::${var.log_archive_account_id}:role/sra-execution" } + default_tags { tags = { - Owner = "Security" + Owner = "Security" + Environment = "SRA" + Terraform = "true" } } -} \ No newline at end of file +} diff --git a/aws_sra_examples/terraform/solutions/security_hub/configuration/variables.tf b/aws_sra_examples/terraform/solutions/security_hub/configuration/variables.tf index 766b5db93..63b0087a2 100644 --- a/aws_sra_examples/terraform/solutions/security_hub/configuration/variables.tf +++ b/aws_sra_examples/terraform/solutions/security_hub/configuration/variables.tf @@ -131,7 +131,7 @@ variable "lambda_log_group_retention" { type = number default = 365 validation { - condition = var.lambda_log_group_retention >= 365 + condition = var.lambda_log_group_retention >= 365 error_message = "Cloudwatch log group retention must be at least 365 days to meet CKV_AWS338 best practice." } } diff --git a/aws_sra_examples/terraform/solutions/security_hub/variables.tf b/aws_sra_examples/terraform/solutions/security_hub/variables.tf index 2d4083f09..6cb1362c4 100644 --- a/aws_sra_examples/terraform/solutions/security_hub/variables.tf +++ b/aws_sra_examples/terraform/solutions/security_hub/variables.tf @@ -41,7 +41,7 @@ variable "cis_standard_version" { validation { condition = contains(["NONE", "1.2.0", "1.4.0", "3.0.0"], var.cis_standard_version) error_message = "Valid values for cis_standard_version are NONE, 1.2.0, 1.4.0, or 3.0.0." - } + } } variable "compliance_frequency" { @@ -161,7 +161,7 @@ variable "lambda_log_group_retention" { type = number default = 365 validation { - condition = var.lambda_log_group_retention >= 365 + condition = var.lambda_log_group_retention >= 365 error_message = "Cloudwatch log group retention must be at least 365 days to meet CKV_AWS_338 best practice." } }