diff --git a/aws_sra_examples/solutions/config/config_org/lambda/src/app.py b/aws_sra_examples/solutions/config/config_org/lambda/src/app.py index 65fef0a0f..f71af8439 100644 --- a/aws_sra_examples/solutions/config/config_org/lambda/src/app.py +++ b/aws_sra_examples/solutions/config/config_org/lambda/src/app.py @@ -509,7 +509,10 @@ def orchestrator(event: Dict[str, Any], context: Any) -> None: event: event data context: runtime information """ - if event.get("RequestType"): + if event.get("Terraform"): + LOGGER.info("...calling terraform handler...") + process_event_cloudformation(event, context) + elif event.get("RequestType"): LOGGER.info("...calling helper...") helper(event, context) elif event.get("Records") and event["Records"][0]["EventSource"] == "aws:sns": diff --git a/aws_sra_examples/terraform/common/main.tf b/aws_sra_examples/terraform/common/main.tf index f6988e0f4..ad4dff5ba 100644 --- a/aws_sra_examples/terraform/common/main.tf +++ b/aws_sra_examples/terraform/common/main.tf @@ -123,7 +123,8 @@ resource "local_file" "config_file_creation" { enable_cloudtrail_org = false enable_iam_password_policy = false enable_inspector = false - + enable_config_org = false + ######################################################################## # Guard Duty Settings ######################################################################## diff --git a/aws_sra_examples/terraform/common/variables.tf b/aws_sra_examples/terraform/common/variables.tf index 2c23b10b7..65706f3f5 100644 --- a/aws_sra_examples/terraform/common/variables.tf +++ b/aws_sra_examples/terraform/common/variables.tf @@ -2,9 +2,15 @@ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # SPDX-License-Identifier: MIT-0 ######################################################################## +variable "account_region" { + type = string + description = "Default Account Region to deploy in" + default = "us-east-1" +} + variable "control_tower" { description = "AWS Control Tower landing zone deployed/in-use" - default = "true" + default = "false" } variable "governed_regions" { 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 d0153a966..20ca17c8c 100644 --- a/aws_sra_examples/terraform/solutions/cloudtrail_org/s3/main.tf +++ b/aws_sra_examples/terraform/solutions/cloudtrail_org/s3/main.tf @@ -144,7 +144,7 @@ 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" diff --git a/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/data.tf b/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/data.tf new file mode 100644 index 000000000..97f34e1ef --- /dev/null +++ b/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/data.tf @@ -0,0 +1,9 @@ +######################################################################## +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +######################################################################## + +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} +data "aws_organizations_organization" "current" {} \ No newline at end of file diff --git a/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/main.tf b/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/main.tf new file mode 100644 index 000000000..1f2b81c1f --- /dev/null +++ b/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/main.tf @@ -0,0 +1,36 @@ +######################################################################## +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +######################################################################## +resource "aws_iam_role" "r_config_aggregator_role" { + name = var.p_aggregator_role_name + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = "sts:AssumeRole" + Principal = { + Service = "config.amazonaws.com" + } + } + ] + }) + + managed_policy_arns = [ + "arn:aws:iam::aws:policy/service-role/AWSConfigRoleForOrganizations" + ] + + tags = { + "${var.p_sra_solution_name_key}" = var.p_sra_solution_name + } +} + +resource "aws_config_configuration_aggregator" "r_organization_config_aggregator" { + name = var.p_aggregator_name + + account_aggregation_source { + account_ids = [data.aws_caller_identity.current.account_id] + all_regions = true + } +} \ No newline at end of file diff --git a/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/providers.tf b/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/providers.tf new file mode 100644 index 000000000..226f1ca46 --- /dev/null +++ b/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/providers.tf @@ -0,0 +1,13 @@ +######################################################################## +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +######################################################################## + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 5.1.0" + } + } +} \ No newline at end of file diff --git a/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/variables.tf b/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/variables.tf new file mode 100644 index 000000000..d8bb029ba --- /dev/null +++ b/aws_sra_examples/terraform/solutions/config_org/aggregator_org_configuration/variables.tf @@ -0,0 +1,27 @@ +######################################################################## +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +######################################################################## +variable "p_aggregator_name" { + type = string + description = "Config Aggregator Name" + default = "sra-config-aggregator-org" +} + +variable "p_aggregator_role_name" { + type = string + description = "Config Aggregator Role Name" + default = "sra-config-aggregator-org" +} + +variable "p_sra_solution_name" { + type = string + description = "The SRA solution name. The default value is the folder name of the solution" + default = "sra-config-aggregator-org" +} + +variable "p_sra_solution_name_key" { + type = string + description = "The key used for tagging resources with the SRA solution name." + default = "sra-solution" +} \ No newline at end of file diff --git a/aws_sra_examples/terraform/solutions/config_org/configuration/data.tf b/aws_sra_examples/terraform/solutions/config_org/configuration/data.tf new file mode 100644 index 000000000..97f34e1ef --- /dev/null +++ b/aws_sra_examples/terraform/solutions/config_org/configuration/data.tf @@ -0,0 +1,9 @@ +######################################################################## +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +######################################################################## + +data "aws_partition" "current" {} +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} +data "aws_organizations_organization" "current" {} \ No newline at end of file diff --git a/aws_sra_examples/terraform/solutions/config_org/configuration/invoke.tf b/aws_sra_examples/terraform/solutions/config_org/configuration/invoke.tf new file mode 100644 index 000000000..d82ae53af --- /dev/null +++ b/aws_sra_examples/terraform/solutions/config_org/configuration/invoke.tf @@ -0,0 +1,31 @@ +######################################################################## +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +######################################################################## + +resource "aws_lambda_invocation" "new_lambda_invoke" { + function_name = aws_lambda_function.r_config_org_lambda_function.function_name + + input = jsonencode({ + "Terraform" : "true", + "RequestType" : "Create", + "ResourceType" : "Custom::LambdaCustomResource", + "ResourceProperties" : { + "ServiceToken" : "${aws_lambda_function.r_config_org_lambda_function.arn}", + "AUDIT_ACCOUNT" : "${var.p_audit_account_id}", + "CONFIGURATION_ROLE_NAME" : "${var.p_config_configuration_role_name}", + "CONTROL_TOWER_REGIONS_ONLY" : "${var.p_control_tower_regions_only}", + "ENABLED_REGIONS" : "${var.p_enabled_regions}", + "ALL_SUPPORTED" : "${var.p_all_supported}", + "INCLUDE_GLOBAL_RESOURCE_TYPES" : "${var.p_include_global_resource_types}", + "DELIVERY_CHANNEL_NAME" : "${var.p_delivery_channel_name}", + "FREQUENCY" : "${var.p_frequency}", + "RESOURCE_TYPES" : "${var.p_resource_types}", + "RECORDER_NAME" : "${var.p_recorder_name}", + "KMS_KEY_SECRET_NAME" : "${var.p_kms_key_arn_secret_name}", + "HOME_REGION" : "${var.p_home_region}", + "SNS_TOPIC_ARN_FANOUT" : "${aws_sns_topic.r_config_org_topic.arn}", + "PUBLISHING_DESTINATION_BUCKET_ARN" : "arn:aws:s3:::${var.p_publishing_destination_bucket_name}" + } + }) +} \ No newline at end of file diff --git a/aws_sra_examples/terraform/solutions/config_org/configuration/main.tf b/aws_sra_examples/terraform/solutions/config_org/configuration/main.tf new file mode 100644 index 000000000..ee2978eeb --- /dev/null +++ b/aws_sra_examples/terraform/solutions/config_org/configuration/main.tf @@ -0,0 +1,479 @@ +######################################################################## +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: MIT-0 +######################################################################## +locals { + graviton_regions = [ + "ap-northeast-1", + "ap-south-1", + "ap-southeast-1", + "ap-southeast-2", + "eu-central-1", + "eu-west-1", + "eu-west-2", + "us-east-1", + "us-east-2", + "us-west-2", + ] + account_id = data.aws_caller_identity.current.account_id + region = data.aws_region.current.name + src_path = "${path.root}/../../solutions/config/config_org/lambda/src/" + + compliance_frequency_single_day = var.p_compliance_frequency == 1 + create_dlq_alarm = var.p_sra_alarm_email != "" + create_lambda_log_group = var.p_create_lambda_log_group == "true" + is_all_supported = var.p_all_supported == "true" + use_graviton = contains(local.graviton_regions, data.aws_region.current.name) + use_kms_key = var.p_lambda_log_group_kms_key != "" + not_global_region_us_east_1 = var.p_current_region != "us-east-1" +} + +resource "aws_cloudwatch_log_group" "r_config_org_lambda_log_group" { + count = local.create_lambda_log_group ? 1 : 0 + name = "/aws/lambda/${var.p_config_org_lambda_function_name}" + retention_in_days = var.p_lambda_log_group_retention + kms_key_id = local.use_kms_key != "" ? var.p_lambda_log_group_kms_key : null + lifecycle { + prevent_destroy = true + } +} + +######################################################################## +# Lambda Policies Documents +######################################################################## +resource "aws_iam_role" "r_config_org_lambda_role" { + name = var.p_config_org_lambda_role_name + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = ["lambda.amazonaws.com"] + } + } + ] + }) +} + +resource "aws_iam_policy" "sra_config_org_policy_organizations" { + name = "sra-config-org-policy-organizations" + description = "IAM policy for Macie Org Lambda Organizations" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Sid = "OrganizationsReadAccess" + Effect = "Allow" + Action = [ + "organizations:DescribeAccount", + "organizations:ListAccounts" + ] + Resource = "*" + } + ] + }) +} + +resource "aws_iam_policy" "ssm_access" { + name = "ssm-access" + description = "IAM policy for SSM access" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow", + Action = [ + "ssm:GetParameter", + "ssm:GetParameters" + ], + Resource = [ + "arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter/sra*" + ] + } + ] + }) +} + +resource "aws_iam_policy" "sra_config_org_policy_sns" { + name = "sra-config-org-policy-sns" + description = "IAM policy for SNS access" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Sid = "SNSPublish" + Effect = "Allow" + Action = [ + "sns:Publish", + "sns:PublishBatch" + ], + Resource = aws_sns_topic.r_config_org_topic.arn + } + ] + }) +} + +resource "aws_iam_policy" "sra_config_org_policy_iam" { + name = "sra-config-org-policy-iam-lambda" + description = "IAM policy for IAM access" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Sid = "AssumeRole" + Effect = "Allow" + Action = ["sts:AssumeRole"] + Condition = { + StringEquals = { + "aws:PrincipalOrgId" = var.p_organization_id + } + }, + Resource = [ + "arn:aws:iam::*:role/${var.p_config_configuration_role_name}" + ] + }, + { + Sid = "AllowReadIamActions" + Effect = "Allow" + Action = ["iam:GetRole"] + Resource = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/*" + }, + { + Sid = "AllowCreateServiceLinkedRole" + Effect = "Allow" + Action = ["iam:CreateServiceLinkedRole"] + Condition = { + StringLike = { + "iam:AWSServiceName" = "config.amazonaws.com" + } + }, + Resource = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig" + }, + { + Sid = "AllowPolicyActions" + Effect = "Allow" + Action = ["iam:PutRolePolicy"] + Resource = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig" + } + ] + }) +} + +resource "aws_iam_policy" "sra_config_org_policy_logs" { + name = "sra-config-org-policy-logs" + description = "IAM policy for logs access" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Sid = "CreateLogGroupAndEvents" + Effect = "Allow" + Action = [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + Resource = "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:/aws/lambda/${var.p_config_org_lambda_function_name}:log-stream:*" + } + ] + }) +} + +resource "aws_iam_policy" "sra_config_org_policy_sqs" { + name = "sra-config-org-policy-sqs" + description = "IAM policy for SQS access" + policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Sid = "SQSSendMessage" + Effect = "Allow" + Action = ["sqs:SendMessage"], + Resource = aws_sqs_queue.r_config_org_dlq.arn + } + ] + }) +} + +######################################################################## +# Policy Attachment +######################################################################## +resource "aws_iam_role_policy_attachment" "r_config_org_lambda_role_organizations" { + role = aws_iam_role.r_config_org_lambda_role.name + policy_arn = aws_iam_policy.sra_config_org_policy_organizations.arn +} + +resource "aws_iam_role_policy_attachment" "r_config_org_lambda_role_ssm_access" { + role = aws_iam_role.r_config_org_lambda_role.name + policy_arn = aws_iam_policy.ssm_access.arn +} + +resource "aws_iam_role_policy_attachment" "r_config_org_lambda_role_sns" { + role = aws_iam_role.r_config_org_lambda_role.name + policy_arn = aws_iam_policy.sra_config_org_policy_sns.arn +} + +resource "aws_iam_role_policy_attachment" "r_config_org_lambda_role_iam" { + role = aws_iam_role.r_config_org_lambda_role.name + policy_arn = aws_iam_policy.sra_config_org_policy_iam.arn +} + +resource "aws_iam_role_policy_attachment" "r_config_org_lambda_role_logs" { + role = aws_iam_role.r_config_org_lambda_role.name + policy_arn = aws_iam_policy.sra_config_org_policy_logs.arn +} + +resource "aws_iam_role_policy_attachment" "r_config_org_lambda_role_sqs" { + role = aws_iam_role.r_config_org_lambda_role.name + policy_arn = aws_iam_policy.sra_config_org_policy_sqs.arn +} + +######################################################################## +# Lambda Function +######################################################################## +data "archive_file" "hash_check" { + type = "zip" + source_dir = local.src_path + output_path = "${path.module}/lambda/lambda_function.zip" + excludes = ["lambda_function.zip, data.zip"] +} + +resource "null_resource" "package_lambda" { + triggers = { + src_hash = "${data.archive_file.hash_check.output_sha}" + } + + provisioner "local-exec" { + command = <