diff --git a/modules/aws-config/README.md b/modules/aws-config/README.md index 481850a1c..c8f35b94c 100644 --- a/modules/aws-config/README.md +++ b/modules/aws-config/README.md @@ -20,6 +20,26 @@ Some of the key features of AWS Config include: - Notifications and alerts: AWS Config can send notifications and alerts when changes are made to your AWS resources that could impact their compliance or security posture. +:::caution AWS Config Limitations + +You'll also want to be aware of some limitations with AWS Config: + +- The maximum number of AWS Config rules that can be evaluated in a single account is 1000. + - This can be mitigated by removing rules that are duplicated across packs. You'll have to manually search for these + duplicates. + - You can also look for rules that do not apply to any resources and remove those. You'll have to manually click + through rules in the AWS Config interface to see which rules are not being evaluated. + - If you end up still needing more than 1000 rules, one recommendation is to only run packs on a schedule with a + lambda that removes the pack after results are collected. If you had different schedule for each day of the week, + that would mean 7000 rules over the week. The aggregators would not be able to handle this, so you would need to + make sure to store them somewhere else (i.e. S3) so the findings are not lost. + - See the + [Audit Manager docs](https://aws.amazon.com/blogs/mt/integrate-across-the-three-lines-model-part-2-transform-aws-config-conformance-packs-into-aws-audit-manager-assessments/) + if you think you would like to convert conformance packs to custom Audit Manager assessments. +- The maximum number of AWS Config conformance packs that can be created in a single account is 50. + +::: + Overall, AWS Config provides you with a powerful toolset to help you monitor and manage the configurations of your AWS resources, ensuring that they remain compliant, secure, and properly configured over time. @@ -54,9 +74,26 @@ Before deploying this AWS Config component `config-bucket` and `cloudtrail-bucke ## Usage -**Stack Level**: Regional +**Stack Level**: Regional or Global + +This component has a `default_scope` variable for configuring if it will be an organization-wide or account-level +component by default. Note that this can be overridden by the `scope` variable in the `conformance_packs` items. + +:::info Using the account default_scope + +If default_scope == `account`, AWS Config is regional AWS service, so this component needs to be deployed to all +regions. If an individual `conformance_packs` item has `scope` set to `organization`, that particular pack will be +deployed to the organization level. + +::: -_**NOTE**: Since AWS Config is regional AWS service, this component needs to be deployed to all regions._ +:::info Using the organization default_scope + +If default_scope == `organization`, AWS Config is global unless overriden in the `conformance_packs` items. You will +need to update your org to allow the `config-multiaccountsetup.amazonaws.com` service access principal for this to work. +If you are using our `account` component, just add that principal to the `aws_service_access_principals` variable. + +::: At the AWS Organizational level, the Components designate an account to be the `central collection account` and a single region to be the `central collection region` so that compliance information can be aggregated into a central location. @@ -99,7 +136,7 @@ components: input_parameters: maxAccessKeyAge: "30" enabled: true - tags: {} + tags: { } ``` ## Deployment @@ -122,95 +159,97 @@ Apply aws-config to all stacks in all stages. atmos terraform plan aws-config-{each region} --stack {each region}-{each stage} ``` - + ## Requirements -| Name | Version | -|------|---------| -| [terraform](#requirement\_terraform) | >= 1.0.0 | -| [aws](#requirement\_aws) | >= 4.0 | -| [awsutils](#requirement\_awsutils) | >= 0.16.0 | +| Name | Version | +| ------------------------------------------------------------------------ | --------- | +| [terraform](#requirement_terraform) | >= 1.0.0 | +| [aws](#requirement_aws) | >= 4.0 | +| [awsutils](#requirement_awsutils) | >= 0.16.0 | ## Providers -| Name | Version | -|------|---------| -| [aws](#provider\_aws) | >= 4.0 | +| Name | Version | +| ------------------------------------------------ | ------- | +| [aws](#provider_aws) | >= 4.0 | ## Modules -| Name | Source | Version | -|------|--------|---------| -| [account\_map](#module\_account\_map) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | -| [aws\_config](#module\_aws\_config) | cloudposse/config/aws | 1.1.0 | -| [aws\_config\_label](#module\_aws\_config\_label) | cloudposse/label/null | 0.25.0 | -| [aws\_team\_roles](#module\_aws\_team\_roles) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | -| [config\_bucket](#module\_config\_bucket) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | -| [conformance\_pack](#module\_conformance\_pack) | cloudposse/config/aws//modules/conformance-pack | 1.1.0 | -| [global\_collector\_region](#module\_global\_collector\_region) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | -| [iam\_roles](#module\_iam\_roles) | ../account-map/modules/iam-roles | n/a | -| [this](#module\_this) | cloudposse/label/null | 0.25.0 | -| [utils](#module\_utils) | cloudposse/utils/aws | 1.3.0 | +| Name | Source | Version | +| -------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | ------- | +| [account_map](#module_account_map) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | +| [aws_config](#module_aws_config) | cloudposse/config/aws | 1.1.0 | +| [aws_config_label](#module_aws_config_label) | cloudposse/label/null | 0.25.0 | +| [aws_team_roles](#module_aws_team_roles) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | +| [config_bucket](#module_config_bucket) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | +| [conformance_pack](#module_conformance_pack) | cloudposse/config/aws//modules/conformance-pack | 1.1.0 | +| [global_collector_region](#module_global_collector_region) | cloudposse/stack-config/yaml//modules/remote-state | 1.5.0 | +| [iam_roles](#module_iam_roles) | ../account-map/modules/iam-roles | n/a | +| [org_conformance_pack](#module_org_conformance_pack) | ./modules/org-conformance-pack | n/a | +| [this](#module_this) | cloudposse/label/null | 0.25.0 | +| [utils](#module_utils) | cloudposse/utils/aws | 1.3.0 | ## Resources -| Name | Type | -|------|------| +| Name | Type | +| -------------------------------------------------------------------------------------------------------------------------- | ----------- | | [aws_caller_identity.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) | data source | -| [aws_partition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | -| [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | +| [aws_partition.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/partition) | data source | +| [aws_region.this](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/region) | data source | ## Inputs -| Name | Description | Type | Default | Required | -|------|-------------|------|---------|:--------:| -| [account\_map\_tenant](#input\_account\_map\_tenant) | (Optional) The tenant where the account\_map component required by remote-state is deployed. | `string` | `""` | no | -| [additional\_tag\_map](#input\_additional\_tag\_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | -| [attributes](#input\_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | -| [az\_abbreviation\_type](#input\_az\_abbreviation\_type) | AZ abbreviation type, `fixed` or `short` | `string` | `"fixed"` | no | -| [central\_resource\_collector\_account](#input\_central\_resource\_collector\_account) | The name of the account that is the centralized aggregation account. | `string` | n/a | yes | -| [config\_bucket\_env](#input\_config\_bucket\_env) | The environment of the AWS Config S3 Bucket | `string` | n/a | yes | -| [config\_bucket\_stage](#input\_config\_bucket\_stage) | The stage of the AWS Config S3 Bucket | `string` | n/a | yes | -| [config\_bucket\_tenant](#input\_config\_bucket\_tenant) | (Optional) The tenant of the AWS Config S3 Bucket | `string` | `""` | no | -| [conformance\_packs](#input\_conformance\_packs) | List of conformance packs. Each conformance pack is a map with the following keys: name, conformance\_pack, parameter\_overrides.

For example:
conformance\_packs = [
{
name = "Operational-Best-Practices-for-CIS-AWS-v1.4-Level1"
conformance\_pack = "https://raw.githubusercontent.com/awslabs/aws-config-rules/master/aws-config-conformance-packs/Operational-Best-Practices-for-CIS-AWS-v1.4-Level1.yaml"
parameter\_overrides = {
"AccessKeysRotatedParamMaxAccessKeyAge" = "45"
}
},
{
name = "Operational-Best-Practices-for-CIS-AWS-v1.4-Level2"
conformance\_pack = "https://raw.githubusercontent.com/awslabs/aws-config-rules/master/aws-config-conformance-packs/Operational-Best-Practices-for-CIS-AWS-v1.4-Level2.yaml"
parameter\_overrides = {
"IamPasswordPolicyParamMaxPasswordAge" = "45"
}
}
]

Complete list of AWS Conformance Packs managed by AWSLabs can be found here:
https://github.com/awslabs/aws-config-rules/tree/master/aws-config-conformance-packs |
list(object({
name = string
conformance_pack = string
parameter_overrides = map(string)
}))
| `[]` | no | -| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | -| [create\_iam\_role](#input\_create\_iam\_role) | Flag to indicate whether an IAM Role should be created to grant the proper permissions for AWS Config | `bool` | `false` | no | -| [delegated\_accounts](#input\_delegated\_accounts) | The account IDs of other accounts that will send their AWS Configuration or Security Hub data to this account | `set(string)` | `null` | no | -| [delimiter](#input\_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | -| [descriptor\_formats](#input\_descriptor\_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | -| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | -| [environment](#input\_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | -| [global\_environment](#input\_global\_environment) | Global environment name | `string` | `"gbl"` | no | -| [global\_resource\_collector\_region](#input\_global\_resource\_collector\_region) | The region that collects AWS Config data for global resources such as IAM | `string` | n/a | yes | -| [iam\_role\_arn](#input\_iam\_role\_arn) | The ARN for an IAM Role AWS Config uses to make read or write requests to the delivery channel and to describe the
AWS resources associated with the account. This is only used if create\_iam\_role is false.

If you want to use an existing IAM Role, set the variable to the ARN of the existing role and set create\_iam\_role to `false`.

See the AWS Docs for further information:
http://docs.aws.amazon.com/config/latest/developerguide/iamrole-permissions.html | `string` | `null` | no | -| [iam\_roles\_environment\_name](#input\_iam\_roles\_environment\_name) | The name of the environment where the IAM roles are provisioned | `string` | `"gbl"` | no | -| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | -| [label\_key\_case](#input\_label\_key\_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | -| [label\_order](#input\_label\_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | -| [label\_value\_case](#input\_label\_value\_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | -| [labels\_as\_tags](#input\_labels\_as\_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | -| [managed\_rules](#input\_managed\_rules) | A list of AWS Managed Rules that should be enabled on the account.

See the following for a list of possible rules to enable:
https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html

Example:
managed_rules = {
access-keys-rotated = {
identifier = "ACCESS_KEYS_ROTATED"
description = "Checks whether the active access keys are rotated within the number of days specified in maxAccessKeyAge. The rule is NON_COMPLIANT if the access keys have not been rotated for more than maxAccessKeyAge number of days."
input_parameters = {
maxAccessKeyAge : "90"
}
enabled = true
tags = {}
}
}
|
map(object({
description = string
identifier = string
input_parameters = any
tags = map(string)
enabled = bool
}))
| `{}` | no | -| [name](#input\_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | -| [namespace](#input\_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | -| [privileged](#input\_privileged) | True if the default provider already has access to the backend | `bool` | `false` | no | -| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | -| [region](#input\_region) | AWS Region | `string` | n/a | yes | -| [root\_account\_stage](#input\_root\_account\_stage) | The stage name for the Organization root (master) account | `string` | `"root"` | no | -| [stage](#input\_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | -| [tags](#input\_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | -| [tenant](#input\_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | +| Name | Description | Type | Default | Required | +| --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------: | +| [account_map_tenant](#input_account_map_tenant) | (Optional) The tenant where the account_map component required by remote-state is deployed. | `string` | `""` | no | +| [additional_tag_map](#input_additional_tag_map) | Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`.
This is for some rare cases where resources want additional configuration of tags
and therefore take a list of maps with tag key, value, and additional configuration. | `map(string)` | `{}` | no | +| [attributes](#input_attributes) | ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`,
in the order they appear in the list. New attributes are appended to the
end of the list. The elements of the list are joined by the `delimiter`
and treated as a single ID element. | `list(string)` | `[]` | no | +| [az_abbreviation_type](#input_az_abbreviation_type) | AZ abbreviation type, `fixed` or `short` | `string` | `"fixed"` | no | +| [central_resource_collector_account](#input_central_resource_collector_account) | The name of the account that is the centralized aggregation account. | `string` | n/a | yes | +| [config_bucket_env](#input_config_bucket_env) | The environment of the AWS Config S3 Bucket | `string` | n/a | yes | +| [config_bucket_stage](#input_config_bucket_stage) | The stage of the AWS Config S3 Bucket | `string` | n/a | yes | +| [config_bucket_tenant](#input_config_bucket_tenant) | (Optional) The tenant of the AWS Config S3 Bucket | `string` | `""` | no | +| [conformance_packs](#input_conformance_packs) | List of conformance packs. Each conformance pack is a map with the following keys: name, conformance_pack, parameter_overrides.

For example:
conformance_packs = [
{
name = "Operational-Best-Practices-for-CIS-AWS-v1.4-Level1"
conformance\_pack = "https://raw.githubusercontent.com/awslabs/aws-config-rules/master/aws-config-conformance-packs/Operational-Best-Practices-for-CIS-AWS-v1.4-Level1.yaml"
parameter\_overrides = {
"AccessKeysRotatedParamMaxAccessKeyAge" = "45"
}
},
{
name = "Operational-Best-Practices-for-CIS-AWS-v1.4-Level2"
conformance\_pack = "https://raw.githubusercontent.com/awslabs/aws-config-rules/master/aws-config-conformance-packs/Operational-Best-Practices-for-CIS-AWS-v1.4-Level2.yaml"
parameter\_overrides = {
"IamPasswordPolicyParamMaxPasswordAge" = "45"
}
}
]

Complete list of AWS Conformance Packs managed by AWSLabs can be found here:
https://github.com/awslabs/aws-config-rules/tree/master/aws-config-conformance-packs |
list(object({
name = string
conformance_pack = string
parameter_overrides = map(string)
scope = optional(string, null)
}))
| `[]` | no | +| [context](#input_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional_tag_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"descriptor_formats": {},
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"labels_as_tags": [
"unset"
],
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {},
"tenant": null
}
| no | +| [create_iam_role](#input_create_iam_role) | Flag to indicate whether an IAM Role should be created to grant the proper permissions for AWS Config | `bool` | `false` | no | +| [default_scope](#input_default_scope) | The default scope of the conformance pack. Valid values are `account` and `organization`. | `string` | `"account"` | no | +| [delegated_accounts](#input_delegated_accounts) | The account IDs of other accounts that will send their AWS Configuration or Security Hub data to this account | `set(string)` | `null` | no | +| [delimiter](#input_delimiter) | Delimiter to be used between ID elements.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no | +| [descriptor_formats](#input_descriptor_formats) | Describe additional descriptors to be output in the `descriptors` output map.
Map of maps. Keys are names of descriptors. Values are maps of the form
`{
format = string
labels = list(string)
}`
(Type is `any` so the map values can later be enhanced to provide additional options.)
`format` is a Terraform format string to be passed to the `format()` function.
`labels` is a list of labels, in order, to pass to `format()` function.
Label values will be normalized before being passed to `format()` so they will be
identical to how they appear in `id`.
Default is `{}` (`descriptors` output will be empty). | `any` | `{}` | no | +| [enabled](#input_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no | +| [environment](#input_environment) | ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no | +| [global_environment](#input_global_environment) | Global environment name | `string` | `"gbl"` | no | +| [global_resource_collector_region](#input_global_resource_collector_region) | The region that collects AWS Config data for global resources such as IAM | `string` | n/a | yes | +| [iam_role_arn](#input_iam_role_arn) | The ARN for an IAM Role AWS Config uses to make read or write requests to the delivery channel and to describe the
AWS resources associated with the account. This is only used if create_iam_role is false.

If you want to use an existing IAM Role, set the variable to the ARN of the existing role and set create_iam_role to `false`.

See the AWS Docs for further information:
http://docs.aws.amazon.com/config/latest/developerguide/iamrole-permissions.html | `string` | `null` | no | +| [iam_roles_environment_name](#input_iam_roles_environment_name) | The name of the environment where the IAM roles are provisioned | `string` | `"gbl"` | no | +| [id_length_limit](#input_id_length_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for keep the existing setting, which defaults to `0`.
Does not affect `id_full`. | `number` | `null` | no | +| [label_key_case](#input_label_key_case) | Controls the letter case of the `tags` keys (label names) for tags generated by this module.
Does not affect keys of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no | +| [label_order](#input_label_order) | The order in which the labels (ID elements) appear in the `id`.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. | `list(string)` | `null` | no | +| [label_value_case](#input_label_value_case) | Controls the letter case of ID elements (labels) as included in `id`,
set as tag values, and output by this module individually.
Does not affect values of tags passed in via the `tags` input.
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs.
Default value: `lower`. | `string` | `null` | no | +| [labels_as_tags](#input_labels_as_tags) | Set of labels (ID elements) to include as tags in the `tags` output.
Default is to include all labels.
Tags with empty values will not be included in the `tags` output.
Set to `[]` to suppress all generated tags.
**Notes:**
The value of the `name` tag, if included, will be the `id`, not the `name`.
Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be
changed in later chained modules. Attempts to change it will be silently ignored. | `set(string)` |
[
"default"
]
| no | +| [managed_rules](#input_managed_rules) | A list of AWS Managed Rules that should be enabled on the account.

See the following for a list of possible rules to enable:
https://docs.aws.amazon.com/config/latest/developerguide/managed-rules-by-aws-config.html

Example:
managed_rules = {
access-keys-rotated = {
identifier = "ACCESS_KEYS_ROTATED"
description = "Checks whether the active access keys are rotated within the number of days specified in maxAccessKeyAge. The rule is NON_COMPLIANT if the access keys have not been rotated for more than maxAccessKeyAge number of days."
input_parameters = {
maxAccessKeyAge : "90"
}
enabled = true
tags = {}
}
}
|
map(object({
description = string
identifier = string
input_parameters = any
tags = map(string)
enabled = bool
}))
| `{}` | no | +| [name](#input_name) | ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'.
This is the only ID element not also included as a `tag`.
The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. | `string` | `null` | no | +| [namespace](#input_namespace) | ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique | `string` | `null` | no | +| [privileged](#input_privileged) | True if the default provider already has access to the backend | `bool` | `false` | no | +| [regex_replace_chars](#input_regex_replace_chars) | Terraform regular expression (regex) string.
Characters matching the regex will be removed from the ID elements.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no | +| [region](#input_region) | AWS Region | `string` | n/a | yes | +| [root_account_stage](#input_root_account_stage) | The stage name for the Organization root (master) account | `string` | `"root"` | no | +| [stage](#input_stage) | ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no | +| [tags](#input_tags) | Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`).
Neither the tag keys nor the tag values will be modified by this module. | `map(string)` | `{}` | no | +| [tenant](#input_tenant) | ID element \_(Rarely used, not included by default)\_. A customer identifier, indicating who this instance of a resource is for | `string` | `null` | no | ## Outputs -| Name | Description | -|------|-------------| -| [aws\_config\_configuration\_recorder\_id](#output\_aws\_config\_configuration\_recorder\_id) | The ID of the AWS Config Recorder | -| [aws\_config\_iam\_role](#output\_aws\_config\_iam\_role) | The ARN of the IAM Role used for AWS Config | -| [storage\_bucket\_arn](#output\_storage\_bucket\_arn) | Storage Config bucket ARN | -| [storage\_bucket\_id](#output\_storage\_bucket\_id) | Storage Config bucket ID | +| Name | Description | +| ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| [aws_config_configuration_recorder_id](#output_aws_config_configuration_recorder_id) | The ID of the AWS Config Recorder | +| [aws_config_iam_role](#output_aws_config_iam_role) | The ARN of the IAM Role used for AWS Config | +| [storage_bucket_arn](#output_storage_bucket_arn) | Storage Config bucket ARN | +| [storage_bucket_id](#output_storage_bucket_id) | Storage Config bucket ID | + - ## References diff --git a/modules/aws-config/main.tf b/modules/aws-config/main.tf index 28b444184..b36912ada 100644 --- a/modules/aws-config/main.tf +++ b/modules/aws-config/main.tf @@ -40,15 +40,37 @@ module "utils" { context = module.this.context } +locals { + packs = [for pack in var.conformance_packs : merge(pack, { scope = coalesce(pack.scope, var.default_scope) })] + account_packs = { for pack in local.packs : pack.name => pack if pack.scope == "account" } + org_packs = { for pack in local.packs : pack.name => pack if pack.scope == "organization" } +} + module "conformance_pack" { source = "cloudposse/config/aws//modules/conformance-pack" version = "1.1.0" - count = local.enabled ? length(var.conformance_packs) : 0 + for_each = local.enabled ? local.account_packs : {} + + name = each.key + conformance_pack = each.value.conformance_pack + parameter_overrides = each.value.parameter_overrides + + depends_on = [ + module.aws_config + ] + + context = module.this.context +} + +module "org_conformance_pack" { + source = "./modules/org-conformance-pack" + + for_each = local.enabled ? local.org_packs : {} - name = var.conformance_packs[count.index].name - conformance_pack = var.conformance_packs[count.index].conformance_pack - parameter_overrides = var.conformance_packs[count.index].parameter_overrides + name = each.key + conformance_pack = each.value.conformance_pack + parameter_overrides = each.value.parameter_overrides depends_on = [ module.aws_config diff --git a/modules/aws-config/modules/org-conformance-pack/README.md b/modules/aws-config/modules/org-conformance-pack/README.md new file mode 100644 index 000000000..5e196d5ff --- /dev/null +++ b/modules/aws-config/modules/org-conformance-pack/README.md @@ -0,0 +1,48 @@ +# AWS Config Conformance Pack + +This module deploys a +[Conformance Pack](https://docs.aws.amazon.com/config/latest/developerguide/conformance-packs.html). A conformance pack +is a collection of AWS Config rules and remediation actions that can be easily deployed as a single entity in an account +and a Region or across an organization in AWS Organizations. Conformance packs are created by authoring a YAML template +that contains the list of AWS Config managed or custom rules and remediation actions. + +The Conformance Pack cannot be deployed until AWS Config is deployed, which can be deployed using the +[aws-config](../../) component. + +## Usage + +First, make sure your root `account` allows the service access principal `config-multiaccountsetup.amazonaws.com` to +update child organizations. You can see the docs on the account module here: +[aws_service_access_principals](https://docs.cloudposse.com/components/library/aws/account/#input_aws_service_access_principals) + +Then you have two options: + +- Set the `default_scope` of the parent `aws-config` component to be `organization` (can be overridden by the `scope` of + each `conformance_packs` item) +- Set the `scope` of the `conformance_packs` item to be `organization` + +An example YAML stack config for Atmos follows. Note, that both options are shown for demonstration purposes. In +practice you should only have one `aws-config` per account: + +```yaml +components: + terraform: + account: + vars: + aws_service_access_principals: + - config-multiaccountsetup.amazonaws.com + + aws-config/cis/level-1: + vars: + conformance_packs: + - name: Operational-Best-Practices-for-CIS-AWS-v1.4-Level1 + conformance_pack: https://raw.githubusercontent.com/awslabs/aws-config-rules/master/aws-config-conformance-packs/Operational-Best-Practices-for-CIS-AWS-v1.4-Level1.yaml + scope: organization + + aws-config/cis/level-2: + vars: + default_scope: organization + conformance_packs: + - name: Operational-Best-Practices-for-CIS-AWS-v1.4-Level2 + conformance_pack: https://raw.githubusercontent.com/awslabs/aws-config-rules/master/aws-config-conformance-packs/Operational-Best-Practices-for-CIS-AWS-v1.4-Level2.yaml +``` diff --git a/modules/aws-config/modules/org-conformance-pack/context.tf b/modules/aws-config/modules/org-conformance-pack/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/modules/aws-config/modules/org-conformance-pack/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/modules/aws-config/modules/org-conformance-pack/main.tf b/modules/aws-config/modules/org-conformance-pack/main.tf new file mode 100644 index 000000000..b2c021536 --- /dev/null +++ b/modules/aws-config/modules/org-conformance-pack/main.tf @@ -0,0 +1,17 @@ +resource "aws_config_organization_conformance_pack" "default" { + name = module.this.name + + dynamic "input_parameter" { + for_each = var.parameter_overrides + content { + parameter_name = input_parameter.key + parameter_value = input_parameter.value + } + } + + template_body = data.http.conformance_pack.body +} + +data "http" "conformance_pack" { + url = var.conformance_pack +} diff --git a/modules/aws-config/modules/org-conformance-pack/outputs.tf b/modules/aws-config/modules/org-conformance-pack/outputs.tf new file mode 100644 index 000000000..f3b7cef11 --- /dev/null +++ b/modules/aws-config/modules/org-conformance-pack/outputs.tf @@ -0,0 +1,4 @@ +output "arn" { + value = aws_config_organization_conformance_pack.default.arn + description = "ARN for the AWS Config Organization Conformance Pack" +} diff --git a/modules/aws-config/modules/org-conformance-pack/variables.tf b/modules/aws-config/modules/org-conformance-pack/variables.tf new file mode 100644 index 000000000..cb92dbf5c --- /dev/null +++ b/modules/aws-config/modules/org-conformance-pack/variables.tf @@ -0,0 +1,10 @@ +variable "conformance_pack" { + type = string + description = "The URL to a Conformance Pack" +} + +variable "parameter_overrides" { + type = map(any) + description = "A map of parameters names to values to override from the template" + default = {} +} diff --git a/modules/aws-config/modules/org-conformance-pack/versions.tf b/modules/aws-config/modules/org-conformance-pack/versions.tf new file mode 100644 index 000000000..cff384723 --- /dev/null +++ b/modules/aws-config/modules/org-conformance-pack/versions.tf @@ -0,0 +1,15 @@ +terraform { + required_version = ">= 1.0.0" + + required_providers { + aws = { + source = "hashicorp/aws" + version = ">= 4.0" + } + + http = { + source = "hashicorp/http" + version = ">= 2.1.0" + } + } +} diff --git a/modules/aws-config/variables.tf b/modules/aws-config/variables.tf index cf8d17c8d..367ddc360 100644 --- a/modules/aws-config/variables.tf +++ b/modules/aws-config/variables.tf @@ -108,8 +108,14 @@ variable "conformance_packs" { name = string conformance_pack = string parameter_overrides = map(string) + scope = optional(string, null) })) default = [] + validation { + # verify scope is valid + condition = alltrue([for conformance_pack in var.conformance_packs : conformance_pack.scope == null || conformance_pack.scope == "account" || conformance_pack.scope == "organization"]) + error_message = "The scope must be either `account` or `organization`." + } } variable "delegated_accounts" { @@ -155,3 +161,13 @@ variable "managed_rules" { })) default = {} } + +variable "default_scope" { + type = string + description = "The default scope of the conformance pack. Valid values are `account` and `organization`." + default = "account" + validation { + condition = var.default_scope == "account" || var.default_scope == "organization" + error_message = "The scope must be either `account` or `organization`." + } +}