AWS Config & governance
Config history, rules, auto-remediation.
Every airliner carries a flight recorder that writes down what the aircraft was doing, second by second, so investigators can replay it afterwards. AWS Config is that recorder bolted onto every resource in your account. It writes down the exact shape of each thing you run: the rules on a security group, the access control list on an S3 (Simple Storage Service) bucket, the wording of an IAM (Identity and Access Management) policy. Whenever one of those changes, Config writes a fresh entry and then asks whether the resource still matches the state you said you wanted. CloudTrail answers a different question, *who called which API (application programming interface)*. Config answers *what does this resource look like right now, what did it look like last Tuesday, and is it still allowed?* Answering that continuously, instead of once a quarter with a spreadsheet, is the ground that governance, drift detection and automatic repair are built on.
Configuration items: what actually gets recorded
The entry itself is called a configuration item, shortened everywhere to CI. One CI is a photograph of a single resource at a single moment: its settings, its links to other resources, its metadata, stamped with the time the photo was taken. The configuration recorder is the thing holding the camera. It lives in one region, belongs to one account, and there is exactly one of them per account per region. It fires a new CI every time a resource is created, changed or deleted. The delivery channel is the mail slot. It ships those CIs, plus periodic full snapshots, into an S3 bucket and optionally onto an SNS (Simple Notification Service) topic. Here is where people trip. Nothing is recorded until you create the recorder, point it at a bucket, and *start* it. Skip that last verb and you meet an empty history on the worst possible day, halfway through an incident.
# One-time: create the recorder, point a delivery channel at S3, then START it.aws configservice put-configuration-recorder \--configuration-recorder name=default,roleARN=arn:aws:iam::111122223333:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig \--recording-group '{"allSupported":true,"includeGlobalResourceTypes":false}'aws configservice put-delivery-channel \--delivery-channel '{"name":"default","s3BucketName":"config-bucket-111122223333"}'aws configservice start-configuration-recorder --configuration-recorder-name defaultaws configservice describe-configuration-recorder-status# {# "ConfigurationRecordersStatus": [# { "name": "default", "recording": true, "lastStatus": "SUCCESS",# "lastStartTime": "2026-07-14T09:12:03.456000+00:00" }# ]# }
Configuration history: answering "what changed?"
With recording on, get-resource-config-history hands you the whole timeline for one resource: every CI in order, each with its capture time. Finding out *who* did it takes one extra hop. The relatedEvents field used to carry the CloudTrail event IDs behind a change, but it has come back empty since configuration-item version 1.3, so you match the CI's capture time against CloudTrail's LookupEvents call (or an Athena query over the trail files) rather than reading the culprit straight off the CI. Fleet-wide questions have a different shape. You do not want to page through a thousand histories, so select-resource-config runs a SQL-like (structured query language) query against Config's latest snapshot. It costs nothing, there is no cluster to keep alive, and one call lists every unencrypted volume in the account.
# Timeline of one security group — every CI since recording began.aws configservice get-resource-config-history \--resource-type AWS::EC2::SecurityGroup --resource-id sg-0a1b2c3d4e5f --limit 2# {# "configurationItems": [# { "configurationItemCaptureTime": "2026-07-08T14:03:22Z",# "configurationItemStatus": "OK",# "resourceType": "AWS::EC2::SecurityGroup",# "relatedEvents": [], # empty since CI version 1.3 — correlate captureTime with CloudTrail LookupEvents# "configuration": "{...ipPermissions: 0.0.0.0/0:22...}" }# ]# }# Ad-hoc fleet-wide query (free; runs against Config's snapshot):aws configservice select-resource-config \--expression "SELECT resourceId, resourceName WHERE resourceType = 'AWS::EC2::Volume' AND configuration.encrypted = false"# { "Results": ["{\"resourceId\":\"vol-0f3e9a...\",\"resourceName\":\"data-01\"}"], ... }
Config rules: checks that run themselves
A Config rule is a standing question about your resources. It compares each one against the state you want and stamps it COMPLIANT, NON_COMPLIANT, NOT_APPLICABLE or INSUFFICIENT_DATA. Managed rules are checks AWS already wrote. You switch one on by naming its SourceIdentifier, and there are hundreds of them. Custom rules run your own Lambda function or a CloudFormation Guard policy, for the standards only your company cares about. Rules fire in one of two ways. *Change-triggered* rules re-evaluate the moment a matching CI lands. *Periodic* rules run on a clock, every 1 to 24 hours, which is why a resource you fixed five minutes ago can still show red until the next pass. One trade-off is worth knowing before you promise anyone a self-healing account: Config can spot almost anything, but it cannot repair everything. You cannot encrypt an existing EBS (Elastic Block Store) volume in place, so ENCRYPTED_VOLUMES is a detect-and-shout rule. A public S3 bucket, by contrast, can be closed with an API call.
# Managed rule: flag any S3 bucket that allows public read.aws configservice put-config-rule --config-rule '{"ConfigRuleName": "s3-bucket-public-read-prohibited","Source": { "Owner": "AWS", "SourceIdentifier": "S3_BUCKET_PUBLIC_READ_PROHIBITED" }}'aws configservice describe-compliance-by-config-rule \--config-rule-names s3-bucket-public-read-prohibited# {# "ComplianceByConfigRules": [# { "ConfigRuleName": "s3-bucket-public-read-prohibited",# "Compliance": { "ComplianceType": "NON_COMPLIANT",# "ComplianceContributorCount": { "CappedCount": 1, "CapExceeded": false } } }# ]# }
Auto-remediation: closing the loop
put-remediation-configurations ties a rule to an SSM (Systems Manager) Automation document, which is a pre-written script that runs against a resource. The id of the offending resource is handed to that document through a ResourceValue of RESOURCE_ID, and the document runs under whatever role you pass as AutomationAssumeRole. Two details will save you an afternoon of debugging. First, Config calls Systems Manager itself; you do not need an EventBridge rule or a Lambda in the middle for the managed remediation path. Second, Automatic defaults to false. Attach a remediation, leave that field alone, and nothing happens on its own. The fix runs only when a human types start-remediation-execution. Set Automatic: true and give the assume-role exactly the permissions the document needs (nothing wider, since this role can rewrite production resources unattended), and clear-cut violations are corrected seconds after they are spotted. MaximumAutomaticAttempts is your brake against a fix that fails and retries forever.
# Attach automatic remediation — Automatic defaults to false, so set it true.aws configservice put-remediation-configurations --remediation-configurations '[{"ConfigRuleName": "s3-bucket-public-read-prohibited","TargetType": "SSM_DOCUMENT","TargetId": "AWS-DisableS3BucketPublicReadWrite","TargetVersion": "1","Parameters": {"AutomationAssumeRole": { "StaticValue": { "Values": ["arn:aws:iam::111122223333:role/ConfigRemediationRole"] } },"S3BucketName": { "ResourceValue": { "Value": "RESOURCE_ID" } }},"Automatic": true,"MaximumAutomaticAttempts": 5,"RetryAttemptSeconds": 60}]'aws configservice describe-remediation-execution-status \--config-rule-name s3-bucket-public-read-prohibited# {# "RemediationExecutionStatuses": [# { "ResourceKey": { "resourceType": "AWS::S3::Bucket", "resourceId": "prod-assets-public" },# "State": "SUCCEEDED",# "InvocationTime": "2026-07-14T09:20:11+00:00" }# ]# }
Governance at scale: conformance packs, aggregators and cost
A conformance pack is a boxed set: many rules and their remediations bundled into one deployable unit. AWS publishes sample packs for CIS (the Center for Internet Security benchmarks), PCI-DSS (the Payment Card Industry Data Security Standard) and FedRAMP (the US government's cloud authorization program), and you push one out to a whole Organization with StackSets so every account enforces the same baseline. A configuration aggregator is the read-only window onto the result. One call to describe-aggregate-compliance-by-config-rules returns non-compliant resources across every account and every region, so nobody signs into consoles one at a time. Keep an eye on the meter while you do this. In US regions Config charges roughly $0.003 per configuration item recorded, $0.001 per rule evaluation for the first 100k per region per month, and $0.001 per conformance-pack evaluation for the first 100k. The default quota is 150 Config rules per region, raisable through Service Quotas, and you still get one recorder per region. Plan the estate around those ceilings instead of discovering them in production.
# Deploy a whole framework's checks (+ remediations) as one unit:aws configservice put-conformance-pack \--conformance-pack-name Operational-Best-Practices-for-CIS \--template-s3-uri s3://config-templates-111122223333/cis-benchmark.yaml \--delivery-s3-bucket awsconfigconforms-111122223333# { "ConformancePackArn": "arn:aws:config:us-east-1:111122223333:conformance-pack/Operational-Best-Practices-for-CIS/cp-a1b2c3" }# Org-wide compliance from one aggregator (read-only, cross-account):aws configservice describe-aggregate-compliance-by-config-rules \--configuration-aggregator-name org-agg \--filters ComplianceType=NON_COMPLIANT# {# "AggregateComplianceByConfigRules": [# { "ConfigRuleName": "s3-bucket-public-read-prohibited",# "AccountId": "444455556666", "AwsRegion": "us-east-1",# "Compliance": { "ComplianceType": "NON_COMPLIANT" } }# ]# }
allSupported recording in a busy account, one with Auto Scaling groups cycling instances all day, a new Lambda version on every deploy, spot fleets appearing and vanishing, and you can produce millions of CIs a month. Global resource types make it worse. Recording IAM and WAF (Web Application Firewall) objects in *every* region stores the same CI once per region. Enable includeGlobalResourceTypes in exactly one region, use the daily periodic recording mode or an exclusion list for the noisy types, and read the Config line on your bill before you roll this out across the Organization.Config is inventory and judgement in one service. If the recorder is not covering a resource type, every rule about that type is evaluating fiction, and it will happily report clean. The delivery channel into S3 is what leaves you something to mine six months later, when someone asks which day a bucket policy loosened.
Managed rules already handle the boring, common failures: public buckets, unencrypted volumes, security groups open to the world. Custom Lambda rules are for standards that exist only inside your company, like a mandatory cost-center tag or an approved AMI (Amazon Machine Image) list. Conformance packs are how you ship a whole baseline in one move, and an aggregator is how you read the answers back.
The loop is worth spelling out once. A bucket goes public, the rule flips to NON_COMPLIANT, an SSM Automation document blocks public access, and a fresh configuration item records the closed state. Run it in notify-only mode first. Flip it to automatic once a week or two of real findings has convinced you the rule is not crying wolf.
Cost tracks two numbers: how many resources you record and how many evaluations you run. Strip out the resource types you have no intention of governing. In an estate with ten thousand instances, recording everything "in case we need it" is a decision with a monthly invoice attached to it.
Keep the division of labour straight in your head. Config tells you what a resource looks like and whether that is allowed. CloudTrail tells you who changed it. You want both, because drift without attribution is a mystery novel missing the chapter that names the culprit.
Automatic fixes should be boring by design. Turning public access back off on a lab bucket is boring. Deleting a rule from a production security group is not, and that one should wake a person instead. Write down which rules notify, which remediate and which only report, then keep that list beside the on-call handbook where somebody will actually find it.
Aggregators and Organization-wide conformance packs are how one security account sees every member account without a password manager full of console logins. Wire them up early if you already run Organizations. Bolting them on after a breach is how findings arrive as a spreadsheet attached to an email.
Start with a handful of managed rules aimed at risks you genuinely have: public S3, unencrypted EBS, security groups open to 0.0.0.0/0. Write custom Lambda rules later, if ever. Noise teaches people to ignore dashboards, and a short list that pages when it matters beats a full CIS pack nobody opens.
Auditors ask three questions: what exists, what changed, and does it still match policy. Config answers all three without anyone rebuilding the story by hand. Turn it on only during audit season and you are left reconstructing history from CloudTrail alone, missing the configuration item timeline that makes root cause quick.
Try this
Read before you write. Check whether the recorder is actually running, then look at what your rules currently believe.
aws configservice describe-configuration-recorders --query 'ConfigurationRecorders[].{Name:name,On:recordingGroup.allSupported}' --output tableaws configservice describe-configuration-recorder-status --output tableaws configservice describe-compliance-by-config-rule \--query 'ComplianceByConfigRules[:5].{Rule:ConfigRuleName,Status:Compliance.ComplianceType}' --output table
default | True-----------------------------| RecorderStatus | Recording|+----------------+----------+| default | True |+----------------+----------+s3-bucket-public-read-prohibited | COMPLIANTencrypted-volumes | NON_COMPLIANT
Takeaway
Config records the state, rules judge the state, remediation can repair it. Governance here is a loop that runs all day, not a spreadsheet somebody fills in each quarter.
Next step in your own account: switch on one managed rule with an SNS notification, watch what it reports for a week, and add the SSM remediation only once you trust the signal.
Automatic at false unless you say otherwise, so set it to true and give the AutomationAssumeRole the permissions the document needs.Config tells you a resource has *drifted* away from the shape you asked for. The next lesson, CloudWatch metrics, logs & alarms, is how you know a resource is *misbehaving*: turning the raw telemetry those same resources emit into metrics, dashboards and alarms that page a human before drift, or a bad deploy, turns into downtime.