diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f2e5ebb..90628a2 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,7 +9,7 @@ fail_fast: False repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 # Get the latest from: https://github.com/pre-commit/pre-commit-hooks/releases + rev: v5.0.0 # Get the latest from: https://github.com/pre-commit/pre-commit-hooks/releases hooks: - id: check-added-large-files - id: check-case-conflict @@ -39,12 +39,12 @@ repos: stages: [commit-msg] - repo: https://github.com/asottile/add-trailing-comma - rev: v2.5.1 # Get the latest from: https://github.com/asottile/add-trailing-comma + rev: v3.1.0 # Get the latest from: https://github.com/asottile/add-trailing-comma hooks: - id: add-trailing-comma - repo: https://github.com/gruntwork-io/pre-commit - rev: v0.1.22 # Get the latest from: https://github.com/gruntwork-io/pre-commit/releases + rev: v0.1.24 # Get the latest from: https://github.com/gruntwork-io/pre-commit/releases hooks: - id: terraform-validate - id: terraform-fmt @@ -53,7 +53,7 @@ repos: - id: golint - repo: https://github.com/antonbabenko/pre-commit-terraform - rev: v1.81.0 # Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases + rev: v1.96.2 # Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases hooks: - id: terraform_fmt - id: terraform_tflint @@ -66,7 +66,7 @@ repos: - id: terraform_checkov - repo: https://github.com/ambv/black - rev: 23.3.0 # Get the latest from: https://github.com/psf/black/releases + rev: 24.10.0 # Get the latest from: https://github.com/psf/black/releases hooks: - id: black language_version: python3 diff --git a/.tflint.hcl b/.tflint.hcl index 9073e00..d3923a0 100644 --- a/.tflint.hcl +++ b/.tflint.hcl @@ -1,6 +1,6 @@ plugin "aws" { enabled = true - version = "0.20.0" + version = "0.36.0" source = "github.com/terraform-linters/tflint-ruleset-aws" deep_check = false } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5af276d..d1e5afd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2.0.0] - 2024-12-19 +### Breaking Changes +- Migration from regional to global API identifiers + ## [1.0.0] - 2023-06-22 ### Added - Initial public release diff --git a/README.md b/README.md index 3cfe1b7..35fd4a0 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ - [AWS Prescriptive Guidance](#aws-prescriptive-guidance) - [Goal](#goal) -- [Prerequisites and limitations](#prerequisites-and-limitations) +- [Prerequisites and Limitations](#prerequisites-and-limitations) - [Architecture](#architecture) - [Tools](#tools) - [Best practices](#best-practices) @@ -24,24 +24,24 @@ For a complete guide, prerequisites and instructions for using this AWS Prescrip ## Goal -This pattern describes how to use AWS Control Tower controls, HashiCorp Terraform, and infrastructure as code (IaC) to implement and administer preventive, detective, and proactive security controls. A control (also known as a guardrail) is a high-level rule that provides ongoing governance for your overall AWS Control Tower environment. For example, you can use controls to require logging for your AWS accounts and then configure automatic notifications if specific security-related events occur. +This pattern describes how to use AWS Control Tower controls, HashiCorp Terraform, and infrastructure as code (IaC) to implement and administer preventive, detective, and proactive security controls. A [control](https://docs.aws.amazon.com/controltower/latest/userguide/controls.html) (also known as a guardrail) is a high-level rule that provides ongoing governance for your overall AWS Control Tower environment. For example, you can use controls to require logging for your AWS accounts and then configure automatic notifications if specific security-related events occur. AWS Control Tower helps you implement preventive, detective, and proactive controls that govern your AWS resources and monitor compliance across multiple AWS accounts. Each control enforces a single rule. In this pattern, you use a provided IaC template to specify which controls you want to deploy in your environment. -AWS Control Tower controls apply to an entire organizational unit (OU), and the control affects every AWS account within the OU. Therefore, when users perform any action in any account in your landing zone, the action is subject to the controls that govern the OU. +AWS Control Tower controls apply to an entire [organizational unit (OU)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_getting-started_concepts.html#organizationalunit), and the control affects every AWS account within the OU. Therefore, when users perform any action in any account in your landing zone, the action is subject to the controls that govern the OU. Implementing AWS Control Tower controls helps establish a strong security foundation for your AWS landing zone. By using this pattern to deploy the controls as IaC through Terraform, you can standardize the controls in your landing zone and more efficiently deploy and manage them. -### Target Audience +### Intended Audience This pattern is recommended for users who have experience with AWS Control Tower, Terraform, and AWS Organizations. -## Prerequisites and limitations +## Prerequisites and Limitations ### Prerequisites -- Active AWS accounts managed as an organization in AWS Organizations and an AWS Control Tower landing zone. For instructions, see [Create an account structure](https://www.wellarchitectedlabs.com/cost/100_labs/100_1_aws_account_setup/2_account_structure/) (AWS Well-Architected Labs). +- Active AWS accounts managed as an organization in AWS Organizations and an AWS Control Tower landing zone. For instructions, see [Getting started](https://docs.aws.amazon.com/controltower/latest/userguide/getting-started-with-control-tower.html) in the AWS Control Tower documentation. - AWS Command Line Interface (AWS CLI), [installed](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) and [configured](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html). @@ -57,6 +57,14 @@ This pattern is recommended for users who have experience with AWS Control Tower - Terraform backend, [configured](https://developer.hashicorp.com/terraform/language/settings/backends/configuration#using-a-backend-block) (Terraform documentation). +### Limitations + +- For AWS Control Tower controls, this pattern requires the use of [global identifiers](https://docs.aws.amazon.com/controltower/latest/controlreference/all-global-identifiers.html) that are in the following format: +`arn::controlcatalog:::control/` +**Note:** In most cases, the value for `` is aws. +Previous versions of this pattern used [regional identifiers](https://docs.aws.amazon.com/controltower/latest/controlreference/control-metadata-tables.html) that are no longer supported. We recommend that you migrate from regional identifiers to global identifiers. Global identifiers help you manage controls and expand the number of controls you can use. + + ## Architecture This section provides a high-level overview of this solution and the architecture established by the sample code. The following diagram shows controls deployed across the various accounts in the OU. @@ -67,7 +75,7 @@ AWS Control Tower controls are categorized according to their behavior and their There are three primary types of control behaviors: -1. Preventive controls are designed to prevent actions from occurring. These are implemented with [service control policies (SCPs)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html) in AWS Organizations. The status of a preventive control is either enforced or not enabled. Preventive controls are supported in all AWS Regions. +1. Preventive controls are designed to prevent actions from occurring. These are implemented with [service control policies (SCPs)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_scps.html) or [resource control policies (RCPs)](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_rcps.html) in AWS Organizations. The status of a preventive control is either enforced or not enabled. Preventive controls are supported in all AWS Regions. 2. Detective controls are designed to detect specific events when they occur and log the action in CloudTrail. These are implemented with AWS [Config rules](https://docs.aws.amazon.com/config/latest/developerguide/evaluate-config.html). The status of a detective control is either clear, in violation, or not enabled. Detective controls apply only in those AWS Regions supported by AWS Control Tower. @@ -113,23 +121,23 @@ To deploy this solution, you need | Name | Version | |------|---------| -| [AWS Control Tower](https://aws.amazon.com/controltower/) | >= 3.0 | +| [AWS Control Tower](https://aws.amazon.com/controltower/) | >= 3.2 | and the following requirements. - + ## Requirements | Name | Version | |------|---------| -| [terraform](#requirement\_terraform) | ~> 1.5 | -| [aws](#requirement\_aws) | ~> 4.67 | +| [terraform](#requirement\_terraform) | > 1.5 | +| [aws](#requirement\_aws) | > 4.67 | ## Providers | Name | Version | |------|---------| -| [aws](#provider\_aws) | 4.67.0 | +| [aws](#provider\_aws) | 5.81.0 | ## Modules @@ -146,7 +154,6 @@ No modules. | [aws_organizations_organizational_units.ous_depth_3](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organizational_units) | data source | | [aws_organizations_organizational_units.ous_depth_4](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organizational_units) | data source | | [aws_organizations_organizational_units.root](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/organizations_organizational_units) | data source | -| [aws_region.current](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | ## Inputs @@ -159,7 +166,7 @@ No modules. | Name | Description | |------|-------------| | [ous\_id\_to\_arn\_map](#output\_ous\_id\_to\_arn\_map) | Map from OU id to OU arn for the whole organization | - + ## Controls Configuration File @@ -169,14 +176,14 @@ The following is an example of an updated `variables.tfvars` file. controls = [ { control_names = [ - "AWS-GR_ENCRYPTED_VOLUMES", + "503uicglhjkokaajywfpt6ros", ... ], organizational_unit_ids = ["ou-1111-11111111", "ou-2222-22222222"...], }, { control_names = [ - "AWS-GR_SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLED", + "50z1ot237wl8u1lv5ufau6qqo", ... ], organizational_unit_ids = ["ou-1111-11111111"...], @@ -184,17 +191,15 @@ controls = [ ] ``` -1. In the `controls` section, in the `control_names` parameter, enter the control API identifier. Each control has a unique API identifier for each Region in which AWS Control Tower is available. To find the control identifier, do the following: - - 1. In [Tables of control metadata](https://docs.aws.amazon.com/controltower/latest/userguide/control-metadata-tables.html), locate the control you want to enable. +1. Open [All global identifiers](https://docs.aws.amazon.com/controltower/latest/controlreference/all-global-identifiers.html) in the AWS Control Tower documentation. - 2. In the Control API identifiers, by Region column, locate the API identifier for the Region in which you are making the API call, such as `arn:aws:controltower:us-east-1::control/AWS-GR_AUDIT_BUCKET_ENCRYPTION_ENABLED`. +2. In the JSON-formatted list, locate the control that you want to implement, and then copy its global identifier (also known as the `{CONTROL_CATALOG_OPAQUE_ID}` value). For example, the global identifier for the `AWS-GR_AUDIT_BUCKET_ENCRYPTION_ENABLED` control is `k4izcjxhukijhajp6ks5mjxk`. - 3. Extract the control identifier from the Regional identifier, such as `GR_AUDIT_BUCKET_ENCRYPTION_ENABLED`. +3. In the `controls` section, in the `control_names` parameter, enter the global identifier that you copied. -2. In the `controls` section, in the `organizational_unit_ids` parameter, enter the ID of the organizational unit where you want to enable the control, such as `ou-1111-11111111`. Enter the ID in double quotation marks, and separate multiple IDs with commas. For more information about how to retrieve OU IDs, see Viewing the [details of an OU](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_org_details.html#orgs_view_ou). +4. In the `controls` section, in the `organizational_unit_ids` parameter, enter the ID of the organizational unit where you want to enable the control, such as `ou-1111-11111111`. Enter the ID in double quotation marks, and separate multiple IDs with commas. For more information about how to retrieve OU IDs, see Viewing the [details of an OU](https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_org_details.html#orgs_view_ou). -3. Save and close the variables.tfvars file. For an example of an updated variables.tfvars file, see the [Additional information](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/deploy-and-manage-aws-control-tower-controls-by-using-terraform.html#deploy-and-manage-aws-control-tower-controls-by-using-terraform-additional) section of this pattern. +5. Save and close the variables.tfvars file. For an example of an updated variables.tfvars file, see the [Additional information](https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/deploy-and-manage-aws-control-tower-controls-by-using-terraform.html#deploy-and-manage-aws-control-tower-controls-by-using-terraform-additional) section of this pattern. ## Deployment diff --git a/img/ctc-architecture.png b/img/ctc-architecture.png index 28d9790..76a3503 100644 Binary files a/img/ctc-architecture.png and b/img/ctc-architecture.png differ diff --git a/main.tf b/main.tf index 7889cda..54108a6 100644 --- a/main.tf +++ b/main.tf @@ -16,8 +16,6 @@ # AWS Control Tower Controls (sometimes called Guardrails) Terraform Module -data "aws_region" "current" {} - data "aws_organizations_organization" "organization" {} # Get OUs data resources up to five levels of OUs deep under a root (maximum nesting quota limit) @@ -64,7 +62,7 @@ locals { guardrails_list = flatten([ for i in range(0, length(var.controls)) : [ for pair in setproduct(element(var.controls, i).control_names, element(var.controls, i).organizational_unit_ids) : - { "arn:aws:controltower:${data.aws_region.current.name}::control/${pair[0]}" = pair[1] } + { "arn:aws:controlcatalog:::control/${pair[0]}" = pair[1] } ] ]) diff --git a/variables.tfvars b/variables.tfvars index 0c8f480..40f6cc5 100644 --- a/variables.tfvars +++ b/variables.tfvars @@ -19,41 +19,41 @@ controls = [ { control_names = [ - "AWS-GR_ENCRYPTED_VOLUMES", - "AWS-GR_EBS_OPTIMIZED_INSTANCE", - "AWS-GR_EC2_VOLUME_INUSE_CHECK", - "AWS-GR_RDS_INSTANCE_PUBLIC_ACCESS_CHECK", - "AWS-GR_RDS_SNAPSHOTS_PUBLIC_PROHIBITED", - "AWS-GR_RDS_STORAGE_ENCRYPTED", - "AWS-GR_RESTRICTED_COMMON_PORTS", - "AWS-GR_RESTRICTED_SSH", - "AWS-GR_RESTRICT_ROOT_USER", - "AWS-GR_RESTRICT_ROOT_USER_ACCESS_KEYS", - "AWS-GR_ROOT_ACCOUNT_MFA_ENABLED", - "AWS-GR_S3_BUCKET_PUBLIC_READ_PROHIBITED", - "AWS-GR_S3_BUCKET_PUBLIC_WRITE_PROHIBITED", + "503uicglhjkokaajywfpt6ros", # AWS-GR_ENCRYPTED_VOLUMES + "2j9gjxqfo040xtx8kd1jf4ni6", # AWS-GR_EBS_OPTIMIZED_INSTANCE + "8c3i4catfgmyy1e19476v06rr", # AWS-GR_EC2_VOLUME_INUSE_CHECK + "4jc77cq1lcr7g64xywwypykv8", # AWS-GR_RDS_INSTANCE_PUBLIC_ACCESS_CHECK + "1h4eyqyyonp19dlrreqf1i3w0", # AWS-GR_RDS_SNAPSHOTS_PUBLIC_PROHIBITED + "e34kieahgkm0lggs5g0s412jt", # AWS-GR_RDS_STORAGE_ENCRYPTED + "df2ta5ytg2zatj1q7y5e09u32", # AWS-GR_RESTRICTED_COMMON_PORTS + "6rilu41n0gb9w6mxrkyewoer4", # AWS-GR_RESTRICTED_SSH + "5kvme4m5d2b4d7if2fs5yg2ui", # AWS-GR_RESTRICT_ROOT_USER + "8ui9y3oace2513xarz8aqojl7", # AWS-GR_RESTRICT_ROOT_USER_ACCESS_KEYS + "24izmu4k16gv9tvd7sexnyrfy", # AWS-GR_ROOT_ACCOUNT_MFA_ENABLED + "8sw3pbid15t9cbww8d2w2qwgf", # AWS-GR_S3_BUCKET_PUBLIC_READ_PROHIBITED + "9j9nwxj789d82sypnukhyyowy", # AWS-GR_S3_BUCKET_PUBLIC_WRITE_PROHIBITED ], organizational_unit_ids = ["ou-1111-11111111", "ou-2222-22222222"], }, { control_names = [ - "AWS-GR_SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLED", - "AWS-GR_AUTOSCALING_LAUNCH_CONFIG_PUBLIC_IP_DISABLED", - "AWS-GR_DISALLOW_CROSS_REGION_NETWORKING", - "AWS-GR_DISALLOW_VPC_INTERNET_ACCESS", - "AWS-GR_DISALLOW_VPN_CONNECTIONS", - "AWS-GR_DMS_REPLICATION_NOT_PUBLIC", - "AWS-GR_EBS_SNAPSHOT_PUBLIC_RESTORABLE_CHECK", - "AWS-GR_EC2_INSTANCE_NO_PUBLIC_IP", - "AWS-GR_EKS_ENDPOINT_NO_PUBLIC_ACCESS", - "AWS-GR_ELASTICSEARCH_IN_VPC_ONLY", - "AWS-GR_EMR_MASTER_NO_PUBLIC_IP", - "AWS-GR_LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED", - "AWS-GR_NO_UNRESTRICTED_ROUTE_TO_IGW", - "AWS-GR_REDSHIFT_CLUSTER_PUBLIC_ACCESS_CHECK", - "AWS-GR_S3_ACCOUNT_LEVEL_PUBLIC_ACCESS_BLOCKS_PERIODIC", - "AWS-GR_SAGEMAKER_NOTEBOOK_NO_DIRECT_INTERNET_ACCESS", - "AWS-GR_SSM_DOCUMENT_NOT_PUBLIC", + "50z1ot237wl8u1lv5ufau6qqo", # AWS-GR_SUBNET_AUTO_ASSIGN_PUBLIC_IP_DISABLED + "aemn4s3hxv9erree434pvjboi", # AWS-GR_AUTOSCALING_LAUNCH_CONFIG_PUBLIC_IP_DISABLED + "dvuaav61i5cnfazfelmvn9m6k", # AWS-GR_DISALLOW_CROSS_REGION_NETWORKING + "41ngl8m5c4eb1myoz0t707n7h", # AWS-GR_DISALLOW_VPC_INTERNET_ACCESS + "5rlqt6yj6u0v0gb62pqdy4ae", # AWS-GR_DISALLOW_VPN_CONNECTIONS + "dekrrxbiux86m6jdowdsbamze", # AWS-GR_DMS_REPLICATION_NOT_PUBLIC + "87qo8rsoettjrxjevmjqcw1tu", # AWS-GR_EBS_SNAPSHOT_PUBLIC_RESTORABLE_CHECK + "4v7xtm83uvvyulk1wwpm4qm3s", # AWS-GR_EC2_INSTANCE_NO_PUBLIC_IP + "aeellyghb27pbehyzua1nyena", # AWS-GR_EKS_ENDPOINT_NO_PUBLIC_ACCESS + "2civrte1w8tqff4vbtzdl4abq", # AWS-GR_ELASTICSEARCH_IN_VPC_ONLY + "5cnql6so7p7bs0khdjodjr9e2", # AWS-GR_EMR_MASTER_NO_PUBLIC_IP + "b2gzofz99eb7nsuj5g8wcimse", # AWS-GR_LAMBDA_FUNCTION_PUBLIC_ACCESS_PROHIBITED + "b8pjfqosgkgknznstduvel4rh", # AWS-GR_NO_UNRESTRICTED_ROUTE_TO_IGW + "1oxkwnc4hwhi2ndv6ekwy7np7", # AWS-GR_REDSHIFT_CLUSTER_PUBLIC_ACCESS_CHECK + "6wmutsohbkwhfw6sf7cbt5e81", # AWS-GR_S3_ACCOUNT_LEVEL_PUBLIC_ACCESS_BLOCKS_PERIODIC + "66gfl06uj1v999z53szvu0exa", # AWS-GR_SAGEMAKER_NOTEBOOK_NO_DIRECT_INTERNET_ACCESS + "dfanrd8y5p7oj8fjyugqnakfr", # AWS-GR_SSM_DOCUMENT_NOT_PUBLIC ], organizational_unit_ids = ["ou-1111-11111111"], }, diff --git a/versions.tf b/versions.tf index 87f5d91..5c830b5 100644 --- a/versions.tf +++ b/versions.tf @@ -17,12 +17,12 @@ # Terraform and Providers Required Versions terraform { - required_version = "~> 1.5" + required_version = "> 1.5" required_providers { aws = { source = "hashicorp/aws" - version = "~> 4.67" + version = "> 4.67" } } }