Module Overview
Estimated Time: 2-3 hours | Difficulty: Intermediate | Prerequisites: IAM, basic compliance concepts
- Config recorders and delivery channels
- Managed and custom rules
- Conformance packs for compliance
- Automatic remediation
- Advanced queries with Config Aggregator
How Config Works
Copy
┌─────────────────────────────────────────────────────────────────────────┐
│ AWS Config Architecture │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ AWS Resources Configuration Outputs │
│ ───────────── ───────────── ──────── │
│ │
│ ┌──────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ EC2 │─────────▶│ │────▶│ S3 Bucket │ │
│ │ Instances │ │ Config │ │ (History) │ │
│ └──────────────┘ │ Recorder │ └────────────────┘ │
│ │ │ │
│ ┌──────────────┐ │ Captures: │ ┌────────────────┐ │
│ │ Security │─────────▶│ - Current │────▶│ SNS Topic │ │
│ │ Groups │ │ Config │ │ (Notifications)│ │
│ └──────────────┘ │ - Changes │ └────────────────┘ │
│ │ - Relationships│ │
│ ┌──────────────┐ └────────┬────────┘ │
│ │ S3 Buckets │─────────▶ │ │
│ └──────────────┘ │ │
│ ▼ │
│ ┌──────────────┐ ┌─────────────────┐ ┌────────────────┐ │
│ │ IAM Roles │─────────▶│ Config Rules │────▶│ Compliance │ │
│ └──────────────┘ │ │ │ Dashboard │ │
│ │ Evaluates: │ └────────────────┘ │
│ ┌──────────────┐ │ COMPLIANT │ │
│ │ RDS │─────────▶│ NON_COMPLIANT │ ┌────────────────┐ │
│ │ Instances │ │ NOT_APPLICABLE │────▶│ Remediation │ │
│ └──────────────┘ └─────────────────┘ │ Actions │ │
│ └────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Enabling AWS Config
Console or CLI
Copy
# Create S3 bucket for config snapshots
aws s3 mb s3://my-config-bucket-123456789012
# Create IAM role for Config
aws iam create-role \
--role-name ConfigRole \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {"Service": "config.amazonaws.com"},
"Action": "sts:AssumeRole"
}]
}'
# Attach managed policy
aws iam attach-role-policy \
--role-name ConfigRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWS_ConfigRole
# Create configuration recorder
aws configservice put-configuration-recorder \
--configuration-recorder name=default,roleARN=arn:aws:iam::123456789012:role/ConfigRole \
--recording-group allSupported=true,includeGlobalResourceTypes=true
# Create delivery channel
aws configservice put-delivery-channel \
--delivery-channel name=default,s3BucketName=my-config-bucket-123456789012
# Start recording
aws configservice start-configuration-recorder --configuration-recorder-name default
CloudFormation
Copy
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS Config Setup
Resources:
ConfigBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${AWS::AccountId}-config-logs
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: true
BlockPublicPolicy: true
IgnorePublicAcls: true
RestrictPublicBuckets: true
ConfigBucketPolicy:
Type: AWS::S3::BucketPolicy
Properties:
Bucket: !Ref ConfigBucket
PolicyDocument:
Version: '2012-10-17'
Statement:
- Sid: AWSConfigBucketPermissionsCheck
Effect: Allow
Principal:
Service: config.amazonaws.com
Action: s3:GetBucketAcl
Resource: !GetAtt ConfigBucket.Arn
- Sid: AWSConfigBucketDelivery
Effect: Allow
Principal:
Service: config.amazonaws.com
Action: s3:PutObject
Resource: !Sub ${ConfigBucket.Arn}/*
Condition:
StringEquals:
s3:x-amz-acl: bucket-owner-full-control
ConfigRole:
Type: AWS::IAM::Role
Properties:
RoleName: AWSConfigRole
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: config.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWS_ConfigRole
ConfigRecorder:
Type: AWS::Config::ConfigurationRecorder
Properties:
Name: default
RoleARN: !GetAtt ConfigRole.Arn
RecordingGroup:
AllSupported: true
IncludeGlobalResourceTypes: true
ConfigDeliveryChannel:
Type: AWS::Config::DeliveryChannel
DependsOn: ConfigBucketPolicy
Properties:
Name: default
S3BucketName: !Ref ConfigBucket
ConfigSnapshotDeliveryProperties:
DeliveryFrequency: Six_Hours
Config Rules
Managed Rules
AWS provides 300+ managed rules:Copy
Common Managed Rules:
# Security
- s3-bucket-public-read-prohibited
- s3-bucket-public-write-prohibited
- s3-bucket-ssl-requests-only
- encrypted-volumes
- rds-storage-encrypted
- cloudtrail-enabled
- root-account-mfa-enabled
# IAM
- iam-user-mfa-enabled
- iam-password-policy
- iam-root-access-key-check
- access-keys-rotated
# Network
- restricted-ssh
- vpc-sg-open-only-to-authorized-ports
- vpc-default-security-group-closed
# Compute
- ec2-instance-managed-by-ssm
- ec2-instance-no-public-ip
- ebs-optimized-instance
# Database
- rds-multi-az-support
- db-instance-backup-enabled
- dynamodb-autoscaling-enabled
Create Managed Rule
Copy
Resources:
S3PublicReadProhibited:
Type: AWS::Config::ConfigRule
DependsOn: ConfigRecorder
Properties:
ConfigRuleName: s3-bucket-public-read-prohibited
Description: Checks that S3 buckets do not allow public read access
Source:
Owner: AWS
SourceIdentifier: S3_BUCKET_PUBLIC_READ_PROHIBITED
MaximumExecutionFrequency: TwentyFour_Hours
EncryptedVolumes:
Type: AWS::Config::ConfigRule
DependsOn: ConfigRecorder
Properties:
ConfigRuleName: encrypted-volumes
Description: Checks whether EBS volumes are encrypted
Source:
Owner: AWS
SourceIdentifier: ENCRYPTED_VOLUMES
RootAccountMFA:
Type: AWS::Config::ConfigRule
DependsOn: ConfigRecorder
Properties:
ConfigRuleName: root-account-mfa-enabled
Description: Checks whether root account has MFA enabled
Source:
Owner: AWS
SourceIdentifier: ROOT_ACCOUNT_MFA_ENABLED
MaximumExecutionFrequency: TwentyFour_Hours
Custom Rules (Lambda)
Copy
# custom_rule.py
import boto3
import json
def lambda_handler(event, context):
"""
Custom Config Rule: Check if EC2 instances have required tags
"""
config = boto3.client('config')
# Get configuration item
invoking_event = json.loads(event['invokingEvent'])
configuration_item = invoking_event['configurationItem']
# Rule parameters
rule_parameters = json.loads(event.get('ruleParameters', '{}'))
required_tags = rule_parameters.get('requiredTags', 'Environment,Owner,CostCenter').split(',')
# Evaluate compliance
compliance_type = 'COMPLIANT'
annotation = 'All required tags are present'
if configuration_item['resourceType'] == 'AWS::EC2::Instance':
tags = {t['key']: t['value'] for t in configuration_item.get('tags', [])}
missing_tags = [tag for tag in required_tags if tag not in tags]
if missing_tags:
compliance_type = 'NON_COMPLIANT'
annotation = f'Missing required tags: {", ".join(missing_tags)}'
# Put evaluation
config.put_evaluations(
Evaluations=[{
'ComplianceResourceType': configuration_item['resourceType'],
'ComplianceResourceId': configuration_item['resourceId'],
'ComplianceType': compliance_type,
'Annotation': annotation,
'OrderingTimestamp': configuration_item['configurationItemCaptureTime']
}],
ResultToken=event['resultToken']
)
return {
'compliance_type': compliance_type,
'annotation': annotation
}
Copy
# CloudFormation for custom rule
Resources:
CustomRuleLambda:
Type: AWS::Lambda::Function
Properties:
FunctionName: config-required-tags-rule
Runtime: python3.11
Handler: index.lambda_handler
Role: !GetAtt CustomRuleLambdaRole.Arn
Code:
ZipFile: |
# Lambda code here
CustomRuleLambdaRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSConfigRulesExecutionRole
LambdaConfigPermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref CustomRuleLambda
Action: lambda:InvokeFunction
Principal: config.amazonaws.com
RequiredTagsRule:
Type: AWS::Config::ConfigRule
DependsOn: ConfigRecorder
Properties:
ConfigRuleName: required-tags-rule
Description: Checks that EC2 instances have required tags
InputParameters:
requiredTags: "Environment,Owner,CostCenter"
Scope:
ComplianceResourceTypes:
- AWS::EC2::Instance
Source:
Owner: CUSTOM_LAMBDA
SourceIdentifier: !GetAtt CustomRuleLambda.Arn
SourceDetails:
- EventSource: aws.config
MessageType: ConfigurationItemChangeNotification
Conformance Packs
Deploy Conformance Pack
Copy
# conformance-pack-security.yaml
ConformancePackName: SecurityBestPractices
Description: Security baseline conformance pack
Resources:
# S3 Security
S3BucketPublicReadProhibited:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: s3-bucket-public-read-prohibited
Source:
Owner: AWS
SourceIdentifier: S3_BUCKET_PUBLIC_READ_PROHIBITED
S3BucketSSLRequestsOnly:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: s3-bucket-ssl-requests-only
Source:
Owner: AWS
SourceIdentifier: S3_BUCKET_SSL_REQUESTS_ONLY
# Encryption
EncryptedVolumes:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: encrypted-volumes
Source:
Owner: AWS
SourceIdentifier: ENCRYPTED_VOLUMES
RDSStorageEncrypted:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: rds-storage-encrypted
Source:
Owner: AWS
SourceIdentifier: RDS_STORAGE_ENCRYPTED
# IAM
IAMUserMFAEnabled:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: iam-user-mfa-enabled
Source:
Owner: AWS
SourceIdentifier: IAM_USER_MFA_ENABLED
RootAccountMFAEnabled:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: root-account-mfa-enabled
Source:
Owner: AWS
SourceIdentifier: ROOT_ACCOUNT_MFA_ENABLED
# Logging
CloudTrailEnabled:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: cloudtrail-enabled
Source:
Owner: AWS
SourceIdentifier: CLOUD_TRAIL_ENABLED
Copy
# Deploy conformance pack
aws configservice put-conformance-pack \
--conformance-pack-name SecurityBestPractices \
--template-body file://conformance-pack-security.yaml
# Get conformance pack status
aws configservice describe-conformance-pack-status \
--conformance-pack-names SecurityBestPractices
# Get compliance summary
aws configservice get-conformance-pack-compliance-summary \
--conformance-pack-names SecurityBestPractices
AWS Sample Conformance Packs
Copy
Available Sample Packs:
# Regulatory
- Operational-Best-Practices-for-PCI-DSS
- Operational-Best-Practices-for-HIPAA
- Operational-Best-Practices-for-NIST-CSF
- Operational-Best-Practices-for-CIS-Critical-Security-Controls
- Operational-Best-Practices-for-SOC2
# Security
- Operational-Best-Practices-for-Security-Services
- Operational-Best-Practices-for-Encryption-and-Keys
- Operational-Best-Practices-for-Logging
# AWS Services
- Operational-Best-Practices-for-Amazon-S3
- Operational-Best-Practices-for-Amazon-DynamoDB
- Operational-Best-Practices-for-AWS-Lambda
Automatic Remediation
Remediation with SSM
Copy
Resources:
# Rule to check if EBS volumes are encrypted
EncryptedVolumesRule:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: encrypted-volumes
Source:
Owner: AWS
SourceIdentifier: ENCRYPTED_VOLUMES
# Remediation action
EncryptedVolumesRemediation:
Type: AWS::Config::RemediationConfiguration
Properties:
ConfigRuleName: encrypted-volumes
TargetId: AWS-DisablePublicAccessForSecurityGroup # SSM Document
TargetType: SSM_DOCUMENT
Automatic: true
MaximumAutomaticAttempts: 5
RetryAttemptSeconds: 60
Parameters:
AutomationAssumeRole:
StaticValue:
Values:
- !GetAtt RemediationRole.Arn
# S3 public access remediation
S3PublicAccessRemediationRule:
Type: AWS::Config::ConfigRule
Properties:
ConfigRuleName: s3-bucket-public-read-prohibited
Source:
Owner: AWS
SourceIdentifier: S3_BUCKET_PUBLIC_READ_PROHIBITED
S3PublicAccessRemediation:
Type: AWS::Config::RemediationConfiguration
Properties:
ConfigRuleName: s3-bucket-public-read-prohibited
TargetId: AWS-DisableS3BucketPublicReadWrite
TargetType: SSM_DOCUMENT
Automatic: true
MaximumAutomaticAttempts: 3
RetryAttemptSeconds: 60
Parameters:
S3BucketName:
ResourceValue:
Value: RESOURCE_ID
AutomationAssumeRole:
StaticValue:
Values:
- !GetAtt RemediationRole.Arn
Custom Remediation Lambda
Copy
# remediation.py
import boto3
def lambda_handler(event, context):
"""
Custom remediation: Add required tags to EC2 instances
"""
ec2 = boto3.client('ec2')
resource_id = event['ResourceId'] # i-1234567890abcdef0
# Add default tags
ec2.create_tags(
Resources=[resource_id],
Tags=[
{'Key': 'Environment', 'Value': 'Unknown'},
{'Key': 'Owner', 'Value': 'Unassigned'},
{'Key': 'CostCenter', 'Value': 'Remediated'}
]
)
return {
'statusCode': 200,
'message': f'Added default tags to {resource_id}'
}
Config Aggregator
Multi-Account Aggregation
Copy
┌─────────────────────────────────────────────────────────────────────────┐
│ Config Aggregator Architecture │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ Central Security Account │ │
│ │ (Aggregator) │ │
│ │ │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ Aggregated Compliance View │ │ │
│ │ │ ─ All accounts, all regions │ │ │
│ │ │ ─ Cross-account compliance queries │ │ │
│ │ │ ─ Centralized dashboards │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ ▲ │
│ ┌─────────────┼─────────────┐ │
│ │ │ │ │
│ ┌─────┴─────┐ ┌─────┴─────┐ ┌─────┴─────┐ │
│ │ Account │ │ Account │ │ Account │ │
│ │ Dev │ │ Staging │ │ Prod │ │
│ │ │ │ │ │ │ │
│ │ Config + │ │ Config + │ │ Config + │ │
│ │ Rules │ │ Rules │ │ Rules │ │
│ └───────────┘ └───────────┘ └───────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Create Aggregator
Copy
# Create organization aggregator
aws configservice put-configuration-aggregator \
--configuration-aggregator-name org-aggregator \
--organization-aggregation-source '{
"RoleArn": "arn:aws:iam::123456789012:role/ConfigAggregatorRole",
"AwsRegions": ["us-east-1", "us-west-2", "eu-west-1"],
"AllAwsRegions": false
}'
# Or create account-based aggregator
aws configservice put-configuration-aggregator \
--configuration-aggregator-name account-aggregator \
--account-aggregation-sources '[{
"AccountIds": ["111111111111", "222222222222"],
"AllAwsRegions": true
}]'
Advanced Queries
Copy
-- Find all non-compliant resources across accounts
SELECT
accountId,
awsRegion,
resourceType,
resourceId,
configuration.complianceType
WHERE
resourceType = 'AWS::Config::ResourceCompliance'
AND configuration.complianceType = 'NON_COMPLIANT'
-- Find all public S3 buckets
SELECT
accountId,
resourceId,
resourceName
WHERE
resourceType = 'AWS::S3::Bucket'
AND configuration.publicAccessBlockConfiguration.blockPublicAcls = false
-- Find EC2 instances without required tags
SELECT
accountId,
resourceId,
tags
WHERE
resourceType = 'AWS::EC2::Instance'
AND tags.key != 'Environment'
-- Find unencrypted EBS volumes
SELECT
accountId,
awsRegion,
resourceId
WHERE
resourceType = 'AWS::EC2::Volume'
AND configuration.encrypted = false
-- Resource count by type
SELECT
resourceType,
COUNT(resourceId) as resourceCount
GROUP BY
resourceType
ORDER BY
resourceCount DESC
Configuration Timeline
Copy
# Get configuration history for a resource
aws configservice get-resource-config-history \
--resource-type AWS::EC2::SecurityGroup \
--resource-id sg-0123456789abcdef0 \
--limit 10
# Get relationship between resources
aws configservice get-aggregate-resource-config \
--configuration-aggregator-name org-aggregator \
--resource-identifier '{
"SourceAccountId": "123456789012",
"SourceRegion": "us-east-1",
"ResourceId": "sg-0123456789abcdef0",
"ResourceType": "AWS::EC2::SecurityGroup"
}'
Best Practices
Enable All Resources
Record all resource types for complete visibility
Use Conformance Packs
Deploy rules as conformance packs for consistency
Automate Remediation
Use SSM automation for automatic fixes
Centralize with Aggregator
Use Config Aggregator for multi-account visibility
🎯 Interview Questions
Q1: How does AWS Config differ from CloudTrail?
Q1: How does AWS Config differ from CloudTrail?
CloudTrail = Who did what, when
- Records API calls
- Answers: “Who deleted the S3 bucket?”
- Focus: API activity auditing
- Records resource configurations
- Answers: “Is this S3 bucket public?”
- Focus: Configuration compliance
Q2: How do you implement drift detection at scale?
Q2: How do you implement drift detection at scale?
- Enable Config in all accounts and regions
- Deploy conformance packs via Organizations
- Create aggregator for centralized view
- Set up notifications for non-compliant resources
- Automate remediation with SSM documents
- Track trends with compliance dashboards
Q3: How do you handle false positives in Config rules?
Q3: How do you handle false positives in Config rules?
Options:
- Scope rules: Limit to specific resource types or tags
- Suppression: Use resource exceptions in rule
- Custom rules: Write Lambda for nuanced evaluation
- Tags: Exclude resources with specific tags
- Document: Maintain exception registry
Hands-On Lab
1
Enable AWS Config
Set up Config recorder with S3 delivery channel
2
Deploy Managed Rules
Enable 5 common security rules
3
Create Custom Rule
Write Lambda-based rule for required tags
4
Set Up Remediation
Configure automatic remediation for S3 public access
5
Create Aggregator
Set up cross-account aggregator and run queries
Next Module
AWS Inspector
Automated vulnerability scanning for EC2, Lambda, and containers