Security & ComplianceIntermediate15 min read

    AWS CloudWatch Security Best Practices

    Tarek Cheikh

    Founder & AWS Security Expert

    View Security Card

    AWS CloudWatch is the backbone of security monitoring in AWS. Every security-critical event -- unauthorized API calls, root account logins, IAM policy changes, network configuration modifications -- can be detected, alerted on, and investigated through CloudWatch. Without properly configured metric filters, alarms, and log encryption, you are flying blind to active threats in your environment.

    The CIS AWS Foundations Benchmark dedicates an entire section (Section 4) to monitoring controls, requiring specific CloudWatch metric filters and alarms for 14 distinct categories of security events. AWS Security Hub maps these to controls [CloudWatch.1] through [CloudWatch.17]. Organizations that skip these controls consistently fail compliance audits and miss early indicators of compromise.

    This guide covers 12 battle-tested CloudWatch security best practices, each with real AWS CLI commands, CIS control mappings, Security Hub control IDs, and the latest 2025-2026 updates from AWS.

    1. Encrypt CloudWatch Logs with KMS CMK

    By default, CloudWatch Logs encrypts data at rest using AES-256-GCM with AWS-managed keys. However, for compliance frameworks (PCI DSS, HIPAA, SOC 2), you must use a customer-managed KMS key (CMK) to maintain full control over encryption and key rotation. Without CMK encryption, you cannot enforce key policies, audit key usage, or revoke access independently of IAM.

    Implementation

    # Create a KMS key for CloudWatch Logs
    aws kms create-key   --description "CloudWatch Logs encryption key"   --key-usage ENCRYPT_DECRYPT   --key-spec SYMMETRIC_DEFAULT
    
    # Add the required key policy for CloudWatch Logs
    # The key policy must grant logs.REGION.amazonaws.com permission to use the key
    aws kms put-key-policy   --key-id arn:aws:kms:us-east-1:123456789012:key/KEY-ID   --policy-name default   --policy file://cloudwatch-kms-policy.json
    
    # Associate the KMS key with an existing log group
    aws logs associate-kms-key   --log-group-name /aws/cloudtrail/my-trail   --kms-key-id arn:aws:kms:us-east-1:123456789012:key/KEY-ID
    
    # Create a new log group with KMS encryption from the start
    aws logs create-log-group   --log-group-name /aws/lambda/secure-function   --kms-key-id arn:aws:kms:us-east-1:123456789012:key/KEY-ID

    KMS Key Policy for CloudWatch Logs

    {
      "Version": "2012-10-17",
      "Statement": [{
        "Sid": "AllowCloudWatchLogs",
        "Effect": "Allow",
        "Principal": {
          "Service": "logs.us-east-1.amazonaws.com"
        },
        "Action": [
          "kms:Encrypt*",
          "kms:Decrypt*",
          "kms:ReEncrypt*",
          "kms:GenerateDataKey*",
          "kms:Describe*"
        ],
        "Resource": "*",
        "Condition": {
          "ArnLike": {
            "kms:EncryptionContext:aws:logs:arn": "arn:aws:logs:us-east-1:123456789012:log-group:*"
          }
        }
      }]
    }

    Audit Unencrypted Log Groups

    # List all log groups without KMS encryption
    aws logs describe-log-groups   --query "logGroups[?!kmsKeyId].logGroupName"   --output table

    Security Hub Control: While no dedicated CloudWatch encryption control exists, Prowler and other CSPM tools flag unencrypted log groups as medium-severity findings. CIS recommends CMK encryption for log groups containing sensitive data.

    2. CIS Metric Filters -- Unauthorized API Calls, Root Usage, IAM Changes, CloudTrail Changes

    The CIS AWS Foundations Benchmark Section 4 requires specific CloudWatch metric filters on your CloudTrail log group to detect security-relevant events. These first four filters cover the most critical identity and audit trail threats.

    Unauthorized API Calls [CloudWatch.2]

    # CIS 3.1 / 4.1 - Metric filter for unauthorized API calls
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name UnauthorizedAPICalls   --filter-pattern '{ ($.errorCode = "*UnauthorizedAccess") || ($.errorCode = "AccessDenied*") }'   --metric-transformations     metricName=UnauthorizedAPICalls,metricNamespace=CISBenchmark,metricValue=1,defaultValue=0

    Root Account Usage [CloudWatch.1]

    # CIS 1.7 / 4.3 - Metric filter for root account usage
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name RootAccountUsage   --filter-pattern '{ $.userIdentity.type = "Root" && $.userIdentity.invokedBy NOT EXISTS && $.eventType != "AwsServiceEvent" }'   --metric-transformations     metricName=RootAccountUsage,metricNamespace=CISBenchmark,metricValue=1,defaultValue=0

    IAM Policy Changes [CloudWatch.4]

    # CIS 3.4 / 4.4 - Metric filter for IAM policy changes
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name IAMPolicyChanges   --filter-pattern '{ ($.eventName=CreatePolicy) || ($.eventName=DeletePolicy) || ($.eventName=DeleteRolePolicy) || ($.eventName=PutRolePolicy) || ($.eventName=AttachRolePolicy) || ($.eventName=DetachRolePolicy) || ($.eventName=AttachUserPolicy) || ($.eventName=DetachUserPolicy) || ($.eventName=AttachGroupPolicy) || ($.eventName=DetachGroupPolicy) || ($.eventName=PutGroupPolicy) || ($.eventName=DeleteGroupPolicy) || ($.eventName=PutUserPolicy) || ($.eventName=DeleteUserPolicy) || ($.eventName=CreatePolicyVersion) || ($.eventName=DeletePolicyVersion) }'   --metric-transformations     metricName=IAMPolicyChanges,metricNamespace=CISBenchmark,metricValue=1,defaultValue=0

    CloudTrail Configuration Changes [CloudWatch.5]

    # CIS 3.5 / 4.5 - Metric filter for CloudTrail changes
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name CloudTrailChanges   --filter-pattern '{ ($.eventName=CreateTrail) || ($.eventName=UpdateTrail) || ($.eventName=DeleteTrail) || ($.eventName=StartLogging) || ($.eventName=StopLogging) }'   --metric-transformations     metricName=CloudTrailChanges,metricNamespace=CISBenchmark,metricValue=1,defaultValue=0

    CIS Benchmark: Controls 4.1-4.5 (v1.4.0), mapped to Security Hub controls [CloudWatch.1] through [CloudWatch.5].

    3. CIS Metric Filters -- Security Groups, NACLs, Gateways, Route Tables

    Network configuration changes are among the highest-risk events in AWS. An attacker who modifies a security group or NACL can open inbound access from anywhere, bypass network segmentation, or exfiltrate data through an unauthorized gateway.

    Security Group Changes [CloudWatch.10]

    # CIS 3.10 / 4.10 - Metric filter for security group changes
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name SecurityGroupChanges   --filter-pattern '{ ($.eventName=AuthorizeSecurityGroupIngress) || ($.eventName=AuthorizeSecurityGroupEgress) || ($.eventName=RevokeSecurityGroupIngress) || ($.eventName=RevokeSecurityGroupEgress) || ($.eventName=CreateSecurityGroup) || ($.eventName=DeleteSecurityGroup) }'   --metric-transformations     metricName=SecurityGroupChanges,metricNamespace=CISBenchmark,metricValue=1,defaultValue=0

    NACL Changes [CloudWatch.11]

    # CIS 3.11 / 4.11 - Metric filter for NACL changes
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name NACLChanges   --filter-pattern '{ ($.eventName=CreateNetworkAcl) || ($.eventName=CreateNetworkAclEntry) || ($.eventName=DeleteNetworkAcl) || ($.eventName=DeleteNetworkAclEntry) || ($.eventName=ReplaceNetworkAclEntry) || ($.eventName=ReplaceNetworkAclAssociation) }'   --metric-transformations     metricName=NACLChanges,metricNamespace=CISBenchmark,metricValue=1,defaultValue=0

    Network Gateway Changes [CloudWatch.12]

    # CIS 3.12 / 4.12 - Metric filter for gateway changes
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name GatewayChanges   --filter-pattern '{ ($.eventName=CreateCustomerGateway) || ($.eventName=DeleteCustomerGateway) || ($.eventName=AttachInternetGateway) || ($.eventName=CreateInternetGateway) || ($.eventName=DeleteInternetGateway) || ($.eventName=DetachInternetGateway) }'   --metric-transformations     metricName=GatewayChanges,metricNamespace=CISBenchmark,metricValue=1,defaultValue=0

    Route Table Changes [CloudWatch.13]

    # CIS 3.13 / 4.13 - Metric filter for route table changes
    aws logs put-metric-filter   --log-group-name CloudTrail/DefaultLogGroup   --filter-name RouteTableChanges   --filter-pattern '{ ($.eventName=CreateRoute) || ($.eventName=CreateRouteTable) || ($.eventName=ReplaceRoute) || ($.eventName=ReplaceRouteTableAssociation) || ($.eventName=DeleteRouteTable) || ($.eventName=DeleteRoute) || ($.eventName=DisassociateRouteTable) }'   --metric-transformations     metricName=RouteTableChanges,metricNamespace=CISBenchmark,metricValue=1,defaultValue=0

    Additional CIS Filters: [CloudWatch.6] console authentication failures, [CloudWatch.7] KMS key deletion/disabling, [CloudWatch.8] S3 bucket policy changes, [CloudWatch.9] AWS Config changes, [CloudWatch.14] VPC changes. Deploy all 14 filters for full CIS compliance.

    4. Configure Log Retention Policies

    CloudWatch Logs default to Never expire, which leads to unbounded storage costs that grow silently. A single high-volume Lambda function can generate terabytes of logs over months, costing hundreds of dollars monthly. Conversely, setting retention too short violates compliance requirements that mandate log preservation for 1-7 years.

    Implementation

    # Set retention to 90 days for operational logs
    aws logs put-retention-policy   --log-group-name /aws/lambda/my-function   --retention-in-days 90
    
    # Set retention to 365 days for security/audit logs
    aws logs put-retention-policy   --log-group-name /aws/cloudtrail/my-trail   --retention-in-days 365
    
    # Audit log groups with no retention policy (Never expire)
    aws logs describe-log-groups   --query "logGroups[?!retentionInDays].{Name:logGroupName,StoredBytes:storedBytes}"   --output table

    Recommended Retention Periods

    Log Type Retention Rationale
    CloudTrail API logs 365-2557 days Compliance (PCI DSS: 1 year, SOX: 7 years)
    VPC Flow Logs 90-365 days Network forensics, incident response
    Application logs 30-90 days Debugging, operational needs
    Lambda execution logs 14-30 days Cost optimization for high-volume functions
    WAF logs 90-365 days Attack pattern analysis, compliance

    Security Hub Control: [CloudWatch.16] requires that CloudWatch log groups are retained for a specified time period (minimum 365 days by default). Never set retention to "Never expire" without documented justification and cost approval.

    Cost tip: For long-term retention beyond 90 days, consider exporting logs to S3 with Glacier lifecycle policies. Use aws logs create-export-task or subscription filters for automated export.

    5. CloudWatch Alarms for Security Events

    Metric filters without alarms are useless -- they detect events but nobody gets notified. Every CIS metric filter must have a corresponding CloudWatch Alarm connected to an SNS topic for real-time notification via email, Slack, PagerDuty, or a SIEM webhook.

    Create SNS Topic and Alarms

    # Create an SNS topic for security alerts
    aws sns create-topic --name SecurityAlerts
    aws sns subscribe   --topic-arn arn:aws:sns:us-east-1:123456789012:SecurityAlerts   --protocol email   --notification-endpoint security-team@example.com
    
    # Create alarm for root account usage
    aws cloudwatch put-metric-alarm   --alarm-name CIS-RootAccountUsage   --alarm-description "Alarm for root account usage (CIS 4.3)"   --metric-name RootAccountUsage   --namespace CISBenchmark   --statistic Sum   --period 300   --threshold 1   --comparison-operator GreaterThanOrEqualToThreshold   --evaluation-periods 1   --alarm-actions arn:aws:sns:us-east-1:123456789012:SecurityAlerts   --treat-missing-data notBreaching
    
    # Create alarm for unauthorized API calls
    aws cloudwatch put-metric-alarm   --alarm-name CIS-UnauthorizedAPICalls   --alarm-description "Alarm for unauthorized API calls (CIS 4.1)"   --metric-name UnauthorizedAPICalls   --namespace CISBenchmark   --statistic Sum   --period 300   --threshold 5   --comparison-operator GreaterThanOrEqualToThreshold   --evaluation-periods 1   --alarm-actions arn:aws:sns:us-east-1:123456789012:SecurityAlerts   --treat-missing-data notBreaching

    Composite Alarms for Correlated Events

    Composite alarms reduce alert fatigue by triggering only when multiple related conditions are met simultaneously. For example, alert only when both IAM policy changes AND unauthorized API calls occur within the same period:

    # Create a composite alarm combining IAM changes and unauthorized calls
    aws cloudwatch put-composite-alarm   --alarm-name CIS-CorrelatedIAMThreat   --alarm-description "Potential IAM attack - policy changes with unauthorized calls"   --alarm-rule 'ALARM("CIS-IAMPolicyChanges") AND ALARM("CIS-UnauthorizedAPICalls")'   --alarm-actions arn:aws:sns:us-east-1:123456789012:SecurityAlerts

    Security Hub Controls: [CloudWatch.15] requires that alarms have specified actions configured. [CloudWatch.17] requires that alarm actions are activated (not disabled). Alarms without actions are compliance violations.

    6. CloudWatch Anomaly Detection for Security Metrics

    Static thresholds miss slow-building attacks and generate false positives during legitimate traffic spikes. CloudWatch Anomaly Detection uses machine learning to build dynamic baselines that adapt to daily, weekly, and seasonal patterns in your metrics. When a metric deviates beyond the expected band, it triggers an alarm -- catching threats that fixed thresholds would miss.

    Implementation

    # Create an anomaly detector for unauthorized API calls
    aws cloudwatch put-anomaly-detector   --namespace CISBenchmark   --metric-name UnauthorizedAPICalls   --stat Sum
    
    # Create an alarm based on the anomaly detection band
    aws cloudwatch put-metric-alarm   --alarm-name AnomalyDetection-UnauthorizedAPICalls   --alarm-description "Anomalous spike in unauthorized API calls"   --evaluation-periods 3   --comparison-operator GreaterThanUpperThreshold   --threshold-metric-id ad1   --metrics '[
        {
          "Id": "m1",
          "MetricStat": {
            "Metric": {
              "Namespace": "CISBenchmark",
              "MetricName": "UnauthorizedAPICalls"
            },
            "Period": 300,
            "Stat": "Sum"
          }
        },
        {
          "Id": "ad1",
          "Expression": "ANOMALY_DETECTION_BAND(m1, 2)"
        }
      ]'   --alarm-actions arn:aws:sns:us-east-1:123456789012:SecurityAlerts   --treat-missing-data notBreaching

    Key Security Metrics for Anomaly Detection

    • UnauthorizedAPICalls -- sudden spike indicates credential probing or compromised keys
    • ConsoleSignInFailures -- brute force or credential stuffing attack
    • API call volume per principal -- abnormal activity from a specific IAM role
    • Data transfer metrics -- potential data exfiltration via unusually large egress
    • Lambda invocation errors -- may indicate injection attacks on serverless functions

    Best practice: Use anomaly detection bands of 2 standard deviations for security metrics. Allow the model at least 2 weeks of baseline data before relying on it for alerting. Exclude known maintenance windows using the --configuration parameter.

    7. Cross-Account CloudWatch with Observability Access Manager (OAM)

    Organizations with multiple AWS accounts need centralized security monitoring. CloudWatch Observability Access Manager (OAM), introduced as a fully supported feature, allows a central monitoring account to view metrics, logs, and traces from source accounts without copying data -- reducing cost and complexity compared to log aggregation approaches.

    Implementation

    # In the MONITORING account: create an OAM sink
    aws oam create-sink   --name CentralSecurityMonitoring   --tags Environment=Security
    
    # Create a sink policy to allow source accounts in your organization
    aws oam put-sink-policy   --sink-identifier arn:aws:oam:us-east-1:111111111111:sink/SINK-ID   --policy '{
        "Version": "2012-10-17",
        "Statement": [{
          "Effect": "Allow",
          "Principal": "*",
          "Action": ["oam:CreateLink", "oam:UpdateLink"],
          "Resource": "*",
          "Condition": {
            "ForAnyValue:StringEquals": {
              "aws:PrincipalOrgID": "o-EXAMPLE"
            }
          }
        }]
      }'
    
    # In each SOURCE account: create a link to the monitoring account sink
    aws oam create-link   --sink-identifier arn:aws:oam:us-east-1:111111111111:sink/SINK-ID   --label-template "$AccountName"   --resource-types "AWS::Logs::LogGroup" "AWS::CloudWatch::Metric" "AWS::XRay::Trace"

    Security Considerations

    • Least privilege: Share only the resource types needed. Start with AWS::Logs::LogGroup and AWS::CloudWatch::Metric.
    • Organization-scoped policies: Always restrict sink policies to your AWS Organization ID to prevent unauthorized cross-account access.
    • Monitoring account isolation: The monitoring account should be a dedicated account in a Security OU with restricted access, not shared with workloads.
    • Data stays in source accounts: OAM provides cross-account visibility without copying logs, maintaining data residency requirements.

    Best practice: Deploy OAM links via AWS CloudFormation StackSets across all accounts in your organization. Pair OAM with cross-account CloudWatch dashboards to build a single-pane security operations view.

    8. Container Insights and Lambda Insights for Security Monitoring

    Container Insights and Lambda Insights provide deep visibility into compute workloads that standard CloudWatch metrics miss. From a security perspective, they surface runtime anomalies, resource exhaustion attacks, and suspicious execution patterns.

    Enable Container Insights on EKS

    # Enable Container Insights with enhanced observability on an EKS cluster
    aws eks update-cluster-config   --name my-cluster   --logging '{"clusterLogging":[{"types":["api","audit","authenticator","controllerManager","scheduler"],"enabled":true}]}'
    
    # Install the CloudWatch agent as a DaemonSet for Container Insights
    # Use the AWS-provided Helm chart
    helm repo add aws-observability https://aws-observability.github.io/helm-charts
    helm install amazon-cloudwatch-observability   aws-observability/amazon-cloudwatch-observability   --namespace amazon-cloudwatch --create-namespace   --set clusterName=my-cluster   --set region=us-east-1

    Enable Lambda Insights

    # Add the Lambda Insights extension layer to a function
    aws lambda update-function-configuration   --function-name my-secure-function   --layers arn:aws:lambda:us-east-1:580247275435:layer:LambdaInsightsExtension:53
    
    # Verify the Insights layer is attached
    aws lambda get-function-configuration   --function-name my-secure-function   --query "Layers[].Arn"

    Security-Relevant Metrics

    • Container Insights: Pod restart counts (crash loops from injection attacks), CPU/memory spikes (cryptomining), network RX/TX anomalies (data exfiltration), node-level filesystem usage
    • Lambda Insights: Memory utilization near limits (resource exhaustion), cold start frequency (scaling attacks), duration anomalies (code injection extending execution), concurrent execution spikes
    • EKS audit logs: Failed authentication attempts, unauthorized API server calls, RBAC policy changes, secret access patterns

    Best practice: Enable KMS encryption on the Container Insights log groups (/aws/containerinsights/CLUSTER_NAME/) and set appropriate retention policies. Create anomaly detection alarms on pod restart counts and Lambda duration metrics to catch runtime attacks.

    9. CloudWatch Logs Insights -- Security Query Patterns

    CloudWatch Logs Insights provides an interactive query language for ad-hoc security investigations. When an alarm fires or a Security Hub finding appears, Logs Insights lets you drill into the raw CloudTrail events to understand scope, timeline, and impact within seconds.

    Essential Security Queries

    Find all actions by a compromised IAM principal:

    fields @timestamp, eventName, sourceIPAddress, errorCode, requestParameters
    | filter userIdentity.arn = "arn:aws:iam::123456789012:user/compromised-user"
    | sort @timestamp desc
    | limit 200

    Detect console logins from unusual locations:

    fields @timestamp, userIdentity.arn, sourceIPAddress, responseElements.ConsoleLogin
    | filter eventName = "ConsoleLogin"
    | stats count(*) as loginCount by sourceIPAddress, userIdentity.arn
    | sort loginCount desc

    Find failed API calls (potential privilege escalation attempts):

    fields @timestamp, userIdentity.arn, eventName, errorCode, errorMessage
    | filter errorCode IN ["AccessDenied", "UnauthorizedAccess", "Client.UnauthorizedAccess"]
    | stats count(*) as failCount by userIdentity.arn, eventName
    | sort failCount desc
    | limit 50

    Detect IAM key creation (persistence technique):

    fields @timestamp, userIdentity.arn, requestParameters.userName, responseElements.accessKey.accessKeyId
    | filter eventName = "CreateAccessKey"
    | sort @timestamp desc

    Find S3 data exfiltration patterns:

    fields @timestamp, userIdentity.arn, requestParameters.bucketName, requestParameters.key
    | filter eventName IN ["GetObject", "SelectObjectContent"]
    | stats count(*) as downloadCount by userIdentity.arn, requestParameters.bucketName
    | sort downloadCount desc
    | limit 20

    Best practice: Save frequently used security queries in CloudWatch Logs Insights for rapid incident response. You can encrypt query results with a KMS CMK using aws logs put-query-definition and associate a KMS key via aws logs put-account-policy.

    10. VPC Flow Logs Analysis with CloudWatch

    VPC Flow Logs capture network traffic metadata for every ENI in your VPC. When sent to CloudWatch Logs, they enable real-time detection of port scans, data exfiltration, lateral movement, and connections to known malicious IPs. This is a core requirement for the VPC security posture.

    Enable VPC Flow Logs to CloudWatch

    # Create a log group for VPC Flow Logs with encryption and retention
    aws logs create-log-group   --log-group-name /aws/vpc/flowlogs/prod-vpc   --kms-key-id arn:aws:kms:us-east-1:123456789012:key/KEY-ID
    
    aws logs put-retention-policy   --log-group-name /aws/vpc/flowlogs/prod-vpc   --retention-in-days 90
    
    # Create a flow log for the VPC
    aws ec2 create-flow-logs   --resource-type VPC   --resource-ids vpc-0123456789abcdef0   --traffic-type ALL   --log-destination-type cloud-watch-logs   --log-group-name /aws/vpc/flowlogs/prod-vpc   --deliver-logs-permission-arn arn:aws:iam::123456789012:role/FlowLogsRole   --max-aggregation-interval 60

    Security Analysis Queries

    Find rejected traffic (potential port scans or probing):

    fields @timestamp, srcAddr, dstAddr, dstPort, action
    | filter action = "REJECT"
    | stats count(*) as rejectCount by srcAddr, dstPort
    | sort rejectCount desc
    | limit 25

    Detect traffic to unusual ports (C2 communication):

    fields @timestamp, srcAddr, dstAddr, dstPort, protocol, bytes
    | filter action = "ACCEPT" and dstPort NOT IN [80, 443, 22, 53, 123, 3306, 5432]
    | stats sum(bytes) as totalBytes, count(*) as flowCount by dstPort, dstAddr
    | sort totalBytes desc

    Metric filter for rejected traffic spikes:

    # Create a metric filter for high reject rates
    aws logs put-metric-filter   --log-group-name /aws/vpc/flowlogs/prod-vpc   --filter-name RejectedTraffic   --filter-pattern '[version, account_id, interface_id, srcaddr, dstaddr, srcport, dstport, protocol, packets, bytes, start, end, action="REJECT", log_status]'   --metric-transformations     metricName=RejectedFlows,metricNamespace=VPCFlowLogs,metricValue=1,defaultValue=0

    Best practice: Set the flow log aggregation interval to 60 seconds (instead of the default 600 seconds) for near-real-time security detection. Use the v5 custom log format to include additional fields like tcp-flags, pkt-src-addr, and flow-direction for deeper analysis.

    11. Real-Time Log Processing with Subscription Filters

    Subscription filters enable real-time streaming of CloudWatch Logs to downstream services for security processing. This is essential for feeding logs into a SIEM (Splunk, Datadog, OpenSearch), triggering automated remediation via Lambda, or building real-time threat detection pipelines.

    Lambda-Based Real-Time Security Processing

    # Create a subscription filter to send CloudTrail logs to a Lambda function
    aws logs put-subscription-filter   --log-group-name /aws/cloudtrail/my-trail   --filter-name SecurityEventProcessor   --filter-pattern '{ $.errorCode = "AccessDenied" || $.eventName = "ConsoleLogin" || $.userIdentity.type = "Root" }'   --destination-arn arn:aws:lambda:us-east-1:123456789012:function:SecurityEventProcessor
    
    # Stream logs to Kinesis Data Firehose for SIEM ingestion
    aws logs put-subscription-filter   --log-group-name /aws/cloudtrail/my-trail   --filter-name SIEMIngestion   --filter-pattern ""   --destination-arn arn:aws:firehose:us-east-1:123456789012:deliverystream/security-logs   --role-arn arn:aws:iam::123456789012:role/CWLtoFirehoseRole

    Cross-Account Log Streaming

    # Stream logs from a source account to a destination in the security account
    aws logs put-subscription-filter   --log-group-name /aws/cloudtrail/my-trail   --filter-name CrossAccountSIEM   --filter-pattern ""   --destination-arn arn:aws:logs:us-east-1:SECURITY-ACCOUNT-ID:destination:SecurityLogDestination   --role-arn arn:aws:iam::123456789012:role/CWLCrossAccountRole

    Architecture Patterns

    • CloudWatch Logs -> Lambda -> SNS/Slack: Real-time alerting for high-severity events (root login, key deletion)
    • CloudWatch Logs -> Kinesis Firehose -> S3 -> Athena: Cost-effective long-term log analytics
    • CloudWatch Logs -> Kinesis Firehose -> OpenSearch: Full-text search and visualization for security operations
    • CloudWatch Logs -> Lambda -> Security Hub: Create custom Security Hub findings from log patterns

    Limitation: Each log group supports a maximum of 2 subscription filters. Plan your architecture accordingly -- use Kinesis Data Streams as a fan-out layer if you need to send the same logs to more than 2 destinations.

    12. Continuous Compliance -- Config Rules, Security Hub, and Automated Remediation

    Manual monitoring configuration is fragile. A new log group created by a developer without encryption or retention policies silently creates a compliance gap. Continuous compliance uses AWS Config, Security Hub, and automated remediation to enforce CloudWatch security standards across all accounts.

    AWS Config Rules for CloudWatch

    # Ensure all log groups have retention policies
    aws configservice put-config-rule --config-rule '{
      "ConfigRuleName": "cw-loggroup-retention-period-check",
      "Source": {
        "Owner": "AWS",
        "SourceIdentifier": "CW_LOGGROUP_RETENTION_PERIOD_CHECK"
      },
      "InputParameters": "{"minRetentionTime":"90"}"
    }'
    
    # Ensure CloudWatch log groups are encrypted with KMS
    aws configservice put-config-rule --config-rule '{
      "ConfigRuleName": "cloudwatch-log-group-encrypted",
      "Source": {
        "Owner": "AWS",
        "SourceIdentifier": "CLOUDWATCH_LOG_GROUP_ENCRYPTED"
      }
    }'
    
    # Ensure CloudWatch alarms have actions configured
    aws configservice put-config-rule --config-rule '{
      "ConfigRuleName": "cloudwatch-alarm-action-check",
      "Source": {
        "Owner": "AWS",
        "SourceIdentifier": "CLOUDWATCH_ALARM_ACTION_CHECK"
      },
      "InputParameters": "{"alarmActionRequired":"true","insufficientDataActionRequired":"false","okActionRequired":"false"}"
    }'

    Automated Remediation with SSM

    # Auto-remediate log groups missing retention policies
    aws configservice put-remediation-configurations   --remediation-configurations '[{
        "ConfigRuleName": "cw-loggroup-retention-period-check",
        "TargetType": "SSM_DOCUMENT",
        "TargetId": "AWS-ConfigureCloudWatchLogGroupRetention",
        "Parameters": {
          "LogGroupName": {"ResourceValue": {"Value": "RESOURCE_ID"}},
          "RetentionInDays": {"StaticValue": {"Values": ["90"]}}
        },
        "Automatic": true,
        "MaximumAutomaticAttempts": 3,
        "RetryAttemptSeconds": 60
      }]'

    Security Hub Integration

    Enable the CIS AWS Foundations Benchmark standard in Security Hub to automatically evaluate all 14 CloudWatch monitoring controls ([CloudWatch.1] through [CloudWatch.14]) plus the three operational controls ([CloudWatch.15] through [CloudWatch.17]):

    # Enable CIS Benchmark in Security Hub
    aws securityhub batch-enable-standards   --standards-subscription-requests '[{
        "StandardsArn": "arn:aws:securityhub:::standards/cis-aws-foundations-benchmark/v/3.0.0"
      }]'
    
    # Check CloudWatch-specific findings
    aws securityhub get-findings   --filters '{
        "GeneratorId": [{"Value": "aws/securityhub", "Comparison": "PREFIX"}],
        "Title": [{"Value": "CloudWatch", "Comparison": "PREFIX"}],
        "ComplianceStatus": [{"Value": "FAILED", "Comparison": "EQUALS"}]
      }'   --query "Findings[].{Control:Title,Status:Compliance.Status}"   --output table

    Common Misconfigurations

    Misconfiguration Risk Detection
    Log groups without KMS encryption Sensitive data exposed if storage is compromised Config rule: CLOUDWATCH_LOG_GROUP_ENCRYPTED
    Missing CIS metric filters No detection of critical security events Security Hub: [CloudWatch.1] through [CloudWatch.14]
    Alarms without actions Events detected but nobody notified Security Hub: [CloudWatch.15], [CloudWatch.17]
    Retention set to "Never expire" Unbounded storage costs, no lifecycle management Security Hub: [CloudWatch.16]
    No cross-account log aggregation Blind spots in multi-account environments Manual audit of OAM sink/link configuration

    Quick Reference Checklist

    # Practice Priority
    1Encrypt CloudWatch Logs with KMS CMKHigh
    2Deploy CIS metric filters (identity/audit)Critical
    3Deploy CIS metric filters (network)Critical
    4Configure log retention policiesHigh
    5Create alarms with SNS notificationsCritical
    6Enable Anomaly Detection on security metricsHigh
    7Set up cross-account observability (OAM)High
    8Enable Container/Lambda InsightsMedium
    9Build Logs Insights security queriesMedium
    10Analyze VPC Flow Logs in CloudWatchHigh
    11Configure subscription filters for SIEMHigh
    12Automate compliance with Config and Security HubCritical

    Related Resources

    Go Deeper: The State of AWS Security 2026

    This article is just the start. Get the full picture with our free whitepaper - 8 chapters covering IAM, S3, VPC, monitoring, agentic AI security, compliance, and a prioritized action plan with 50+ CLI commands.

    CloudWatchMonitoringLoggingAlarmsMetric FiltersSecurity MonitoringObservabilityCIS BenchmarkLog AnalyticsAnomaly Detection