Skip to content

Monitoring, Security, and Incident Response

CloudWatch Monitoring

Core Metrics and Dimensions

EC2 Metrics - CPU Utilization, Network In/Out, Disk Read/Write - Status Check Failed (System, Instance) - Default: 5-minute intervals (1-minute with detailed monitoring) - Custom metrics via CloudWatch Agent

RDS Metrics - DatabaseConnections, FreeableMemory, FreeStorageSpace - ReadIOPS, WriteIOPS, ReadLatency, WriteLatency - CPUUtilization, SwapUsage - Enhanced Monitoring for OS-level metrics

ELB/ALB Metrics - RequestCount, TargetResponseTime - HealthyHostCount, UnHealthyHostCount - HTTPCode_Target_4XX_Count, HTTPCode_Target_5XX_Count - ActiveConnectionCount, TargetConnectionErrorCount

Lambda Metrics - Invocations, Duration, Errors, Throttles - ConcurrentExecutions, DeadLetterErrors - IteratorAge (for stream-based invocations)

CloudWatch Agent Configuration

{
  "agent": {
    "metrics_collection_interval": 60,
    "region": "us-east-1",
    "logfile": "/opt/aws/amazon-cloudwatch-agent/logs/amazon-cloudwatch-agent.log"
  },
  "metrics": {
    "namespace": "CustomApp",
    "metrics_collected": {
      "cpu": {
        "measurement": [
          {
            "name": "cpu_usage_idle",
            "rename": "CPU_IDLE",
            "unit": "Percent"
          },
          "cpu_usage_iowait"
        ],
        "metrics_collection_interval": 60,
        "totalcpu": false
      },
      "disk": {
        "measurement": [
          {
            "name": "used_percent",
            "rename": "DISK_USED",
            "unit": "Percent"
          }
        ],
        "metrics_collection_interval": 60,
        "resources": [
          "*"
        ]
      },
      "mem": {
        "measurement": [
          {
            "name": "mem_used_percent",
            "rename": "MEM_USED",
            "unit": "Percent"
          }
        ],
        "metrics_collection_interval": 60
      },
      "statsd": {
        "service_address": ":8125",
        "metrics_collection_interval": 10,
        "metrics_aggregation_interval": 60
      }
    }
  },
  "logs": {
    "logs_collected": {
      "files": {
        "collect_list": [
          {
            "file_path": "/var/log/app/application.log",
            "log_group_name": "/aws/ec2/application",
            "log_stream_name": "{instance_id}",
            "retention_in_days": 7,
            "timezone": "UTC"
          },
          {
            "file_path": "/var/log/nginx/error.log",
            "log_group_name": "/aws/ec2/nginx",
            "log_stream_name": "{instance_id}/error"
          }
        ]
      },
      "windows_events": {
        "collect_list": [
          {
            "event_name": "System",
            "event_levels": ["ERROR", "CRITICAL"],
            "log_group_name": "windows-system-events",
            "log_stream_name": "{instance_id}"
          }
        ]
      }
    }
  }
}

Custom Metrics

import boto3
from datetime import datetime

cloudwatch = boto3.client('cloudwatch')

# Put single metric
cloudwatch.put_metric_data(
    Namespace='MyApp',
    MetricData=[
        {
            'MetricName': 'OrdersProcessed',
            'Value': 42,
            'Unit': 'Count',
            'Timestamp': datetime.utcnow(),
            'Dimensions': [
                {
                    'Name': 'Environment',
                    'Value': 'Production'
                },
                {
                    'Name': 'Region',
                    'Value': 'us-east-1'
                }
            ]
        }
    ]
)

# Put multiple metrics
cloudwatch.put_metric_data(
    Namespace='MyApp',
    MetricData=[
        {
            'MetricName': 'ProcessingTime',
            'Value': 125.5,
            'Unit': 'Milliseconds',
            'StorageResolution': 1  # High-resolution (1-second)
        },
        {
            'MetricName': 'ErrorRate',
            'Value': 0.02,
            'Unit': 'Percent'
        }
    ]
)

# Metric with statistics
cloudwatch.put_metric_data(
    Namespace='MyApp',
    MetricData=[
        {
            'MetricName': 'RequestLatency',
            'StatisticValues': {
                'SampleCount': 1000,
                'Sum': 50000,
                'Minimum': 10,
                'Maximum': 500
            },
            'Unit': 'Milliseconds'
        }
    ]
)

CloudWatch Alarms

Comprehensive Alarm Configuration

Resources:
  HighCPUAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: HighCPUUtilization
      AlarmDescription: Alert when CPU exceeds 80%
      MetricName: CPUUtilization
      Namespace: AWS/EC2
      Statistic: Average
      Period: 300
      EvaluationPeriods: 2
      Threshold: 80
      ComparisonOperator: GreaterThanThreshold
      Dimensions:
        - Name: InstanceId
          Value: !Ref Instance
      AlarmActions:
        - !Ref SNSTopic
        - !Ref ScalingPolicy
      OKActions:
        - !Ref SNSTopic
      TreatMissingData: breaching

  CompositeAlarm:
    Type: AWS::CloudWatch::CompositeAlarm
    Properties:
      AlarmName: HighErrorRateAndHighLatency
      AlarmDescription: Triggers when both error rate and latency are high
      AlarmRule: !Sub |
        (ALARM(${HighErrorRateAlarm}) AND ALARM(${HighLatencyAlarm}))
      ActionsEnabled: true
      AlarmActions:
        - !Ref CriticalAlertTopic

  AnomalyDetectorAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      AlarmName: AnomalousRequestCount
      ComparisonOperator: LessThanLowerOrGreaterThanUpperThreshold
      EvaluationPeriods: 2
      Metrics:
        - Id: m1
          ReturnData: true
          MetricStat:
            Metric:
              Namespace: AWS/ApplicationELB
              MetricName: RequestCount
              Dimensions:
                - Name: LoadBalancer
                  Value: !GetAtt LoadBalancer.LoadBalancerFullName
            Period: 300
            Stat: Sum
        - Id: ad1
          Expression: ANOMALY_DETECTION_BAND(m1, 2)
      ThresholdMetricId: ad1
      AlarmActions:
        - !Ref SNSTopic

CloudWatch Logs Insights

Query Examples

# Find errors in last hour
fields @timestamp, @message
| filter @message like /ERROR/
| sort @timestamp desc
| limit 100

# Count errors by type
fields @message
| filter @message like /ERROR/
| parse @message /ERROR: (?<errorType>.*?) -/
| stats count() by errorType
| sort count desc

# Analyze API latency
fields @timestamp, duration, statusCode, path
| filter statusCode >= 200
| stats avg(duration), max(duration), min(duration), count() by path
| sort avg(duration) desc

# Find slow queries
fields @timestamp, query, duration
| filter duration > 1000
| sort duration desc
| limit 20

# Track user activity
fields @timestamp, userId, action
| filter action in ["login", "logout", "purchase"]
| stats count() by userId, action

# Detect anomalies
fields @timestamp, requestCount
| stats avg(requestCount) as avg_requests, stddev(requestCount) as stddev_requests
| filter requestCount > (avg_requests + 2 * stddev_requests)

CloudWatch Dashboards

import json
import boto3

cloudwatch = boto3.client('cloudwatch')

dashboard_body = {
    "widgets": [
        {
            "type": "metric",
            "properties": {
                "metrics": [
                    ["AWS/EC2", "CPUUtilization", {"stat": "Average"}],
                    [".", ".", {"stat": "Maximum"}]
                ],
                "period": 300,
                "stat": "Average",
                "region": "us-east-1",
                "title": "EC2 CPU Utilization",
                "yAxis": {
                    "left": {"min": 0, "max": 100}
                }
            }
        },
        {
            "type": "log",
            "properties": {
                "query": """
                    SOURCE '/aws/lambda/my-function'
                    | fields @timestamp, @message
                    | filter @message like /ERROR/
                    | sort @timestamp desc
                    | limit 50
                """,
                "region": "us-east-1",
                "title": "Recent Errors"
            }
        },
        {
            "type": "metric",
            "properties": {
                "metrics": [
                    ["AWS/ApplicationELB", "TargetResponseTime",
                     {"stat": "Average", "label": "Avg Response Time"}],
                    ["...", {"stat": "p99", "label": "P99 Response Time"}]
                ],
                "view": "timeSeries",
                "stacked": false,
                "region": "us-east-1",
                "title": "ALB Response Times"
            }
        }
    ]
}

cloudwatch.put_dashboard(
    DashboardName='Production-Overview',
    DashboardBody=json.dumps(dashboard_body)
)

AWS X-Ray

Distributed Tracing

X-Ray SDK Integration

# Python Flask application
from aws_xray_sdk.core import xray_recorder
from aws_xray_sdk.ext.flask.middleware import XRayMiddleware
from flask import Flask

app = Flask(__name__)

# Configure X-Ray
xray_recorder.configure(
    service='my-api',
    context_missing='LOG_ERROR',
    plugins=('EC2Plugin', 'ECSPlugin'),
    sampling_rules='sampling-rules.json'
)

XRayMiddleware(app, xray_recorder)

@app.route('/api/orders')
@xray_recorder.capture('process_order')
def process_order():
    # Custom subsegment
    subsegment = xray_recorder.begin_subsegment('database_query')
    try:
        result = database.query('SELECT * FROM orders')
        subsegment.put_metadata('row_count', len(result))
        subsegment.put_annotation('query_type', 'select')
        return result
    except Exception as e:
        subsegment.add_exception(e)
        raise
    finally:
        xray_recorder.end_subsegment()

# Async operations
@xray_recorder.capture_async('async_operation')
async def fetch_user_data(user_id):
    async with aiohttp.ClientSession() as session:
        async with session.get(f'https://api.example.com/users/{user_id}') as response:
            return await response.json()

Sampling Rules

{
  "version": 2,
  "rules": [
    {
      "description": "High priority endpoints",
      "host": "*",
      "http_method": "*",
      "url_path": "/api/critical/*",
      "fixed_target": 10,
      "rate": 1.0
    },
    {
      "description": "Health checks - low sampling",
      "host": "*",
      "http_method": "GET",
      "url_path": "/health",
      "fixed_target": 0,
      "rate": 0.01
    },
    {
      "description": "Default rule",
      "host": "*",
      "http_method": "*",
      "url_path": "*",
      "fixed_target": 1,
      "rate": 0.05
    }
  ],
  "default": {
    "fixed_target": 1,
    "rate": 0.1
  }
}

EventBridge (CloudWatch Events)

Event Patterns and Rules

Resources:
  EC2StateChangeRule:
    Type: AWS::Events::Rule
    Properties:
      Name: EC2StateChangeNotification
      Description: Notify when EC2 instances stop
      EventPattern:
        source:
          - aws.ec2
        detail-type:
          - EC2 Instance State-change Notification
        detail:
          state:
            - stopped
            - terminated
      State: ENABLED
      Targets:
        - Arn: !GetAtt NotificationLambda.Arn
          Id: NotifyLambda
        - Arn: !Ref SNSTopic
          Id: SNSTarget

  CodePipelineFailureRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - aws.codepipeline
        detail-type:
          - CodePipeline Pipeline Execution State Change
        detail:
          state:
            - FAILED
          pipeline:
            - !Ref Pipeline
      Targets:
        - Arn: !GetAtt IncidentResponseLambda.Arn
          Id: IncidentResponse

  ScheduledRule:
    Type: AWS::Events::Rule
    Properties:
      ScheduleExpression: 'cron(0 2 * * ? *)'  # 2 AM daily
      State: ENABLED
      Targets:
        - Arn: !GetAtt BackupLambda.Arn
          Id: DailyBackup
          Input: |
            {
              "backupType": "full",
              "retention": 30
            }

  CustomEventRule:
    Type: AWS::Events::Rule
    Properties:
      EventPattern:
        source:
          - custom.myapp
        detail-type:
          - Order Placed
        detail:
          orderValue:
            - numeric:
              - ">"
              - 1000
      Targets:
        - Arn: !Ref PriorityQueue
          Id: HighValueOrders

Security Best Practices

IAM Roles and Policies

Least Privilege Policy Example

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowEC2ReadOnly",
      "Effect": "Allow",
      "Action": [
        "ec2:Describe*",
        "ec2:Get*",
        "ec2:List*"
      ],
      "Resource": "*"
    },
    {
      "Sid": "AllowSpecificInstanceActions",
      "Effect": "Allow",
      "Action": [
        "ec2:StartInstances",
        "ec2:StopInstances",
        "ec2:RebootInstances"
      ],
      "Resource": "arn:aws:ec2:*:*:instance/*",
      "Condition": {
        "StringEquals": {
          "ec2:ResourceTag/Environment": "Development"
        }
      }
    },
    {
      "Sid": "DenyInstanceTermination",
      "Effect": "Deny",
      "Action": "ec2:TerminateInstances",
      "Resource": "*",
      "Condition": {
        "StringEquals": {
          "ec2:ResourceTag/Protected": "true"
        }
      }
    }
  ]
}

Cross-Account Access

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::111111111111:root"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "unique-external-id-12345"
        },
        "IpAddress": {
          "aws:SourceIp": "203.0.113.0/24"
        }
      }
    }
  ]
}

Secrets Management

AWS Secrets Manager

import boto3
import json
from botocore.exceptions import ClientError

secrets = boto3.client('secretsmanager')

def get_secret(secret_name):
    """Retrieve secret from Secrets Manager with caching"""
    try:
        response = secrets.get_secret_value(SecretId=secret_name)

        if 'SecretString' in response:
            return json.loads(response['SecretString'])
        else:
            return base64.b64decode(response['SecretBinary'])
    except ClientError as e:
        if e.response['Error']['Code'] == 'ResourceNotFoundException':
            print(f"Secret {secret_name} not found")
        raise e

def rotate_secret(secret_name):
    """Trigger secret rotation"""
    response = secrets.rotate_secret(
        SecretId=secret_name,
        RotationLambdaARN='arn:aws:lambda:region:account:function:rotate-secret',
        RotationRules={
            'AutomaticallyAfterDays': 30
        }
    )
    return response

# Usage
db_credentials = get_secret('prod/database/credentials')
database_url = f"postgresql://{db_credentials['username']}:{db_credentials['password']}@{db_credentials['host']}:5432/mydb"

Parameter Store

import boto3

ssm = boto3.client('ssm')

# Store parameter
ssm.put_parameter(
    Name='/app/prod/db_password',
    Value='secure-password',
    Type='SecureString',
    KeyId='alias/aws/ssm',
    Tier='Standard',
    Tags=[
        {'Key': 'Environment', 'Value': 'Production'},
        {'Key': 'Application', 'Value': 'MyApp'}
    ]
)

# Get parameter
response = ssm.get_parameter(
    Name='/app/prod/db_password',
    WithDecryption=True
)
password = response['Parameter']['Value']

# Get parameters by path
response = ssm.get_parameters_by_path(
    Path='/app/prod/',
    Recursive=True,
    WithDecryption=True
)

config = {param['Name'].split('/')[-1]: param['Value']
          for param in response['Parameters']}

Encryption

S3 Bucket Encryption

Resources:
  EncryptedBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: my-encrypted-bucket
      BucketEncryption:
        ServerSideEncryptionConfiguration:
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: aws:kms
              KMSMasterKeyID: !GetAtt KMSKey.Arn
            BucketKeyEnabled: true
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true

  KMSKey:
    Type: AWS::KMS::Key
    Properties:
      Description: S3 encryption key
      KeyPolicy:
        Version: '2012-10-17'
        Statement:
          - Sid: Enable IAM policies
            Effect: Allow
            Principal:
              AWS: !Sub 'arn:aws:iam::${AWS::AccountId}:root'
            Action: 'kms:*'
            Resource: '*'
          - Sid: Allow S3 to use key
            Effect: Allow
            Principal:
              Service: s3.amazonaws.com
            Action:
              - 'kms:Decrypt'
              - 'kms:GenerateDataKey'
            Resource: '*'

Incident Response

Automated Incident Response

GuardDuty Finding Response

import boto3
import json

ec2 = boto3.client('ec2')
sns = boto3.client('sns')

def lambda_handler(event, context):
    """Respond to GuardDuty findings"""

    finding = event['detail']
    severity = finding['severity']
    finding_type = finding['type']

    if 'recon' in finding_type.lower():
        # Port scanning detected
        instance_id = finding['resource']['instanceDetails']['instanceId']

        # Isolate instance
        isolate_instance(instance_id)

        # Create snapshot for forensics
        create_forensic_snapshot(instance_id)

        # Notify security team
        notify_security_team(finding)

    elif 'trojan' in finding_type.lower() or 'backdoor' in finding_type.lower():
        # Malware detected
        instance_id = finding['resource']['instanceDetails']['instanceId']

        # Immediate isolation
        isolate_instance(instance_id)

        # Revoke IAM credentials
        instance_profile = get_instance_profile(instance_id)
        if instance_profile:
            disable_iam_credentials(instance_profile)

        # Escalate to critical
        escalate_incident(finding, 'CRITICAL')

def isolate_instance(instance_id):
    """Apply restrictive security group"""
    # Create forensics security group
    forensic_sg = ec2.create_security_group(
        GroupName=f'forensic-isolation-{instance_id}',
        Description='Isolation for incident response',
        VpcId=get_instance_vpc(instance_id)
    )

    # No inbound, only outbound to logging
    ec2.authorize_security_group_egress(
        GroupId=forensic_sg['GroupId'],
        IpPermissions=[
            {
                'IpProtocol': 'tcp',
                'FromPort': 443,
                'ToPort': 443,
                'IpRanges': [{'CidrIp': '0.0.0.0/0'}]
            }
        ]
    )

    # Apply to instance
    ec2.modify_instance_attribute(
        InstanceId=instance_id,
        Groups=[forensic_sg['GroupId']]
    )

def create_forensic_snapshot(instance_id):
    """Create EBS snapshots for forensic analysis"""
    volumes = ec2.describe_volumes(
        Filters=[{'Name': 'attachment.instance-id', 'Values': [instance_id]}]
    )

    for volume in volumes['Volumes']:
        ec2.create_snapshot(
            VolumeId=volume['VolumeId'],
            Description=f'Forensic snapshot - GuardDuty finding - {instance_id}',
            TagSpecifications=[
                {
                    'ResourceType': 'snapshot',
                    'Tags': [
                        {'Key': 'Forensic', 'Value': 'true'},
                        {'Key': 'InstanceId', 'Value': instance_id}
                    ]
                }
            ]
        )

def notify_security_team(finding):
    """Send detailed notification"""
    message = {
        'severity': finding['severity'],
        'type': finding['type'],
        'description': finding['description'],
        'resource': finding['resource'],
        'time': finding['time']
    }

    sns.publish(
        TopicArn='arn:aws:sns:region:account:security-incidents',
        Subject=f"GuardDuty Finding: {finding['type']}",
        Message=json.dumps(message, indent=2)
    )

Runbook Automation with Systems Manager

Resources:
  IncidentResponseDocument:
    Type: AWS::SSM::Document
    Properties:
      DocumentType: Automation
      Content:
        schemaVersion: '0.3'
        description: Automated incident response for compromised instance
        parameters:
          InstanceId:
            type: String
            description: ID of the compromised instance
          IsolationSecurityGroupId:
            type: String
            description: Security group for isolation
        mainSteps:
          - name: CreateForensicSnapshot
            action: 'aws:executeAwsApi'
            inputs:
              Service: ec2
              Api: CreateSnapshot
              VolumeId: '{{ InstanceId }}'
              Description: 'Forensic snapshot for incident response'
            outputs:
              - Name: SnapshotId
                Selector: $.SnapshotId
                Type: String

          - name: IsolateInstance
            action: 'aws:executeAwsApi'
            inputs:
              Service: ec2
              Api: ModifyInstanceAttribute
              InstanceId: '{{ InstanceId }}'
              Groups:
                - '{{ IsolationSecurityGroupId }}'

          - name: TagInstance
            action: 'aws:createTags'
            inputs:
              ResourceType: EC2
              ResourceIds:
                - '{{ InstanceId }}'
              Tags:
                - Key: Status
                  Value: Quarantined
                - Key: IncidentDate
                  Value: '{{ global:DATE_TIME }}'

          - name: NotifySecurityTeam
            action: 'aws:executeAwsApi'
            inputs:
              Service: sns
              Api: Publish
              TopicArn: 'arn:aws:sns:region:account:security-team'
              Subject: 'Incident Response Executed'
              Message: 'Instance {{ InstanceId }} has been isolated'

Disaster Recovery

Backup Strategies

AWS Backup

Resources:
  BackupVault:
    Type: AWS::Backup::BackupVault
    Properties:
      BackupVaultName: production-backup-vault
      EncryptionKeyArn: !GetAtt BackupKMSKey.Arn

  BackupPlan:
    Type: AWS::Backup::BackupPlan
    Properties:
      BackupPlan:
        BackupPlanName: DailyBackupPlan
        BackupPlanRule:
          - RuleName: DailyBackup
            TargetBackupVault: !Ref BackupVault
            ScheduleExpression: 'cron(0 5 ? * * *)'
            StartWindowMinutes: 60
            CompletionWindowMinutes: 120
            Lifecycle:
              DeleteAfterDays: 30
              MoveToColdStorageAfterDays: 7
            RecoveryPointTags:
              Environment: Production
              BackupType: Daily

          - RuleName: WeeklyBackup
            TargetBackupVault: !Ref BackupVault
            ScheduleExpression: 'cron(0 5 ? * 1 *)'
            Lifecycle:
              DeleteAfterDays: 90
              MoveToColdStorageAfterDays: 30

  BackupSelection:
    Type: AWS::Backup::BackupSelection
    Properties:
      BackupPlanId: !Ref BackupPlan
      BackupSelection:
        SelectionName: ProductionResources
        IamRoleArn: !GetAtt BackupRole.Arn
        Resources:
          - !Sub 'arn:aws:ec2:${AWS::Region}:${AWS::AccountId}:instance/*'
          - !Sub 'arn:aws:rds:${AWS::Region}:${AWS::AccountId}:db:*'
        Conditions:
          StringEquals:
            - ConditionKey: 'aws:ResourceTag/Backup'
              ConditionValue: 'true'

Exam Tips

Monitoring

  • CloudWatch for metrics, logs, alarms
  • X-Ray for distributed tracing
  • Enhanced monitoring for deeper insights
  • Custom metrics for application-specific data
  • Log Insights for log analysis
  • Anomaly detection for baseline monitoring

Security

  • IAM roles over access keys
  • Least privilege principle
  • Secrets Manager for credentials rotation
  • KMS for encryption at rest
  • VPC endpoints for private connectivity
  • Security groups as stateful firewalls
  • GuardDuty for threat detection

Incident Response

  • Automated remediation with Lambda
  • EventBridge for event-driven response
  • Systems Manager for runbook automation
  • Snapshots for forensic analysis
  • Isolation before investigation
  • Document and tag incidents

Best Practices

  • Monitor everything
  • Automate responses
  • Encrypt sensitive data
  • Regular security audits
  • Test disaster recovery plans
  • Implement least privilege
  • Use managed services for security