The situation
Acme’s platform team runs an AWS Organization with an organisational unit layout that mirrors the business: a Platform OU for shared-services accounts, a Workload OU with sub-OUs for each business domain (Payments, Catalogue, Fulfilment), and a Sandbox OU for experimentation. Twenty-two member accounts in total, four of them in Platform, sixteen across the workload OUs, two sandboxes.
The security team has a list. S3 buckets must block public access and have default encryption. EBS volumes must be encrypted. RDS instances must have backups enabled, deletion protection on, and storage encryption. IAM must not have root access keys or console passwords without MFA. CloudTrail must be enabled in every Region. Security groups must not allow 0.0.0.0/0 on 22 or 3389. The list runs to about forty items.
The audit ask: a single, dated, exportable report per quarter that tells the auditor “here’s the compliance state of every resource in every account, against these forty rules.” Today that report is assembled by hand from twenty-two Config dashboards.
Three things need to change. The rules need to be defined once, deployed uniformly, and kept in sync. The compliance data needs to aggregate to one place. And for the rules where it’s safe to do so, non-compliant resources need to be remediated automatically, not just reported.
What actually matters
Before reaching for a specific service, it’s worth being explicit about the shape of a “uniform compliance across many accounts” problem and which design choices end up mattering most.
The first thing worth thinking about is the unit of definition versus the unit of deployment. Forty rules can be defined once and deployed forty times, or bundled and deployed once. The difference looks cosmetic until somebody changes rule number twenty-three: with separate rules, that change has to fan out into twenty-two accounts and somebody has to verify it landed in all of them. With a bundle, the change is one diff, one deploy, and the fan-out is the platform’s problem instead of an operator’s. Picking the bundle as the unit makes the file under version control the source of truth; picking individual rules makes whichever console somebody last clicked the source of truth.
The second is grouped reporting versus per-rule reporting. The auditor’s question is “is the estate compliant with the forty-rule control set?”, not “is rule number seventeen compliant?”. A reporting layer that aggregates by control set produces an answer the auditor recognises; one that only reports per rule forces somebody to assemble the rollup by hand. The same goes for the dashboard the platform team uses day-to-day, a pack-level summary is the thing humans look at; per-rule status is the drill-down.
The third is who owns the rules and who owns the accounts. The platform-and-security team owns the rule set; the workload teams own their own accounts. The deployment model has to let one team push controls into accounts without those accounts being able to disable them, and without giving the central team a god-mode IAM presence in every workload account. Trusted access from the org root plus a delegated administrator role is the AWS-native pattern; it keeps the management account out of day-to-day operations and gives the security account exactly the permissions it needs.
The fourth is drift resilience. If a member account modifies or deletes the rule, the control has to reassert; otherwise compliance erodes silently. Whatever distributes the rules also needs to own their lifecycle in the target accounts, so a local “delete” is treated as a config-drift event, not as the new desired state.
The fifth is cross-account aggregation. Twenty-two accounts produce twenty-two compliance datasets unless something pulls them into one place. The auditor wants one table; the security team wants one query. Aggregation needs to be a first-class feature of the chosen mechanism, not a bolted-on script that runs once a quarter and is the first thing to break.
The sixth is remediation appetite. “Report only” is one stance; “auto-fix where it’s safe” is another. The decision is per rule, not estate-wide: enabling bucket encryption is idempotent and low-risk; deleting an unapproved EC2 instance is not. Whatever runs the controls also needs a per-rule remediation hook, opt-in, with retry/back-off and human-approval modes, so the safe fixes auto-fix and the dangerous ones flag and wait.
The seventh is sandbox escape. Two of the twenty-two accounts are sandboxes; engineers need room to break things. The control mechanism has to support targeted exclusions visible in one place, not “the sandboxes have different rules than everyone else and nobody knows where that’s configured.”
What we’ll filter on
- One-time deploy vs organisation-wide fan-out, are we configuring 1 account or 22?
- Grouped reporting, is the compliance status per-rule or per-pack?
- Auto-remediation support, can a non-compliant resource be fixed, not just flagged?
- Cross-account aggregation, does a single query span every account?
- Drift resilience, if a member account modifies the rule, does the control reassert?
- Templating and version control, is the definition in a file we review and diff?
The Config deployment landscape
-
Per-account managed rules via console. An operator opens Config in each member account and toggles on the forty rules. Cheap to start, fails every attribute after the first.
-
Per-account rules deployed by CloudFormation StackSets. A single stack set deploys the forty rules to every target account. Version-controlled, uniform, and repeatable. Compliance is still per-rule though; there’s no “pack” grouping to report against. No built-in remediation without extra StackSet work.
-
Conformance pack, deployed per account. The forty rules are a pack, deployed into each account individually via
PutConformancePackor the console. Grouped reporting lands: a singleCompliancePackComplianceSummaryper account. Remediation is part of the pack template. Still twenty-two deploys. -
Organisation conformance pack. The same pack, deployed once from the management account (or delegated admin) across a named OU list. Accounts joining the OU pick it up automatically; accounts leaving drop out. The definitive answer when the question is “this set of rules, uniform across the estate.”
-
AWS Control Tower detective/preventive controls. The managed-service layer that wraps Organizations + Config + SCPs. Useful if Control Tower already owns the landing zone; otherwise an organisation conformance pack does the same job with less lock-in.
-
Security Hub controls. Security Hub’s own controls evaluate against standards like AWS Foundational Security Best Practices. They’re Config rules underneath but managed through Security Hub. Good for the AWS-curated set; less good when the rule list is “our” list rather than the foundational one.
Side by side
| Option | Org-wide fan-out | Grouped reporting | Auto-remediation | Cross-account aggregation | Drift resilience | Templated |
|---|---|---|---|---|---|---|
| Per-account console rules | ✗ | ✗ | ✗ | ✗ | ✗ | ✗ |
| StackSets-deployed rules | ✓ | ✗ | Manual per rule | Via aggregator | ✓ | ✓ |
| Per-account conformance pack | ✗ | ✓ | ✓ | Via aggregator | ✓ | ✓ |
| Organisation conformance pack | ✓ | ✓ | ✓ | Via aggregator | ✓ | ✓ |
| Control Tower controls | ✓ | ✓ | Limited | ✓ | ✓ | ✗ (managed) |
| Security Hub controls | ✓ | ✓ | Via Hub | ✓ | ✓ | ✗ (curated) |
The organisation conformance pack is the row that ticks every column while leaving the rule list in a file we can review. Control Tower wins if Control Tower is already the floor; Security Hub wins if the rule list matches AWS’s foundational set. For “our forty rules across our org,” the pack is the answer.
How the pack lands across the org
The pack in depth
A conformance pack template is CloudFormation-flavoured YAML with two top-level shapes: Parameters and Resources. The Resources are Config rules (AWS::Config::ConfigRule), remediation configurations (AWS::Config::RemediationConfiguration), and optional parameters. A short excerpt from the pack Acme would deploy:
Resources:
S3BucketPublicReadProhibited:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: s3-bucket-public-read-prohibited
Source:
Owner: AWS
SourceIdentifier: S3_BUCKET_PUBLIC_READ_PROHIBITED
Scope:
ComplianceResourceTypes: [ 'AWS::S3::Bucket' ]
S3DefaultEncryption:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: s3-bucket-server-side-encryption-enabled
Source:
Owner: AWS
SourceIdentifier: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED
RemediateS3Encryption:
Type: AWS::Config::RemediationConfiguration
Properties:
ConfigRuleName: s3-bucket-server-side-encryption-enabled
TargetType: SSM_DOCUMENT
TargetId: AWS-EnableS3BucketEncryption
TargetVersion: '1'
Automatic: true
MaximumAutomaticAttempts: 3
RetryAttemptSeconds: 60
Parameters:
AutomationAssumeRole:
StaticValue:
Values:
- 'arn:aws:iam::{{AWS::AccountId}}:role/ConfigRemediationRole'
BucketName:
ResourceValue:
Value: RESOURCE_ID
SSEAlgorithm:
StaticValue:
Values: [ 'AES256' ]
IamRootAccessKeyCheck:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: iam-root-access-key-check
Source:
Owner: AWS
SourceIdentifier: IAM_ROOT_ACCESS_KEY_CHECK
MaximumExecutionFrequency: TwentyFour_Hours
Three things earn their keep. Each ConfigRule resource is identical to what a per-account rule would be, which means anyone already familiar with Config rules can read the pack. The RemediationConfiguration attaches an SSM Automation runbook (AWS-EnableS3BucketEncryption) to the rule, says “Automatic: true” (remediate without human input), caps retries at 3, and passes the non-compliant resource’s ID as the BucketName parameter via the RESOURCE_ID placeholder. And {{AWS::AccountId}} is the pack’s own templating, each member account gets the runbook invoked with its own account ID substituted, so the assume-role reference is correct everywhere.
The pack is deployed via PutOrganizationConformancePack from the delegated-admin Security account:
aws configservice put-organization-conformance-pack \
--organization-conformance-pack-name acme-core-compliance \
--template-body file://acme-core.yaml \
--delivery-s3-bucket config-conformance-pack-delivery \
--excluded-accounts 555555555555 666666666666
--excluded-accounts is the sandbox escape valve. delivery-s3-bucket is where Config stages the rendered template before applying it per account, the bucket needs a policy allowing config-multiaccountsetup.amazonaws.com to write.
IAM story at the edges: the delegated-admin account needs config:Put*OrganizationConformancePack and organizations:Describe* / organizations:ListAccounts. The management account enables trusted access for Config (organizations:EnableAWSServiceAccess for config.amazonaws.com and config-multiaccountsetup.amazonaws.com) and registers the Security account as delegated admin. Each member account gets, automatically as part of the pack deploy, an IAM role named aws-service-role/AWSServiceRoleForConfigConforms that Config uses to provision the rules.
Remediation in depth
The Config remediation model is narrow and worth understanding precisely. A rule evaluates a resource and emits a compliance result. If a RemediationConfiguration is attached and the result is NON_COMPLIANT, Config triggers the target (an SSM Automation runbook) with the non-compliant resource’s ID substituted into the parameters. Success means the next evaluation comes back COMPLIANT. Failure means the remediation is retried up to MaximumAutomaticAttempts, spaced by RetryAttemptSeconds.
Two knobs earn thought per rule. Automatic: true means remediate without human approval; false means the rule reports a non-compliant resource and an operator has to press the “Remediate” button in the console. High-risk rules, anything that might delete a resource, detach a role, or fail open, belong on false. Low-risk idempotent rules (enable bucket encryption, turn on deletion protection) belong on true.
MaximumAutomaticAttempts and RetryAttemptSeconds are the back-off. A bucket whose policy gets rewritten by a drifty CloudFormation stack every 30 seconds will consume remediation attempts indefinitely; capping at 3 attempts and looking at the failure in the console is the correct answer.
Not every rule has a shippable remediation. iam-root-access-key-check doesn’t have an AWS-Delete... runbook, because deleting the root account’s access key without a human present is not a decision a runbook should be trusted with. The pack can include the rule without a RemediationConfiguration; the compliance report flags it, and a human follows up.
A worked audit cycle
End of Q2, one quarter after the pack has been live. The auditor’s ask is the same as before: “show me, for every account, the compliance state against each of the forty controls, and the remediation history.”
The Security account’s Config aggregator has been pulling compliance data from all 22 accounts continuously. One Advanced Query answers the first half:
SELECT
accountId,
configRuleName,
complianceType,
COUNT(*) as resourceCount
FROM
aws_config_aggregator
WHERE
complianceType = 'NON_COMPLIANT'
GROUP BY
accountId, configRuleName, complianceType
The result is a table by account and rule: thirteen non-compliant S3 buckets in cat-dev (pack applied ten minutes before the query, remediation already in flight); zero non-compliant RDS in production; one non-compliant security group in ful-stg whose 0.0.0.0/0:22 rule was added by a contractor two days earlier and remediated within the hour by AWS-DisableSecurityGroupIngressRulesForSshOnAnyPort.
The remediation history is in CloudTrail and in each account’s Config RemediationExceptions / executions API. Across the estate: 412 remediation attempts that quarter, 398 successful, 14 failed and escalated to the rule owner, zero unauthenticated or unauthorised. The 14 failures are the interesting part of the audit meeting, not the 398 successes.
Export: a scheduled Athena query over the Config S3 snapshot, written to a timestamped S3 object, signed, handed to the auditor. The report is a file, not a spreadsheet.
What’s worth remembering
- Config has two halves. Resource history is the configuration-item store; rules are the evaluator on top. Conformance packs are a bundle of rules.
- An organisation conformance pack is the one to reach for when the rule set is “our” rule set. One definition in YAML, deployed uniformly across named OUs from a delegated-admin account, with member accounts kept in sync as they join and leave.
- Delegate Config administration to a dedicated security account. The management account enables trusted access; the security account owns the pack, the aggregator, and the remediation runbooks.
- Pack templates are CloudFormation-Guard flavoured.
AWS::Config::ConfigRuleandAWS::Config::RemediationConfigurationresources, with{{AWS::AccountId}}templating for per-account substitutions. - Remediation is opt-in per rule.
Automatic: truefor low-risk idempotent fixes (bucket encryption, deletion protection).Automatic: falsefor anything that might lose data. No remediation at all for rules whose fix is a human decision. - Aggregator is the reporting half. Authorise source accounts to an aggregator in the security account and a single Advanced Query spans the org. Without it, “show me every non-compliant S3 bucket” means twenty-two separate queries.
--excluded-accountsis the sandbox escape valve. Dev accounts that need room to experiment get excluded explicitly in the pack deployment. The exclusion is visible in one place.- Not every rule is worth remediating automatically.
IAM_ROOT_ACCESS_KEY_CHECKflags but doesn’t auto-fix, because the fix is a human decision. The pack still includes the rule; the audit still sees the finding; the human still follows up.
A conformance pack is the unit of compliance: forty rules reviewed as a file, deployed as a unit, reported as a bundle, and, where safe, remediated without a ticket. The organisation variant is the multiplier that turns 22 accounts into one control surface. The aggregator is how the answer comes back as one table instead of twenty-two dashboards. And the delegation pattern keeps the management account out of the day-to-day, where it belongs.