aws-cloudwatch-read-log-data
logs:GetLogEvents, logs:FilterLogEvents, or logs:StartQuery + logs:GetQueryResults on a log group enables reading raw log events from the LoggingService node; application logs frequently embed secrets, tokens, or credentials usable for credential harvest.
match
A conjunctive graph pattern. Variables (?x) bind node ids; every clause must hold.
where
LoggingService
?log_group.provider_type == 'AWS::Logs::LogGroup'
?principal has EFFECTIVE (logs:GetLogEvents OR logs:FilterLogEvents OR (logs:StartQuery AND logs:GetQueryResults)) on ?log_group ARN
emit
| source type | Identity |
|---|---|
| target type | LoggingService |
| source | ?principal |
| target | ?log_group |
| permissions | logs:GetLogEvents logs:FilterLogEvents logs:StartQuery logs:GetQueryResults |
| conditions | iam_permission resource_policy key_permission |
| state logic | ACTIVE when logs:GetLogEvents OR logs:FilterLogEvents is confirmed EFFECTIVE on the log group ARN (identity-based policy + no resource-policy deny + no SCP deny) AND the log group is NOT KMS-encrypted (or the caller also holds kms:Decrypt on the log group's CMK). ACTIVE when both logs:StartQuery AND logs:GetQueryResults are confirmed EFFECTIVE on the log group ARN (Insights query pair; neither action alone yields data). CONDITIONAL(iam_permission) when only one of the Insights pair is confirmed (StartQuery without GetQueryResults or vice versa). CONDITIONAL(key_permission) when the log group has a kmsKeyId set and kms:Decrypt on that CMK is not confirmed — credential-chains:decrypt-gates-secret-read upgrades to ACTIVE when CanDecrypt is confirmed on the CMK. CONDITIONAL(resource_policy) when a resource policy deny or missing grant caps the effective permission. BLOCKED by an explicit deny on logs:GetLogEvents / logs:FilterLogEvents / logs:StartQuery / logs:GetQueryResults, or on kms:Decrypt (for encrypted groups). The downstream ContainsSecret + CanImpersonate chain (credential-chains) is CONDITIONAL on the ContainsSecret annotation being confirmed — a heuristic ContainsSecret (confidence 0.5) does not produce an ACTIVE CanImpersonate. |
Narrative
{principal.name} can read log events from {log_group.name} (logs:GetLogEvents / logs:FilterLogEvents / logs:StartQuery + logs:GetQueryResults). Application log groups frequently contain credential material embedded in log output (environment variable dumps, stack traces exposing connection strings, SDK debug logging printing session tokens). If {log_group.name} carries a ContainsSecret annotation, the credential-chains rollup (read-secret-yields- identity) produces CanImpersonate over the embedded credential's target identity. This is a credential-harvest path - CanRead on a log group does not by itself grant access to compute or IAM.
Raw rule rules/derived/aws/cloudwatch.yaml
id: aws-cloudwatch-read-log-data
emits: CanRead
description: logs:GetLogEvents, logs:FilterLogEvents, or logs:StartQuery + logs:GetQueryResults on a log
group enables reading raw log events from the LoggingService node; application logs frequently embed
secrets, tokens, or credentials usable for credential harvest.
match:
- - principal: null
- HasPermission
- log_group: null
where:
- node_type(?log_group) == LoggingService
- ?log_group.provider_type == 'AWS::Logs::LogGroup'
- ?principal has EFFECTIVE (logs:GetLogEvents OR logs:FilterLogEvents OR (logs:StartQuery AND logs:GetQueryResults))
on ?log_group ARN
emit:
source_type: Identity
target_type: LoggingService
source: ?principal
target: ?log_group
permissions:
- logs:GetLogEvents
- logs:FilterLogEvents
- logs:StartQuery
- logs:GetQueryResults
conditions:
- iam_permission
- resource_policy
- key_permission
state_logic: "ACTIVE when logs:GetLogEvents OR logs:FilterLogEvents is confirmed EFFECTIVE on the log\
\ group ARN (identity-based policy + no resource-policy deny + no SCP deny) AND the log group is NOT\
\ KMS-encrypted (or the caller also holds kms:Decrypt on the log group's CMK). ACTIVE when both logs:StartQuery\
\ AND logs:GetQueryResults are confirmed EFFECTIVE on the log group ARN (Insights query pair; neither\
\ action alone yields data). CONDITIONAL(iam_permission) when only one of the Insights pair is confirmed\
\ (StartQuery without GetQueryResults or vice versa). CONDITIONAL(key_permission) when the log group\
\ has a kmsKeyId set and kms:Decrypt on that CMK is not confirmed \u2014 credential-chains:decrypt-gates-secret-read\
\ upgrades to ACTIVE when CanDecrypt is confirmed on the CMK. CONDITIONAL(resource_policy) when a\
\ resource policy deny or missing grant caps the effective permission. BLOCKED by an explicit deny\
\ on logs:GetLogEvents / logs:FilterLogEvents / logs:StartQuery / logs:GetQueryResults, or on kms:Decrypt\
\ (for encrypted groups). The downstream ContainsSecret + CanImpersonate chain (credential-chains)\
\ is CONDITIONAL on the ContainsSecret annotation being confirmed \u2014 a heuristic ContainsSecret\
\ (confidence 0.5) does not produce an ACTIVE CanImpersonate."
confidence: min(contributing_confidences) * 0.9
derived_from:
- ?principal HasPermission ?log_group (logs:GetLogEvents / logs:FilterLogEvents effective permission)
- "credential-chains:read-secret-yields-identity (downstream credential harvest \u2014 cited not re-derived)"
- "credential-chains:decrypt-gates-secret-read (KMS-encrypted log group gate \u2014 cited not re-derived)"
false_positive_note: "logs:GetLogEvents and logs:FilterLogEvents are distinct actions; either alone\
\ is sufficient for log-event access (no pair requirement). logs:GetLogEvents reads a specific log\
\ stream; logs:FilterLogEvents searches across all streams in the group with a filter pattern. Either\
\ grants the CanRead capability \u2014 emit for whichever is confirmed effective. logs:StartQuery\
\ and logs:GetQueryResults form an Insights query pair: BOTH actions are required on the log group\
\ ARN to yield results (StartQuery initiates the query, GetQueryResults retrieves results). Emit ACTIVE\
\ only when BOTH are confirmed; emit CONDITIONAL(iam_permission) if only one is confirmed. KMS-encrypted\
\ log groups: logs:GetLogEvents returns encrypted event data when called by a principal that lacks\
\ kms:Decrypt on the log group's CMK \u2014 emit as CONDITIONAL(key_permission) in this case. credential-chains:decrypt-gates-\
\ secret-read handles the upgrade when CanDecrypt is confirmed. ContainsSecret is a heuristic annotation\
\ \u2014 not all log groups contain credential material. The CanRead edge is always valid when the\
\ permission is confirmed; the downstream CanImpersonate (credential-chains) is only ACTIVE when ContainsSecret\
\ is confirmed with high confidence and CredentialsFor an identity is evidenced. Do not over-claim:\
\ a log group of VPC Flow Logs contains IP addresses, not credentials \u2014 CanRead is still valid\
\ for recon but ContainsSecret should NOT be annotated. logs:DescribeLogGroups / logs:DescribeLogStreams\
\ are recon-grade and do NOT warrant this CanRead edge (they return metadata, not event content).\
\ Emits CanRead (not CanReadData) because schema/edges.yaml CanReadData.target is restricted to [Storage,\
\ Data, Messaging]; LoggingService is a ManagementService subtype and falls outside that set."
narrative: "{principal.name} can read log events from {log_group.name} (logs:GetLogEvents / logs:FilterLogEvents\
\ / logs:StartQuery + logs:GetQueryResults). Application log groups frequently contain credential\
\ material embedded in log output (environment variable dumps, stack traces exposing connection strings,\
\ SDK debug logging printing session tokens). If {log_group.name} carries a ContainsSecret annotation,\
\ the credential-chains rollup (read-secret-yields- identity) produces CanImpersonate over the embedded\
\ credential's target identity. This is a credential-harvest path \u2014 CanRead on a log group does\
\ not by itself grant access to compute or IAM."