Skip to main content

CloudWatch Log Group Contains No Secrets in Logs

Overview

This check scans your CloudWatch Log Groups for embedded credentials and sensitive patterns such as API keys, passwords, tokens, and encryption keys. Secrets accidentally logged by applications can be discovered by anyone with read access to those logs, creating a significant security risk.

Risk

When secrets appear in log files:

  • Unauthorized access: Attackers who gain log access can reuse exposed credentials to call AWS APIs or access other systems.
  • Privilege escalation: Leaked credentials may belong to highly privileged accounts, allowing attackers to elevate their access.
  • Data exfiltration: Exposed keys can be used to access databases, storage, or other sensitive resources.
  • Amplified exposure: Log subscriptions, exports to S3, or cross-account sharing can spread secrets beyond the original log group.

Remediation Steps

Prerequisites

  • AWS Console access with permissions to modify CloudWatch Logs settings
  • Ability to identify which log groups contain secrets (Prowler findings will list them)
Required IAM permissions (for administrators)

To apply data protection policies, you need these permissions:

{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:PutDataProtectionPolicy",
"logs:GetDataProtectionPolicy",
"logs:DeleteDataProtectionPolicy",
"logs:DescribeLogGroups"
],
"Resource": "arn:aws:logs:*:*:log-group:*"
}
]
}

AWS Console Method

The primary remediation is to apply a data protection policy that masks secrets in your logs. This prevents anyone without special permissions from viewing the sensitive data.

  1. Open the CloudWatch console in us-east-1.

  2. In the left navigation, select Log groups.

  3. Find and click on the log group flagged by Prowler.

  4. Select the Data protection tab.

  5. Click Activate data protection.

  6. Under Managed data identifiers, expand Credentials and select the relevant patterns:

    • AWS Secret Access Key
    • OpenSSH Private Key
    • PGP Private Key
    • JSON Web Token
    • (Select all that apply to your environment)
  7. For Audit findings destination, choose at least one:

    • CloudWatch Logs (creates a findings log group)
    • S3 bucket
    • Firehose delivery stream
  8. Click Activate data protection.

Important: This policy only masks data in log events ingested after activation. Existing secrets in old log events remain visible.

AWS CLI (optional)

Apply a data protection policy

Create a JSON file named data-protection-policy.json:

{
"Name": "credentials-protection-policy",
"Description": "Mask credentials in CloudWatch Logs",
"Version": "2021-06-01",
"Statement": [
{
"Sid": "audit-policy",
"DataIdentifier": [
"arn:aws:dataprotection::aws:data-identifier/AwsSecretKey",
"arn:aws:dataprotection::aws:data-identifier/OpenSshPrivateKey",
"arn:aws:dataprotection::aws:data-identifier/PgpPrivateKey",
"arn:aws:dataprotection::aws:data-identifier/JsonWebToken"
],
"Operation": {
"Audit": {
"FindingsDestination": {
"CloudWatchLogs": {
"LogGroup": "/aws/data-protection/audit-findings"
}
}
}
}
},
{
"Sid": "redact-policy",
"DataIdentifier": [
"arn:aws:dataprotection::aws:data-identifier/AwsSecretKey",
"arn:aws:dataprotection::aws:data-identifier/OpenSshPrivateKey",
"arn:aws:dataprotection::aws:data-identifier/PgpPrivateKey",
"arn:aws:dataprotection::aws:data-identifier/JsonWebToken"
],
"Operation": {
"Deidentify": {
"MaskConfig": {}
}
}
}
]
}

Apply the policy:

aws logs put-data-protection-policy \
--log-group-identifier "<your-log-group-name>" \
--policy-document file://data-protection-policy.json \
--region us-east-1

Verify the policy is applied

aws logs get-data-protection-policy \
--log-group-identifier "<your-log-group-name>" \
--region us-east-1
CloudFormation (optional)

CloudFormation template

This template creates a log group with a data protection policy already configured:

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudWatch Log Group with Data Protection Policy

Parameters:
LogGroupName:
Type: String
Description: Name of the CloudWatch Log Group
Default: /application/my-app

AuditLogGroupName:
Type: String
Description: Log group for audit findings
Default: /aws/data-protection/audit-findings

Resources:
AuditLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref AuditLogGroupName
RetentionInDays: 30

ApplicationLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Ref LogGroupName
RetentionInDays: 30
DataProtectionPolicy:
Name: credentials-protection-policy
Description: Mask credentials in logs
Version: '2021-06-01'
Statement:
- Sid: audit-policy
DataIdentifier:
- arn:aws:dataprotection::aws:data-identifier/AwsSecretKey
- arn:aws:dataprotection::aws:data-identifier/OpenSshPrivateKey
- arn:aws:dataprotection::aws:data-identifier/PgpPrivateKey
- arn:aws:dataprotection::aws:data-identifier/JsonWebToken
Operation:
Audit:
FindingsDestination:
CloudWatchLogs:
LogGroup: !Ref AuditLogGroupName
- Sid: redact-policy
DataIdentifier:
- arn:aws:dataprotection::aws:data-identifier/AwsSecretKey
- arn:aws:dataprotection::aws:data-identifier/OpenSshPrivateKey
- arn:aws:dataprotection::aws:data-identifier/PgpPrivateKey
- arn:aws:dataprotection::aws:data-identifier/JsonWebToken
Operation:
Deidentify:
MaskConfig: {}

Outputs:
LogGroupArn:
Description: ARN of the protected log group
Value: !GetAtt ApplicationLogGroup.Arn

Deploy with:

aws cloudformation deploy \
--template-file log-group-protection.yaml \
--stack-name log-group-data-protection \
--region us-east-1
Terraform (optional)

Terraform configuration

terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.0"
}
}
}

provider "aws" {
region = "us-east-1"
}

variable "log_group_name" {
description = "Name of the CloudWatch Log Group"
type = string
default = "/application/my-app"
}

variable "audit_log_group_name" {
description = "Log group for audit findings"
type = string
default = "/aws/data-protection/audit-findings"
}

# Audit log group for findings
resource "aws_cloudwatch_log_group" "audit" {
name = var.audit_log_group_name
retention_in_days = 30
}

# Application log group with data protection
resource "aws_cloudwatch_log_group" "application" {
name = var.log_group_name
retention_in_days = 30
}

# Data protection policy
resource "aws_cloudwatch_log_data_protection_policy" "credentials" {
log_group_name = aws_cloudwatch_log_group.application.name

policy_document = jsonencode({
Name = "credentials-protection-policy"
Description = "Mask credentials in logs"
Version = "2021-06-01"
Statement = [
{
Sid = "audit-policy"
DataIdentifier = [
"arn:aws:dataprotection::aws:data-identifier/AwsSecretKey",
"arn:aws:dataprotection::aws:data-identifier/OpenSshPrivateKey",
"arn:aws:dataprotection::aws:data-identifier/PgpPrivateKey",
"arn:aws:dataprotection::aws:data-identifier/JsonWebToken"
]
Operation = {
Audit = {
FindingsDestination = {
CloudWatchLogs = {
LogGroup = aws_cloudwatch_log_group.audit.name
}
}
}
}
},
{
Sid = "redact-policy"
DataIdentifier = [
"arn:aws:dataprotection::aws:data-identifier/AwsSecretKey",
"arn:aws:dataprotection::aws:data-identifier/OpenSshPrivateKey",
"arn:aws:dataprotection::aws:data-identifier/PgpPrivateKey",
"arn:aws:dataprotection::aws:data-identifier/JsonWebToken"
]
Operation = {
Deidentify = {
MaskConfig = {}
}
}
}
]
})
}

output "log_group_arn" {
description = "ARN of the protected log group"
value = aws_cloudwatch_log_group.application.arn
}

Apply with:

terraform init
terraform plan
terraform apply

Verification

After applying the data protection policy:

  1. Return to the Data protection tab on your log group in the CloudWatch console.
  2. Confirm Data protection shows as Active.
  3. Verify the managed data identifiers you selected are listed.

To test that masking works:

  1. Have your application log a test secret (in a non-production environment).
  2. View the log event in CloudWatch - the secret should appear as asterisks (***).
CLI verification commands

Check policy is applied:

aws logs get-data-protection-policy \
--log-group-identifier "<your-log-group-name>" \
--region us-east-1 \
--query 'policyDocument' \
--output text

Re-run Prowler to confirm the finding is resolved:

prowler aws --check cloudwatch_log_group_no_secrets_in_logs --region us-east-1

Additional Resources

Notes

  • Existing data is not protected: Data protection policies only mask log events ingested after the policy is applied. Consider reducing retention periods for log groups that contained secrets, or deleting old log streams.

  • Rotate exposed credentials: If Prowler detected actual secrets in your logs, rotate those credentials immediately. The masking policy does not invalidate leaked credentials.

  • Application-level prevention: The best long-term solution is to prevent secrets from being logged in the first place. Review your application logging configuration to sanitize sensitive data before it reaches CloudWatch.

  • logs:Unmask permission: Users with this IAM permission can view unmasked data. Audit who has this permission and restrict it to only those who truly need it.

  • Performance impact: Data protection scanning adds minimal latency to log ingestion. For most workloads, this is negligible.