Home Resume
Homeβ€Ί Blogβ€Ί Week 12 - AWS Config Compliance Automation: The Dr…
AWS Weekly Lab AWS Terraform

AWS Config Compliance Automation: The Drift Security Hub Never Sees

Security Hub (Week 11) is very good at catching security misconfigurations. It has no concept of a missing cost-allocation tag or a data bucket that was never versioned β€” those aren't security findings, so nothing watches for them. This week closes that gap with a self-contained AWS Config conformance pack: two rules auto-remediate through AWS-managed SSM documents, one is deliberately notify-only because a tag value can't be safely invented, and a daily Lambda digest reports on all three. Along the way, the account's Config recorder vanished mid-build β€” not from anything this stack did β€” and that turned into the week's real lesson.

AWS Platform Engineering Lab Β· Week 12

Why β€” The Problem This Solves

Security Hub's FSBP standard checks roughly 300 controls, and by Week 11 it already covered nearly every security-relevant misconfiguration in this account β€” open ports, public buckets, disabled encryption defaults. But it has no concept of governance drift. A bucket created without a cost-center tag, or one that was never versioned, isn't a security finding β€” Security Hub simply has no control for either. Nothing watches for it.

That kind of drift doesn't announce itself. It accumulates quietly until a monthly cost-allocation report has a chunk of unattributed spend, or until someone deletes an object from an unversioned bucket and there's no previous version to recover. The usual response β€” a quarterly manual tagging audit β€” runs long after the drift has already cost something.

This week adds exactly the checks Security Hub can't: mandatory tagging, S3 versioning, S3 default encryption. Two of the three fix themselves; the third can only ever notify, because a governance value like cost-center isn't something automation is allowed to guess.

What You Need to Know β€” Skills & Tools

AWS Config Config Conformance Packs Config Remediation Systems Manager Automation AWS Lambda (Python 3.13) Amazon EventBridge Amazon SNS Amazon SQS (DLQ) IAM Terraform (HCP)

The one decision that shaped everything: one conformance pack, not standalone rules

The original plan was three standalone aws_config_config_rule resources, wrapped in a conformance pack for the dashboard view. That isn't how conformance packs actually work. A pack's template defines its own AWS::Config::ConfigRule and AWS::Config::RemediationConfiguration resources β€” it doesn't reference rules created elsewhere. Building both would have meant two copies of the same rule. So this stack has no standalone Config-rule resources at all: the pack, rendered from a CloudFormation-style YAML template via Terraform's templatefile(), is the only place these three rules exist. That also means the pack's own console compliance view is the real compliance dashboard, not a second copy of something that already existed.

Why these 3 rules, and not more

The account already has roughly 300 Config rules β€” all prefixed securityhub-*, auto-created by Security Hub's FSBP standard in Week 11. Adding more security-flavored rules would be redundant, not additive. required-tags, s3-bucket-versioning-enabled, and s3-bucket-server-side-encryption-enabled were chosen because they're confirmed absent from that set β€” FSBP's S3 checks are public-access and SSL-in-transit only, and it has no tagging-governance concept whatsoever.

Why SSM Automation, not a custom Lambda β€” the opposite of Week 11

Week 11's remediators were bespoke Lambdas, because revoking the specific ingress rule a finding pointed at needed real parsing logic no canned document could do. These two fixes are the opposite shape: enabling versioning or default encryption is one documented API call, and AWS already ships tested SSM Automation documents for exactly that β€” AWS-ConfigureS3BucketVersioning and AWS-EnableS3BucketEncryption, both confirmed live against this account before use. Writing a Lambda to do the same thing would be reinventing something AWS already maintains. Reach for a custom Lambda only when the fix isn't a single documented action β€” that's genuinely Week 11's situation, not this one.

Prerequisite: an active Config recorder

Without a recorder actively recording, these rules (and Security Hub's FSBP controls) report NO_DATA and evaluate nothing. This stack provisions its own recorder β€” see Architecture below for why that became necessary partway through the build.

Architecture β€” How It Fits Together

A Config recorder feeds configuration items to a single conformance pack containing three rules. Two are tag-scoped and auto-remediate through SSM Automation; the third is broad-scope and notify-only. A scheduled Lambda reads the pack's own compliance status daily and reports it to a subscribed inbox β€” scoped to just these three rules, never the account's other ~300.

Week 12 architecture: a Config recorder feeds a conformance pack of 3 rules; two auto-remediate via SSM Automation to S3, one notifies via a daily Lambda digest to SNS AWS Config Recorder Records all resource types this Lab's own, since Jul 2026 Conformance Pack β€” week12-cfgcompliance-pack self-contained: defines its own rules, no standalone Config-rule resources required-tags environment / owner / cost-center scope: broad (no tag filter) notify only s3-bucket-versioning-enabled scope: tag auto-remediate=true not covered by Security Hub FSBP auto-remediate s3-bucket-sse-enabled scope: tag auto-remediate=true not covered by Security Hub FSBP auto-remediate daily read, all 3 rules auto-fix (2 rules) compliance_reporter daily EventBridge schedule discovers rules via the pack SNS Digest Topic plain-text summary, all 3 rules Subscribed Inbox confirmed email subscription SSM Automation AWS-Configure/EnableS3Bucket* AWS-managed, not custom code S3 Bucket versioning + SSE enabled tagged auto-remediate=true notify-only (no auto-fix) tag-scoped auto-remediate
1

Record

An AWS Config recorder captures configuration changes for every supported resource type, continuously, account-wide. This stack owns its recorder outright β€” more on why in Challenges.

2

Evaluate

The conformance pack's three rules evaluate against the recorded configuration items. required-tags checks everything broadly; the two S3 rules only evaluate buckets carrying auto-remediate=true, via Config's own Scope.TagKey/TagValue filter β€” no custom code decides what's in scope.

3

Remediate β€” only what's tagged

A non-compliant, tagged bucket triggers Config's remediation configuration, which invokes an AWS-managed SSM Automation document. One API call each: enable versioning, or enable default encryption.

4

Digest, daily

A Lambda on a daily EventBridge schedule asks Config directly which rules exist in the pack and what their compliance status is, then publishes a plain-text summary to SNS β€” scoped to just these 3 rules, never the account's other ~300.

How We Built It β€” Step by Step

The stack is 23 Terraform resources across three modules (config_recorder, config_compliance, reporter), deployed through HCP Terraform.

Step 1 β€” Deploy via HCP Terraform

Same VCS-connected workflow as every week since Week 5: push to GitHub, HCP plans and applies remotely. This run's history is honest about the fact that it took more than one try β€” two early plans errored before the stack came up clean. That's the subject of the next section.

HCP Terraform run history for week-12-dev showing 9 runs, 2 errored and 7 successful, with commit messages describing each fix
The real run history: 2 errors, then a fix for each, then a clean apply. Nothing here was staged for the blog.

Step 2 β€” Confirm the recorder is live

Everything downstream depends on this. The recorder records all 621 supported resource types, continuously, account-wide.

AWS Config settings page showing the customer managed recorder with recording on, continuous frequency, and 621 resource types
Recording is on: all resource types, continuous frequency, this Lab's own IAM role.

Step 3 β€” The conformance pack, deployed as one unit

All three rules and both remediation configs land as a single CREATE_COMPLETE conformance pack. This view β€” not a separate dashboard resource β€” is the compliance dashboard this week set out to build.

AWS Config conformance pack detail page showing 3 rules: required-tags noncompliant, s3-bucket-sse-enabled compliant, s3-bucket-versioning-enabled compliant
One pack, three rules, real compliance status β€” not INSUFFICIENT_DATA placeholders.

Step 4 β€” required-tags flags real drift, immediately

No synthetic test needed here: the account already had 22 real resources β€” EC2 instances, EBS volumes, S3 buckets from other projects β€” missing the mandatory tags. This rule found actual, pre-existing governance debt on its first evaluation.

AWS Config rule resources-in-scope table listing EC2 instances, EBS volumes, and S3 buckets all marked noncompliant
Real drift, not a demo: these resources actually exist in the account and actually lack the required tags.

Step 5 β€” Exercise the auto-remediation end to end

A small script (generate_misconfig.sh) creates a bucket tagged auto-remediate=true with no versioning or encryption configured, plus an untagged bucket to exercise the notify-only path. Config picked up the tagged bucket, flagged it non-compliant, and Systems Manager Automation ran the fix.

Systems Manager Automation execution detail for AWS-ConfigureS3BucketVersioning showing overall status Success and the ConfigureVersioning step succeeded
The actual remediation call: one step, one API call, Success.
S3 bucket properties page showing Bucket Versioning set to Enabled and a user-defined tag auto-remediate equal to true
Versioning is Enabled, no human touched the console. The auto-remediate=true tag is what made this bucket eligible.

Step 6 β€” The daily digest

compliance_reporter runs once a day, asks Config directly for the pack's own compliance status, and emails a plain-text summary.

CloudWatch log group for the compliance_reporter Lambda showing 4 log streams from successful invocations
Four clean invocations, no errors β€” the reporter running on schedule.
Amazon SNS topic detail page for the compliance digest showing one confirmed email subscription
The digest topic, one confirmed subscriber.
Email titled Week 12 Config Compliance Digest listing 22 non-compliant resources for required-tags and INSUFFICIENT_DATA for the two S3 rules at the time this digest ran
The actual email: real non-compliant resource IDs, not placeholder text. (Captured before the recorder incident below β€” the two S3 rules hadn't evaluated yet at this point.)

Challenges β€” What Actually Went Wrong

This week had an unusually dense run of real bugs β€” six worth writing down, none of them caught by terraform validate or reading documentation. Only an actual apply, an actual Lambda invoke, and an actual remediation attempt surfaced any of them.

1HCP's remote runners have no AWS credentials by default

The first plan on the brand-new week-12-dev workspace errored immediately: "No valid credential sources found". Obvious in hindsight β€” HCP's remote execution environment isn't running inside this AWS account, so nothing hands it credentials automatically.

Fix

Two workspace environment variables enable HCP's dynamic-credentials (OIDC) flow: TFC_AWS_PROVIDER_AUTH=true and TFC_AWS_RUN_ROLE_ARN=<role that trusts HCP's OIDC provider>. Every prior week's workspace already had these β€” this was simply the first time a brand-new workspace was created without copying them over. Now on the checklist for every future week.

2Config's Scope can't combine a resource-type filter with a tag filter

The second plan's apply failed creating the conformance pack: "Scope cannot be applied to both resource and tag". The two S3 rules' Scope block specified both ComplianceResourceTypes: [AWS::S3::Bucket] and TagKey/TagValue β€” a combination that looks reasonable and is explicitly documented as invalid.

Fix

Drop the resource-type filter and scope by tag alone. The underlying managed rule (S3_BUCKET_VERSIONING_ENABLED, for instance) only ever evaluates S3 buckets regardless of Scope's type filter, so nothing is lost. AWS's own Scope API docs state it plainly: one or more resource types, or a tag key/value pair, or one resource type plus one resource ID β€” never type-plus-tag together.

3A rule's real name isn't the name in the template

Once the pack deployed, its rules showed up as week12-required-tags-conformance-pack-qvc7a6yj1 β€” not the plain week12-required-tags declared in the template. AWS Config appends its own generated suffix to every rule inside a pack. The reporter Lambda's hardcoded rule-name list would have silently matched nothing, forever, without ever raising an error.

Fix

Don't hardcode rule names for anything inside a conformance pack. compliance_reporter now calls DescribeConformancePackCompliance to discover the real names at runtime β€” which also means it never needs updating if AWS ever changes the suffix scheme.

4boto3's Config client isn't called "configservice"

The first real Lambda invoke crashed instantly: UnknownServiceError: Unknown service: 'configservice'. The AWS CLI's command namespace for this service really is aws configservice ..., which made the wrong guess feel confident. boto3's client name is simply "config".

Fix

One-line fix, but only a real invoke caught it β€” py_compile and terraform validate have no way to know a boto3 service name is wrong.

5The account's Config recorder vanished mid-build β€” not from anything here

Partway through testing, every rule fell back to INSUFFICIENT_DATA and a brand-new test bucket simply never got discovered. describe-configuration-recorders came back empty β€” the recorder this stack had been reusing (borrowed from an unrelated project in the same account) was gone. CloudTrail confirmed it: StopConfigurationRecorder and DeleteConfigurationRecorder, timestamped minutes earlier, as part of unrelated cleanup on that other project. It also silently broke Week 11's already-published Security Hub FSBP controls, which depend on the same recorder existing.

Fix

This Lab now provisions and owns its own recorder (S3 delivery bucket, IAM role using the AWS_ConfigRole managed policy plus a bucket-write inline policy, the recorder, delivery channel, and status β€” nine resources total). One AWS account allows exactly one recorder per region, so this also fixed Week 11's dependency with zero changes to Week 11's own Terraform. The lesson generalizes: "reuse the account's centrally-managed recorder" is only safe if you actually own or control the thing you're calling centrally-managed β€” otherwise it's a dependency on someone else's cleanup schedule.

6S3 bucket-level actions silently ignore tag-based IAM conditions

With the recorder back, the versioning rule stayed NON_COMPLIANT for twelve straight minutes with no remediation firing. describe-remediation-execution-status showed a real, specific denial: "User: ...ssm-automation-role/Automation-... is not authorized to perform: s3:PutBucketVersioning ... because no identity-based policy allows the s3:PutBucketVersioning action" β€” despite the role's policy explicitly listing that exact action. The IAM statement also carried an aws:ResourceTag/auto-remediate condition, copied from Week 11's EC2 pattern. Amazon S3 only supports tag-based IAM conditions for object-level actions; a condition on a bucket-level admin action like PutBucketVersioning simply never evaluates true, so the whole statement silently granted nothing.

Fix

Drop the tag condition from the IAM policy entirely. The guardrail doesn't disappear β€” it's still fully enforced by Config's own Scope.TagKey/TagValue (challenge #2's fix), which means an untagged bucket is never flagged non-compliant in the first place, so remediation never runs against it regardless of IAM. The general lesson: a tag-scoped-IAM trick that works for one service (EC2 security groups, confirmed in Week 11) doesn't automatically transfer to another (S3 bucket admin calls) β€” verify per action, not per service.

Security β€” Guardrails on the Automation

Tag-scoped opt-in, enforced by Config alone

The two S3 rules only ever evaluate buckets tagged auto-remediate=true, via Config's native Scope. An untagged bucket is never flagged non-compliant, so remediation never fires for it β€” no custom code decides this.

Native remediation substrate, not custom code

Both fixes are AWS-managed SSM Automation documents, verified live against this account before use. A single documented API call needs nothing more elaborate than that.

Notify, never invent

required-tags never auto-tags a resource. A human has to supply the actual cost-center value β€” automation only ever reports what's missing.

No silent failures

Failed reporter invocations land in a 14-day SQS dead-letter queue that alarms to SNS, same pattern as every remediation-adjacent Lambda since Week 11.

Cost

Single account, us-east-1. Prices as of July 2026 β€” verify at aws.amazon.com/config/pricing.

ItemEstimate
Config rule + conformance pack evaluations ($0.001/eval, no free tier)pennies / month at this account's resource count
Config recorder β€” configuration items ($0.003/item, continuous, all resource types, account-wide)this Lab now owns the account's only recorder β€” bills for the whole account's inventory, not just this week's resources; roughly $1–5/month depending on total resource count and change frequency
Lambda + EventBridge + SNS + SQSpennies (well within free tier)
S3 delivery bucket (90-day lifecycle expiry)pennies
Destroyed$0 for this week's own resources β€” but destroying also removes the account's only recorder; check nothing else (Week 11's Security Hub FSBP controls) still needs it first

Cleanup

Queue a destroy plan on week-12-dev in the HCP UI. This removes the conformance pack, the reporter Lambda/schedule/SNS/DLQ, the SSM automation role β€” and the recorder itself, since this stack now owns the account's only one. Confirm nothing else depends on it first. Leftover test buckets clean up with ./scripts/cleanup.sh.

Key Takeaways

  • A conformance pack is self-contained. It defines its own rules rather than wrapping externally-created ones β€” build standalone Config rules or a conformance pack, never both for the same rule.
  • Config's Scope is either-or. Resource type filtering and tag filtering can't combine in one rule β€” use tag-only scoping when you need both a type and a tag constraint.
  • Never hardcode a name AWS generates for you. Conformance-pack rule names get a real, generated suffix β€” discover them at runtime instead.
  • A tag-scoped IAM guardrail doesn't automatically transfer between services. It works for EC2 security groups; it silently does nothing for S3 bucket-level admin actions. Verify per action.
  • "Reuse the centrally-managed recorder" is only safe if you actually control it. Otherwise it's a dependency on someone else's cleanup schedule β€” own the resources you depend on, or expect them to disappear without warning.

Comments

How was your experience?
Your feedback helps improve this site.
PoorExcellent