Skip to main content

Documentation Index

Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt

Use this file to discover all available pages before exploring further.

AWS Config Architecture

Module Overview

Estimated Time: 2-3 hours | Difficulty: Intermediate | Prerequisites: IAM, basic compliance concepts
AWS Config provides a detailed view of the configuration of AWS resources in your account. It continuously monitors and records configuration changes, and evaluates them against desired configurations using rules. Think of CloudTrail as the security camera (who did what) and AWS Config as the building inspector (is the current state of the building up to code?). CloudTrail tells you “Alice modified security group sg-123 at 3 PM.” Config tells you “security group sg-123 currently allows SSH from 0.0.0.0/0, which violates rule restricted-ssh.” Together, they answer both the “who changed it” and “is it compliant” questions.
Cost awareness: Config charges per configuration item recorded (~0.003each)plusperruleevaluation( 0.003 each) plus per rule evaluation (~0.001 each). In a large account with 10,000+ resources and 50 rules, this can reach $500-1,000/month. Use resource type scoping to record only what you need, especially in development accounts.
What You’ll Learn:
  • Config recorders and delivery channels
  • Managed and custom rules
  • Conformance packs for compliance
  • Automatic remediation
  • Advanced queries with Config Aggregator

How Config Works

┌─────────────────────────────────────────────────────────────────────────┐
│                        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

# Create S3 bucket for config snapshots.
# Config stores configuration history and snapshots here.
# Cost tip: a typical 100-resource account generates ~1-5 GB/month.
# Add S3 lifecycle rules to move old snapshots to Glacier after 90 days.
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.
# allSupported=true records ALL resource types -- the safe default.
# Common mistake: recording only specific types and missing something
# that causes a compliance gap during audit.
# includeGlobalResourceTypes=true captures IAM (global resources).
# Only enable this in ONE region to avoid duplicate recordings and double billing.
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

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. Think of managed rules as pre-built inspections — AWS maintains the evaluation logic and keeps it updated as services evolve. You only pay per evaluation (~$0.001 per rule evaluation per resource). Common mistake: enabling every managed rule on day one. Start with the 10-15 rules that map to your compliance framework (CIS, PCI, SOC2), then expand once your team builds the discipline to respond to findings. Fifty rules generating 200 NON_COMPLIANT findings that nobody investigates is worse than 10 rules with 100% follow-through.
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

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)

# 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.
    # The pattern: start optimistic (COMPLIANT) and flip to NON_COMPLIANT if any
    # check fails. This avoids accidentally marking resources as non-compliant
    # when they are simply not the resource type you care about.
    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
    }
# 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

# 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
# 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

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

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
  # Automatic remediation: when Config detects a non-compliant resource,
  # it triggers an SSM Automation document to fix it automatically.
  # Common mistake: enabling automatic remediation without testing first.
  # Always start with manual remediation (Automatic: false), observe for
  # a week, and confirm no false positives before enabling auto-remediation.
  # A misconfigured auto-remediation rule can break production resources.
  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

# 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

The Aggregator is like a central security desk that pulls compliance data from all your accounts and regions into one view. Without it, you would need to log into each account individually to check compliance — impractical beyond a handful of accounts. A senior engineer would say: “We use a Config Aggregator in our security account so the compliance team has a single pane of glass across all 50 accounts.” Cost tip: the Aggregator itself is free. You only pay for Config recordings and rule evaluations in each source account.
┌─────────────────────────────────────────────────────────────────────────┐
│                    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

# 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

-- 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

# 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

CloudTrail = Who did what, when (the security camera footage)
  • Records API calls
  • Answers: “Who deleted the S3 bucket at 2 AM?”
  • Focus: API activity auditing
AWS Config = What is the configuration (the building inspector report)
  • Records resource configurations over time
  • Answers: “Is this S3 bucket public? When did it become public?”
  • Focus: Configuration compliance and drift detection
They complement each other: Config gives you the diff, CloudTrail gives you the git blame. In an incident, use Config to see what changed and CloudTrail to see who changed it.
  1. Enable Config in all accounts and regions
  2. Deploy conformance packs via Organizations
  3. Create aggregator for centralized view
  4. Set up notifications for non-compliant resources
  5. Automate remediation with SSM documents
  6. Track trends with compliance dashboards
Options:
  1. Scope rules: Limit to specific resource types or tags
  2. Suppression: Use resource exceptions in rule
  3. Custom rules: Write Lambda for nuanced evaluation
  4. Tags: Exclude resources with specific tags
  5. 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