Skip to main content

Ensure that CloudWatch Log Groups are not publicly accessible

Overview

This check verifies that no CloudWatch Log Groups have resource policies that allow access from any entity (Principal: *). Resource policies with overly permissive principals can expose your log data to unauthorized parties, creating a serious security vulnerability.

Risk

Publicly accessible CloudWatch Log Groups can lead to:

  • Data exposure: Sensitive information in logs (credentials, API keys, PII, business data) could be read by anyone
  • Compliance violations: Public log access violates most security frameworks and regulations
  • Attack reconnaissance: Attackers can use exposed logs to learn about your infrastructure and find vulnerabilities
  • Log tampering: If write actions are permitted, attackers could modify subscriptions or delete logs, undermining audit integrity

Remediation Steps

Prerequisites

  • AWS account access with permission to manage CloudWatch Logs resource policies
  • Permissions including logs:DescribeResourcePolicies, logs:DeleteResourcePolicy, and logs:PutResourcePolicy

AWS Console Method

  1. Sign in to the AWS Management Console
  2. Navigate to CloudWatch
  3. In the left navigation pane, click Logs > Resource policies
  4. Review each policy document for overly permissive settings:
    • Look for "Principal": "*" or "Principal": {"AWS": "*"}
    • Look for wildcard "Resource": "*" values
  5. For each problematic policy, either:
    • Delete the policy: Select the policy and click Delete if the policy is no longer needed
    • Replace the policy: Update it with a properly scoped policy that specifies exact AWS service principals or account IDs

Important: Before deleting a resource policy, verify that no legitimate AWS services (such as Route 53, VPC Flow Logs, or CloudTrail) depend on it. These services require resource policies to write logs.

AWS CLI (optional)

List all resource policies to identify public access:

aws logs describe-resource-policies --region us-east-1

Look for policies with "Principal": "*" in the output.

Delete a problematic resource policy:

aws logs delete-resource-policy \
--policy-name <policy-name> \
--region us-east-1

Replace <policy-name> with the actual policy name from the describe command output.

Replace with a properly scoped policy (example for Route 53 DNS query logging):

aws logs put-resource-policy \
--policy-name route53-dns-logs-policy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Route53LogsToCloudWatchLogs",
"Effect": "Allow",
"Principal": {
"Service": "route53.amazonaws.com"
},
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:us-east-1:123456789012:log-group:/aws/route53/*",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "123456789012"
}
}
}
]
}' \
--region us-east-1

Replace 123456789012 with your actual AWS account ID.

CloudFormation (optional)

Define a properly scoped resource policy in CloudFormation:

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudWatch Logs resource policy with restricted access

Parameters:
AccountId:
Type: String
Description: AWS Account ID
Default: '123456789012'

Resources:
LogGroupResourcePolicy:
Type: AWS::Logs::ResourcePolicy
Properties:
PolicyName: restricted-logs-policy
PolicyDocument: !Sub |
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowRoute53Logging",
"Effect": "Allow",
"Principal": {
"Service": "route53.amazonaws.com"
},
"Action": [
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:${AWS::Region}:${AccountId}:log-group:/aws/route53/*",
"Condition": {
"StringEquals": {
"aws:SourceAccount": "${AccountId}"
}
}
}
]
}

Deploy the template:

aws cloudformation deploy \
--template-file logs-resource-policy.yaml \
--stack-name logs-resource-policy-stack \
--parameter-overrides AccountId=123456789012 \
--region us-east-1
Terraform (optional)

Define a properly scoped resource policy in Terraform:

data "aws_caller_identity" "current" {}

resource "aws_cloudwatch_log_resource_policy" "route53_logging" {
policy_name = "route53-dns-logs-policy"

policy_document = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "Route53LogsToCloudWatchLogs"
Effect = "Allow"
Principal = {
Service = "route53.amazonaws.com"
}
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:us-east-1:${data.aws_caller_identity.current.account_id}:log-group:/aws/route53/*"
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
}
}
}
]
})
}

Example for VPC Flow Logs:

resource "aws_cloudwatch_log_resource_policy" "vpc_flow_logs" {
policy_name = "vpc-flow-logs-policy"

policy_document = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = "VPCFlowLogsToCloudWatchLogs"
Effect = "Allow"
Principal = {
Service = "vpc-flow-logs.amazonaws.com"
}
Action = [
"logs:CreateLogStream",
"logs:PutLogEvents",
"logs:DescribeLogStreams"
]
Resource = "arn:aws:logs:us-east-1:${data.aws_caller_identity.current.account_id}:log-group:/aws/vpc-flow-logs/*"
Condition = {
StringEquals = {
"aws:SourceAccount" = data.aws_caller_identity.current.account_id
}
}
}
]
})
}

Verification

After removing or updating the resource policies, verify the changes:

  1. Go to CloudWatch > Logs > Resource policies in the AWS Console
  2. Review each remaining policy and confirm none contain "Principal": "*"
  3. Re-run the Prowler check to confirm the issue is resolved
CLI verification commands

List all resource policies and check for public access:

aws logs describe-resource-policies --region us-east-1 \
--query 'resourcePolicies[*].{Name:policyName,Document:policyDocument}' \
--output table

To check if any policies contain public principal, use this command:

aws logs describe-resource-policies --region us-east-1 \
--query 'resourcePolicies[*].policyDocument' \
--output text | grep -E '"Principal":\s*"\*"'

If no output is returned, no policies have public access.

Additional Resources

Notes

  • Service dependencies: AWS services like Route 53, VPC Flow Logs, CloudTrail, and API Gateway require resource policies to write logs to CloudWatch. Always verify a policy is not needed before deleting it
  • Condition keys: Use aws:SourceAccount and aws:SourceArn condition keys to restrict which accounts and resources can write logs
  • Account vs resource scope: Resource policies can be scoped to the entire account or specific log groups. Prefer resource-specific policies when possible
  • Policy limits: An account can have a maximum of 10 account-scoped policies and one policy per log group
  • Regular audits: Review resource policies periodically to ensure they follow the principle of least privilege