Back to Checks
aws

CloudWatch Log Group is not publicly accessible

cloudwatch_log_group_not_publicly_accessible
Service: cloudwatchSeverity: high
OfficialBuilt-inVerified
Categories:internet-exposedidentity-access
Resource Type:Other

CloudWatch Log Groups with resource policies that grant access to any principal are identified. Statements using Principal:"*" or wildcard Resource that reference a log group ARN indicate that the log group is exposed through a public policy.

Risk

Public access to log groups enables unauthorized reading of logs, revealing secrets and operational metadata, harming confidentiality. If broad actions are allowed, attackers can modify subscriptions or logs, undermining integrity and disrupting availability of audit evidence.

Run this check with Prowler

Run in Prowler Cloud
Run
prowler aws --checks cloudwatch_log_group_not_publicly_accessible

Recommendation

Remove public access from log group resource policies. Replace Principal:"*" and Resource:"*" with narrowly scoped principals and specific ARNs. Grant only necessary actions, apply conditions to constrain use, and enforce least privilege and separation of duties with regular policy reviews.

Remediation

CLI

aws logs delete-resource-policy --policy-name <policy-name>

Terraform

resource "aws_cloudwatch_log_resource_policy" "<example_resource_name>" {
  policy_name     = "<example_resource_name>"
  policy_document = jsonencode({
    Version   = "2012-10-17"
    Statement = [{
      Effect    = "Allow"
      Principal = { AWS = "<example_account_id>" } # FIX: restrict Principal (not "*") to avoid public access
      Action    = "logs:PutSubscriptionFilter"
      Resource  = "arn:aws:logs:<region>:<account-id>:destination:<example_resource_name>"
    }]
  })
}

NativeIaC

Resources:
  <example_resource_name>:
    Type: AWS::Logs::ResourcePolicy
    Properties:
      PolicyName: <example_resource_name>
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              AWS: "<example_account_id>"  # FIX: restrict to specific account (not *) to prevent public access
            Action: logs:PutSubscriptionFilter
            Resource: "arn:aws:logs:<region>:<account-id>:destination:<example_resource_name>"

Other

1Open the CloudWatch console
2Go to Logs > Resource policies
3Select the policy that exposes your log groups (Principal set to "*" or Resource "*")
4Click Delete and confirm

Check Code

from prowler.lib.check.models import Check, Check_Report_AWS
from prowler.providers.aws.services.cloudwatch.logs_client import logs_client
from prowler.providers.aws.services.iam.lib.policy import is_policy_public


class cloudwatch_log_group_not_publicly_accessible(Check):
    def execute(self):
        findings = []
        public_log_groups = []
        if (
            logs_client.resource_policies is not None
            and logs_client.log_groups is not None
        ):
            for resource_policies in logs_client.resource_policies.values():
                if resource_policies is not None:
                    for resource_policy in resource_policies:
                        if is_policy_public(
                            resource_policy.policy, logs_client.audited_account
                        ):
                            for statement in resource_policy.policy.get(
                                "Statement", []
                            ):
                                public_resources = statement.get("Resource", [])
                                if isinstance(public_resources, str):
                                    public_resources = [public_resources]
                                for resource in public_resources:
                                    for log_group in logs_client.log_groups.values():
                                        if log_group.arn in resource or resource == "*":
                                            public_log_groups.append(log_group.arn)
            for log_group in logs_client.log_groups.values():
                report = Check_Report_AWS(metadata=self.metadata(), resource=log_group)
                report.status = "PASS"
                report.status_extended = (
                    f"Log Group {log_group.name} is not publicly accessible."
                )
                if log_group.arn in public_log_groups:
                    report.status = "FAIL"
                    report.status_extended = (
                        f"Log Group {log_group.name} is publicly accessible."
                    )

                findings.append(report)

        return findings