Tarek Cheikh
Founder & AWS Security Expert
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.
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.
# 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
{
"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:*"
}
}
}]
}
# 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.
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.
# 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
# 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
# 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
# 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].
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.
# 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
# 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
# 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
# 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.
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.
# 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
| 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.
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 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 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.
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.
# 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
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.
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.
# 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"
AWS::Logs::LogGroup and AWS::CloudWatch::Metric.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.
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 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
# 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"
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.
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.
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.
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.
# 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
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.
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.
# 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
# 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
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.
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.
# 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"}"
}'
# 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
}]'
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
| 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 |
| # | Practice | Priority |
|---|---|---|
| 1 | Encrypt CloudWatch Logs with KMS CMK | High |
| 2 | Deploy CIS metric filters (identity/audit) | Critical |
| 3 | Deploy CIS metric filters (network) | Critical |
| 4 | Configure log retention policies | High |
| 5 | Create alarms with SNS notifications | Critical |
| 6 | Enable Anomaly Detection on security metrics | High |
| 7 | Set up cross-account observability (OAM) | High |
| 8 | Enable Container/Lambda Insights | Medium |
| 9 | Build Logs Insights security queries | Medium |
| 10 | Analyze VPC Flow Logs in CloudWatch | High |
| 11 | Configure subscription filters for SIEM | High |
| 12 | Automate compliance with Config and Security Hub | Critical |
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.
Comprehensive guide to securing AWS CloudTrail. Covers organization trails, KMS encryption, log integrity validation, S3 bucket hardening, CloudWatch integration, data events, Network Activity Events, Insights, SCP anti-tampering, CloudTrail Lake, and continuous audit.
Comprehensive guide to securing AWS Virtual Private Cloud. Covers Security Groups, NACLs, VPC Flow Logs, VPC Endpoints, Block Public Access, Encryption Controls, Network Firewall, Transit Gateway, and GuardDuty threat detection.
Comprehensive guide to securing AWS Identity and Access Management. Covers MFA enforcement, least privilege, IAM Identity Center, SCPs, Access Analyzer, and credential management.