Tip: To force a full re-run across all Control Tower managed accounts, bump
lambda_versioninvariables.tf(e.g., "1" → "2") and runterraform apply. This updates the Producer Lambda config and re-processes all accounts via EventBridge/CloudTrail.
This Terraform solution customizes AWS Config Recorder settings across all AWS Control Tower managed accounts using an event-driven, serverless pattern. It applies region-aware defaults, records critical resources continuously, excludes noisy types, and uses daily recording where appropriate to reduce cost while maintaining useful visibility.
- Listens to Control Tower lifecycle events (CreateManagedAccount, UpdateManagedAccount, UpdateLandingZone)
- Fans out account/region work items via SQS
- Assumes
AWSControlTowerExecutionrole in target accounts and updates their Config Recorder - Uses region-specific mappings from
locals.tffor continuous recording and exclusions
- Event sources: EventBridge rules for Control Tower and Lambda update events
- Orchestrator: Producer Lambda (
ct-config-recorder-producer) - Queue: SQS (
ct-config-recorder-queue) - Worker: Consumer Lambda (
ct-config-recorder-consumer)
Key runtime settings (as defined in Terraform):
- Producer: python3.12, memory 512 MB, timeout 900s, reserved concurrency 1
- Consumer: python3.12, memory 512 MB, timeout 900s, reserved concurrency 50
- SQS: visibility timeout 900s, delay 5s, KMS alias
alias/aws/sqs
- AWS Control Tower is set up and managing your accounts
- Permissions to deploy in the Control Tower management account
- Terraform 1.x and AWS provider >= 5.84.0
- AWS credentials for the management account (SSO or access keys). The Consumer assumes
AWSControlTowerExecutionin target accounts.
Backend note: providers.tf configures an S3 backend. Update bucket, key, and region for your environment before running Terraform. If you prefer local state, switch the backend accordingly.
excluded_accounts(list(string), default placeholders): Account IDs to skip (typically management, audit, log archive). Must be 12-digit strings.lambda_log_level(string, defaultINFO): One ofDEBUG|INFO|WARNING|ERROR.config_recorder_strategy(string, defaultEXCLUSION): Currently onlyEXCLUSIONis supported.config_recorder_default_recording_frequency(string, defaultDAILY): Currently onlyDAILY.aws_region(string, defaulteu-west-1): Deployment and target region for providers.account_id(string): Account to assume with the provider aliastarget(defaults to a placeholder). Provider assumes roleAWSAFTExecutionwith an external ID.lambda_version(string, default1): Increment to force a manual re-run.
Region mappings (locals.tf):
region_continuous_resources: resource types that should beCONTINUOUSregion_exclusions: resource types to exclude entirely
These are exported to the Consumer Lambda via env vars REGION_RESOURCE_TYPES_MAPPING and REGION_EXCLUSIONS_MAPPING.
- Configure backend and providers
- Edit
providers.tfbackendbucket,key,region - Adjust
aws_region/account_idinvariables.tfor via-var/ tfvars
- Initialize and apply
terraform init
terraform plan
terraform apply- First run behavior
- An
aws_lambda_invocationresource triggers the Producer once after deploy to enqueue work for all accounts.
- Bump
lambda_versioninvariables.tfandterraform apply(recommended) - Update the Producer Lambda configuration in any way that results in a config update event
- Directly invoke the Producer for ad-hoc tests (optional)
- Logs:
/aws/lambda/ct-config-recorder-producer,/aws/lambda/ct-config-recorder-consumer(retention 90 days) - Look for Producer messages like "Message sent to SQS" and for Consumer messages referencing
put_configuration_recorder
- Producer isn’t firing: Verify EventBridge rules exist and CloudTrail is enabled in the management account
- Messages aren’t processed: Check SQS metrics and Consumer Lambda concurrency; verify excluded accounts list
- AssumeRole failures: Ensure
AWSControlTowerExecutionexists and the management account has permission to assume it - Not seeing updates: Confirm your target region is in
locals.tfmappings, and verify the resource lists
Enable more logs by setting lambda_log_level = "DEBUG" and re-applying.
- IAM: Minimal policies for Lambda + SQS; Consumer allows
sts:AssumeRoleand expectsAWSControlTowerExecutionin each account - SQS: Encrypted with AWS-managed KMS key
- Strategy:
EXCLUSION_BY_RESOURCE_TYPESwithDAILYdefault and regionalCONTINUOUSoverrides for critical types
Note: Cost impact depends on your footprint and change rates. This module focuses on reducing noise and avoiding duplicate global resource recording across regions.
Edit locals.tf to adjust per-region lists:
- Add resource types to
region_continuous_resourcesto record them continuously - Add resource types to
region_exclusionsto exclude them entirely
Changes are picked up by the Consumer via environment variables on the next deploy.
The included .gitlab-ci.yml provides simple plan/apply stages. Ensure a suitable runner (tags: [aws-org]) and AWS credentials are available to the runner, and that your backend is reachable.
If you deployed only this module in a dedicated workspace:
terraform destroyaws-tf-ct-config-recorder-customization/
├── main.tf # SQS, Lambdas, IAM, EventBridge, log groups
├── providers.tf # Required providers and backend config
├── variables.tf # Inputs (log level, region, exclusions, etc.)
├── locals.tf # Region mappings and common tags
├── lambda/
│ ├── producer/index.py # Producer Lambda
│ └── consumer/index.py # Consumer Lambda
└── cloudformation-deployment/
├── ct-config-recorder-customization.yml # Reference CFN implementation
└── README.md # CFN deployment guide
- AWS Config: https://docs.aws.amazon.com/config/
- AWS Control Tower: https://docs.aws.amazon.com/controltower/
- Terraform AWS Provider: https://registry.terraform.io/providers/hashicorp/aws/