diff --git a/README.md b/README.md index 938501f..95a80e9 100644 --- a/README.md +++ b/README.md @@ -504,14 +504,13 @@ module "landing_zone" { | [aws_s3_account_public_access_block.master](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/s3_account_public_access_block) | resource | | [aws_securityhub_account.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_account) | resource | | [aws_securityhub_account.management](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_account) | resource | +| [aws_securityhub_configuration_policy.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_configuration_policy) | resource | +| [aws_securityhub_configuration_policy_association.root](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_configuration_policy_association) | resource | +| [aws_securityhub_finding_aggregator.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_finding_aggregator) | resource | | [aws_securityhub_member.logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_member) | resource | | [aws_securityhub_member.management](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_member) | resource | | [aws_securityhub_organization_admin_account.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_organization_admin_account) | resource | | [aws_securityhub_organization_configuration.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_organization_configuration) | resource | -| [aws_securityhub_product_subscription.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_product_subscription) | resource | -| [aws_securityhub_standards_subscription.default](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_standards_subscription) | resource | -| [aws_securityhub_standards_subscription.logging](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_standards_subscription) | resource | -| [aws_securityhub_standards_subscription.management](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/securityhub_standards_subscription) | resource | | [aws_sns_topic.iam_activity](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | | [aws_sns_topic.security_hub_findings](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic) | resource | | [aws_sns_topic_policy.iam_activity](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/sns_topic_policy) | resource | @@ -538,18 +537,19 @@ module "landing_zone" { | Name | Description | Type | Default | Required | |------|-------------|------|---------|:--------:| +| [allowed\_regions](#input\_allowed\_regions) | List of AWS regions where operations are allowed and for which central services like Security Hub and AWS Config are configured. | `list(string)` | n/a | yes | | [control\_tower\_account\_ids](#input\_control\_tower\_account\_ids) | Control Tower core account IDs |
object({| n/a | yes | | [tags](#input\_tags) | Map of tags | `map(string)` | n/a | yes | | [additional\_auditing\_trail](#input\_additional\_auditing\_trail) | CloudTrail configuration for additional auditing trail |
audit = string
logging = string
})
object({| `null` | no | | [aws\_account\_password\_policy](#input\_aws\_account\_password\_policy) | AWS account password policy parameters for the audit, logging and master account |
name = string
bucket = string
kms_key_id = string
event_selector = optional(object({
data_resource = optional(object({
type = string
values = list(string)
}))
exclude_management_event_sources = optional(set(string), null)
include_management_events = optional(bool, true)
read_write_type = optional(string, "All")
}))
})
object({|
allow_users_to_change = bool
max_age = number
minimum_length = number
require_lowercase_characters = bool
require_numbers = bool
require_symbols = bool
require_uppercase_characters = bool
reuse_prevention_history = number
})
{| no | | [aws\_auditmanager](#input\_aws\_auditmanager) | AWS Audit Manager config settings |
"allow_users_to_change": true,
"max_age": 90,
"minimum_length": 14,
"require_lowercase_characters": true,
"require_numbers": true,
"require_symbols": true,
"require_uppercase_characters": true,
"reuse_prevention_history": 24
}
object({|
enabled = bool
reports_bucket_prefix = string
})
{| no | -| [aws\_config](#input\_aws\_config) | AWS Config settings |
"enabled": true,
"reports_bucket_prefix": "audit-manager-reports"
}
object({|
aggregator_account_ids = optional(list(string), [])
aggregator_regions = optional(list(string), [])
delivery_channel_s3_bucket_name = optional(string, null)
delivery_channel_s3_key_prefix = optional(string, null)
delivery_frequency = optional(string, "TwentyFour_Hours")
rule_identifiers = optional(list(string), [])
})
{| no | +| [aws\_config](#input\_aws\_config) | AWS Config settings |
"aggregator_account_ids": [],
"aggregator_regions": [],
"delivery_channel_s3_bucket_name": null,
"delivery_channel_s3_key_prefix": null,
"delivery_frequency": "TwentyFour_Hours",
"rule_identifiers": []
}
object({|
aggregator_account_ids = optional(list(string), [])
delivery_channel_s3_bucket_name = optional(string, null)
delivery_channel_s3_key_prefix = optional(string, null)
delivery_frequency = optional(string, "TwentyFour_Hours")
rule_identifiers = optional(list(string), [])
})
{| no | | [aws\_config\_sns\_subscription](#input\_aws\_config\_sns\_subscription) | Subscription options for the aws-controltower-AggregateSecurityNotifications (AWS Config) SNS topic |
"aggregator_account_ids": [],
"delivery_channel_s3_bucket_name": null,
"delivery_channel_s3_key_prefix": null,
"delivery_frequency": "TwentyFour_Hours",
"rule_identifiers": []
}
map(object({| `{}` | no | | [aws\_ebs\_encryption\_by\_default](#input\_aws\_ebs\_encryption\_by\_default) | Set to true to enable AWS Elastic Block Store encryption by default | `bool` | `true` | no | | [aws\_guardduty](#input\_aws\_guardduty) | AWS GuardDuty settings |
endpoint = string
protocol = string
}))
object({| `{}` | no | | [aws\_inspector](#input\_aws\_inspector) | AWS Inspector settings, at least one of the scan options must be enabled |
enabled = optional(bool, true)
finding_publishing_frequency = optional(string, "FIFTEEN_MINUTES")
ebs_malware_protection_status = optional(bool, true)
eks_audit_logs_status = optional(bool, true)
lambda_network_logs_status = optional(bool, true)
rds_login_events_status = optional(bool, true)
s3_data_events_status = optional(bool, true)
runtime_monitoring_status = optional(object({
enabled = optional(bool, true)
eks_addon_management_status = optional(bool, true)
ecs_fargate_agent_management_status = optional(bool, true)
ec2_agent_management_status = optional(bool, true)
}), {})
})
object({|
enabled = optional(bool, false)
enable_scan_ec2 = optional(bool, true)
enable_scan_ecr = optional(bool, true)
enable_scan_lambda = optional(bool, true)
enable_scan_lambda_code = optional(bool, true)
resource_create_timeout = optional(string, "15m")
})
{| no | | [aws\_required\_tags](#input\_aws\_required\_tags) | AWS Required tags settings |
"enable_scan_ec2": true,
"enable_scan_ecr": true,
"enable_scan_lambda": true,
"enable_scan_lambda_code": true,
"enabled": false,
"resource_create_timeout": "15m"
}
map(list(object({| `null` | no | -| [aws\_security\_hub](#input\_aws\_security\_hub) | AWS Security Hub settings |
name = string
values = optional(list(string))
enforced_for = optional(list(string))
})))
object({|
auto_enable_controls = optional(bool, true)
auto_enable_default_standards = optional(bool, false)
auto_enable_new_accounts = optional(bool, true)
control_finding_generator = optional(string, "SECURITY_CONTROL")
create_cis_metric_filters = optional(bool, true)
product_arns = optional(list(string), [])
standards_arns = optional(list(string), null)
})
{| no | +| [aws\_security\_hub](#input\_aws\_security\_hub) | AWS Security Hub settings |
"auto_enable_controls": true,
"auto_enable_default_standards": false,
"auto_enable_new_accounts": true,
"control_finding_generator": "SECURITY_CONTROL",
"create_cis_metric_filters": true,
"product_arns": [],
"standards_arns": null
}
object({| `{}` | no | | [aws\_security\_hub\_sns\_subscription](#input\_aws\_security\_hub\_sns\_subscription) | Subscription options for the LandingZone-SecurityHubFindings SNS topic |
aggregator_linking_mode = optional(string, "SPECIFIED_REGIONS")
auto_enable_controls = optional(bool, true)
control_finding_generator = optional(string, "SECURITY_CONTROL")
create_cis_metric_filters = optional(bool, true)
product_arns = optional(list(string), [])
standards_arns = optional(list(string), null)
disabled_control_identifiers = optional(list(string), [])
})
map(object({| `{}` | no | | [aws\_service\_control\_policies](#input\_aws\_service\_control\_policies) | AWS SCP's parameters to disable required/denied policies, set a list of allowed AWS regions, and set principals that are exempt from the restriction |
endpoint = string
protocol = string
}))
object({| `{}` | no | | [aws\_sso\_permission\_sets](#input\_aws\_sso\_permission\_sets) | Map of AWS IAM Identity Center permission sets with AWS accounts and group names that should be granted access to each account |
allowed_regions = optional(list(string), [])
aws_deny_disabling_security_hub = optional(bool, true)
aws_deny_leaving_org = optional(bool, true)
aws_deny_root_user_ous = optional(list(string), [])
aws_require_imdsv2 = optional(bool, true)
principal_exceptions = optional(list(string), [])
})
map(object({| `{}` | no | diff --git a/UPGRADING.md b/UPGRADING.md index 79fa1f8..8b0eb76 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -2,6 +2,25 @@ This document captures required refactoring on your part when upgrading to a module version that contains breaking changes. +## Upgrading to v5.0.0 + +### Behaviour + +> [!IMPORTANT] +> **This version changes the [Security Hub configuration to Central](https://docs.aws.amazon.com/securityhub/latest/userguide/central-configuration-intro.html).** + +This version enables Security Hub Findings Aggregation for all regions specfied in `allowed_regions`. You can change this behauviour by setting `var.aws_security_hub.aggregator_linking_mode` to `ALL_REGIONS_EXCEPT_SPECIFIED` and providing the list of regions via `var.aws_security_hub.aggregator_specified_regions`. + + +### Variables + +The following variables have been replaced: +* `aws_service_control_policies.allowed_regions` -> `allowed_regions` +* `aws_config.aggregator_regions` -> `allowed_regions` + +The following variable is added: +* `aws_security_hub.disabled_control_identifiers`. List of Security Hub control IDs that are disabled in the organisation. + ## Upgrading to v4.0.0 > [!WARNING] diff --git a/config.tf b/config.tf index 17f8171..203705e 100644 --- a/config.tf +++ b/config.tf @@ -1,7 +1,7 @@ locals { aws_config_aggregators = flatten([ for account in toset(try(var.aws_config.aggregator_account_ids, [])) : [ - for region in toset(try(var.aws_config.aggregator_regions, [])) : { + for region in toset(try(local.allowed_regions_with_us_east, [])) : { account_id = account region = region } @@ -32,7 +32,7 @@ resource "aws_config_aggregate_authorization" "master" { } resource "aws_config_aggregate_authorization" "master_to_audit" { - for_each = toset(coalescelist(var.aws_config.aggregator_regions, [data.aws_region.current.name])) + for_each = local.allowed_regions_with_us_east account_id = var.control_tower_account_ids.audit region = each.value diff --git a/locals.tf b/locals.tf index 0727e22..d6d9659 100644 --- a/locals.tf +++ b/locals.tf @@ -34,4 +34,8 @@ locals { security_hub_has_cis_aws_foundations_enabled = length(regexall( "cis-aws-foundations-benchmark/v", join(",", local.security_hub_standards_arns) )) > 0 ? true : false + + allowed_regions = toset(distinct(concat(var.allowed_regions, [data.aws_region.current.name]))) + allowed_regions_with_us_east = toset(distinct(concat(var.allowed_regions, [data.aws_region.current.name], ["us-east-1"]))) + allowed_regions_except_home_region = setsubtract(local.allowed_regions_with_us_east, [data.aws_region.current.name]) } diff --git a/organizations_policy.tf b/organizations_policy.tf index 3f2d3d3..8803971 100644 --- a/organizations_policy.tf +++ b/organizations_policy.tf @@ -1,9 +1,9 @@ locals { enabled_root_policies = { allowed_regions = { - enable = var.aws_service_control_policies.allowed_regions != null ? true : false - policy = var.aws_service_control_policies.allowed_regions != null ? templatefile("${path.module}/files/organizations/allowed_regions.json.tpl", { - allowed = var.aws_service_control_policies.allowed_regions != null ? var.aws_service_control_policies.allowed_regions : [] + enable = var.allowed_regions != null ? true : false + policy = local.allowed_regions != null ? templatefile("${path.module}/files/organizations/allowed_regions.json.tpl", { + allowed = var.allowed_regions != null ? local.allowed_regions : [] exceptions = local.aws_service_control_policies_principal_exceptions }) : null } diff --git a/security_hub.tf b/security_hub.tf index 5bbeb76..083837a 100644 --- a/security_hub.tf +++ b/security_hub.tf @@ -23,14 +23,6 @@ resource "aws_securityhub_member" "management" { } } -resource "aws_securityhub_standards_subscription" "management" { - for_each = toset(local.security_hub_standards_arns) - - standards_arn = each.value - - depends_on = [aws_securityhub_account.default] -} - // AWS Security Hub - Audit account configuration and enrollment resource "aws_securityhub_account" "default" { provider = aws.audit @@ -41,29 +33,16 @@ resource "aws_securityhub_account" "default" { resource "aws_securityhub_organization_configuration" "default" { provider = aws.audit - auto_enable = var.aws_security_hub.auto_enable_new_accounts - auto_enable_standards = var.aws_security_hub.auto_enable_default_standards ? "DEFAULT" : "NONE" - - depends_on = [aws_securityhub_organization_admin_account.default] -} - -resource "aws_securityhub_product_subscription" "default" { - for_each = toset(var.aws_security_hub.product_arns) - provider = aws.audit + auto_enable = false + auto_enable_standards = "NONE" - product_arn = each.value + organization_configuration { + configuration_type = "CENTRAL" + } - depends_on = [aws_securityhub_account.default] + depends_on = [aws_securityhub_organization_admin_account.default, aws_securityhub_finding_aggregator.default] } -resource "aws_securityhub_standards_subscription" "default" { - for_each = toset(local.security_hub_standards_arns) - provider = aws.audit - - standards_arn = each.value - - depends_on = [aws_securityhub_account.default] -} resource "aws_cloudwatch_event_rule" "security_hub_findings" { provider = aws.audit @@ -126,10 +105,38 @@ resource "aws_securityhub_member" "logging" { depends_on = [aws_securityhub_organization_configuration.default] } -resource "aws_securityhub_standards_subscription" "logging" { - for_each = toset(local.security_hub_standards_arns) - provider = aws.logging - standards_arn = each.value - depends_on = [aws_securityhub_account.default] +resource "aws_securityhub_finding_aggregator" "default" { + count = length(local.allowed_regions_except_home_region) == 0 ? 0 : 1 + provider = aws.audit + + linking_mode = var.aws_security_hub.aggregator_linking_mode + specified_regions = var.aws_security_hub.aggregator_linking_mode == "SPECIFIED_REGIONS" ? local.allowed_regions_except_home_region : null + + depends_on = [aws_securityhub_account.default] +} + +resource "aws_securityhub_configuration_policy" "default" { + provider = aws.audit + + name = "mcaf-lz" + description = "MCAF Landing Zone default configuration policy" + + configuration_policy { + service_enabled = true + enabled_standard_arns = local.security_hub_standards_arns + + security_controls_configuration { + disabled_control_identifiers = var.aws_security_hub.disabled_control_identifiers + } + } + + depends_on = [aws_securityhub_organization_configuration.default] +} + +resource "aws_securityhub_configuration_policy_association" "root" { + provider = aws.audit + + target_id = data.aws_organizations_organization.default.roots[0].id + policy_id = aws_securityhub_configuration_policy.default.id } diff --git a/variables.tf b/variables.tf index 9bd2c79..05266d0 100644 --- a/variables.tf +++ b/variables.tf @@ -18,6 +18,11 @@ variable "additional_auditing_trail" { description = "CloudTrail configuration for additional auditing trail" } +variable "allowed_regions" { + type = list(string) + description = "List of AWS regions where operations are allowed and for which central services like Security Hub and AWS Config are configured." +} + variable "aws_account_password_policy" { type = object({ allow_users_to_change = bool @@ -57,7 +62,6 @@ variable "aws_auditmanager" { variable "aws_config" { type = object({ aggregator_account_ids = optional(list(string), []) - aggregator_regions = optional(list(string), []) delivery_channel_s3_bucket_name = optional(string, null) delivery_channel_s3_key_prefix = optional(string, null) delivery_frequency = optional(string, "TwentyFour_Hours") @@ -65,7 +69,6 @@ variable "aws_config" { }) default = { aggregator_account_ids = [] - aggregator_regions = [] delivery_channel_s3_bucket_name = null delivery_channel_s3_key_prefix = null delivery_frequency = "TwentyFour_Hours" @@ -151,29 +154,26 @@ variable "aws_required_tags" { variable "aws_security_hub" { type = object({ - auto_enable_controls = optional(bool, true) - auto_enable_default_standards = optional(bool, false) - auto_enable_new_accounts = optional(bool, true) - control_finding_generator = optional(string, "SECURITY_CONTROL") - create_cis_metric_filters = optional(bool, true) - product_arns = optional(list(string), []) - standards_arns = optional(list(string), null) + aggregator_linking_mode = optional(string, "SPECIFIED_REGIONS") + auto_enable_controls = optional(bool, true) + control_finding_generator = optional(string, "SECURITY_CONTROL") + create_cis_metric_filters = optional(bool, true) + product_arns = optional(list(string), []) + standards_arns = optional(list(string), null) + disabled_control_identifiers = optional(list(string), []) }) - default = { - auto_enable_controls = true - auto_enable_default_standards = false - auto_enable_new_accounts = true - control_finding_generator = "SECURITY_CONTROL" - create_cis_metric_filters = true - product_arns = [] - standards_arns = null - } + default = {} description = "AWS Security Hub settings" validation { condition = contains(["SECURITY_CONTROL", "STANDARD_CONTROL"], var.aws_security_hub.control_finding_generator) error_message = "The \"control_finding_generator\" variable must be set to either \"SECURITY_CONTROL\" or \"STANDARD_CONTROL\"." } + + validation { + condition = var.aws_security_hub.aggregator_linking_mode != "ALL_REGIONS" + error_message = "Security Hub Linking mode cannot be set to \"ALL_REGIONS\" since AWS Config needs to be configured in all regions individually." + } } variable "aws_security_hub_sns_subscription" {
assignments = list(map(list(string)))
inline_policy = optional(string, null)
managed_policy_arns = optional(list(string), [])
session_duration = optional(string, "PT4H")
}))