This Terraform module will create, manage, and update AWS-managed Config Rules. It has the following features:
- 200+ managed rule definitions for easy deployment:
- Each rule includes a description and a severity setting (e.g.
Low
,Medium
,High
, andCritical
) - Optional and required parameters for a rule can be passed in via Terraform variables
- Rules can be modified (and added) by the users of this module without updating the module
- New rules will be added over time by the maintainers
- Each rule includes a description and a severity setting (e.g.
- Deploy managed rules in a specific AWS account (the default) or for an Organization
- Deploy Rule Packs, which are groups of rules that correspond to a compliance standard (e.g. NIST 800-53, CIS Best Practices, etc.)
- Packs deployed are de-duplicated (you can specify Packs which have overlapping rules without deploying the same rule multiple times)
- Optionally exclude specific rules
This module was created because we wanted a Terraform-native way to deploy groups of AWS-managed Config Rules across our Organization and within specific accounts. The natural choice is to deploy Conformance Packs, but these have some downsides:
- Rules are specified in each pack, so if you want to change the definition of a config for a rule across multiple packs, you have to edit each YAML file.
- If you want to exclude rules, you have to search in the pack for the rule and delete them from each
- Rule duplication
- Rule visibility: changing individually deployed Terraform rules is more granular than packs
See the examples
directory for more details and complete examples. But as a quick example, if you wanted to:
- Deploy all the rules associated with NIST 800-53 rev 4 and Operational Best Practices for Logging
- Change the
access-keys-rotated
age time in days to60
- Exclude
ec2-imdsv2-check
rule - Change the Config Rule description for the
acm-certificate-expiration-check
rule
You would do something like:
module "managed_rules" {
source = "."
rule_packs = [
"Operational-Best-Practices-for-Operational-Best-Practices-for-Logging",
"Operational-Best-Practices-for-NIST-800-53-rev-4",
]
access_keys_rotated_parameters = {
maxAccessKeyAge = "60"
}
rules_to_exclude = [
"ec2-imdsv2-check",
]
rule_overrides = {
acm-certificate-expiration-check = {
description = "Some other description I think people will like"
}
}
}
This section discusses some of the internal construction of this module. The information provided is not necessary to get started, but helpful to understand what's being done, advanced configuration, how it's built, etc.
A rule pack is a list/array of rules associated to a security/compliance standard like NIST 800-53. The rules that make up these packs are derived from AWS's Conformance Packs. In the files
directory of this repo, you will find two files:
- A YAML file containing all of the Config Rules associated with each pack
- A text file containing only a newline separated list of all the packs
The text file is included to provide the users of this module with an easy to read list of all the packs that exist. These are what you pass to the rule_packs
Terraform input variable if you want to deploy packs.
The YAML file provides a human and machine readable association of the AWS-managed Config Rules to the Conformance Packs. This is used by the Terraform module to derive which rules are associated to which pack.
These files are generated by the maintainers of this module running scripts/generate-rule-pack-info.rb
and committing the results to the repository.
The managed rules are defined in the local.managed_rules
variable; the following is a sample of a few rule definitions that we will go over in detail:
locals {
managed_rules = {
access-keys-rotated = {
description = "Checks if 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 = var.access_keys_rotated_parameters
severity = "Medium"
}
account-part-of-organizations = {
description = "Checks if an AWS account is part of AWS Organizations. The rule is NON_COMPLIANT if an AWS account is not part of AWS Organizations or AWS Organizations master account ID does not match rule parameter MasterAccountId."
input_parameters = var.account_part_of_organizations_parameters
severity = "Low"
}
acm-certificate-expiration-check = {
description = "Checks if AWS Certificate Manager Certificates in your account are marked for expiration within the specified number of days. Certificates provided by ACM are automatically renewed. ACM does not automatically renew certificates that you import."
input_parameters = var.acm_certificate_expiration_check_parameters
resource_types_scope = ["AWS::ACM::Certificate"]
severity = "Medium"
}
}
}
Things to note:
- The rule name is the key to each item in the map
- The rule name corresponds to the managed rule identifier
- Most managed rule names match the identifier, just upper-cased and using underscores instead of hyphens. A handful do not; this module changes the name of the rule to match the identifier when the two do not match up.
- AWS Config has a 256 character limit for descriptions. Descriptions were taken from the managed rule list, but occasionally had to be truncated to make them fit.
- For rules with input parameters, a variable will be present in the form of
rule_name_parameters
. When possible, these are typed to provide guidance and guardrails, but until optional types in Terraform are fully supported, many parameters are not typed. - Regardless of what the Config Rule says in terms of types (e.g.
int
, etc.) from what we've seen, the rule ultimately expects the value passed in to be a string. Keep this in mind when passing in input parameters - Severity has no impact on the Config Rule and provided purely as metadata for consumption in downstream tooling. Rule severity was derived from looking at rules/controls in Security Hub standards, and when not available, at the best judgement of this module's maintainers
- Rules can be overridden and new rules can be added. If AWS came out with a new rule called
account-name-starts-with
, and you wanted to add support for it right away, you could add the following as an input variable to the module:
rule_overrides = {
account-name-starts-with = {
description = "Checks if an AWS account's name starts with the string provided. The rule is NON_COMPLIANT if an AWS account does not start with the name provided"
severity = "Medium" # completely optional, since 'severity' is metadata only
input_parameters = {
accountNameStartsWith = "myorgname"
}
}
}
This variable can also be used to change/override any settings for any managed config rule previously defined