aws/aws-cdk

(aws-logs): convertArnPrincipalToAccountId in aws-logs causes false positive CloudFormation stack drift

Open

#37797 opened on May 7, 2026

View on GitHub
 (1 comment) (0 reactions) (0 assignees)TypeScript (10,710 stars) (3,530 forks)batch import
@aws-cdk/aws-logsbugeffort/smallgood first issuep2

Description

Describe the bug

The convertArnPrincipalToAccountId() method in AWSSecureCDK_Peru (secure-cdk-lib/aws-cdk/packages/aws-cdk-lib/aws-logs/lib/log-group.ts) strips IAM principals down to just the AWS account ID when generating CloudWatch Logs resource policies via .grantRead().

For example, CDK generates:

{"AWS": "211125612616"}

But CloudFormation/AWS normalizes this to:

{"AWS": "arn:aws:iam::211125612616:root"}

These are semantically identical, but CloudFormation drift detection treats them as different, causing every stack that uses logGroup.grantRead() with a cross-account role to be flagged as drifted on every evaluation cycle.

This is generating recurring false-positive SEV3 tickets for our team (IMDbPro-DISCO) across multiple stacks and environments:

V2139077722 — IMDbListGraphQLStack (Prod)
V2114817343 — IMDbListGraphQLStack (Gamma)
V2096862319 — Fleet-PodMonitoringStack (Prod)
V2000840049 — NotificationProcessorStack (Prod)

Suggested fix: Update convertArnPrincipalToAccountId() to output the full ARN format (arn:aws:iam::ACCOUNT_ID:root) instead of just the account ID, so the CDK template matches what CloudFormation actually creates.

Impact: Any team using logGroup.grantRead() with cross-account principals will hit this same false drift issue. There is no workaround on the consumer side — the alarm architecture cannot distinguish this false positive from real drift.

Regression Issue

  • Select this option if this issue appears to be a regression.

Last Known Working CDK Library Version

No response

Expected Behavior

When LogGroup.grantRead() (or any code path reaching addToResourcePolicy) is called with a cross-account IAM principal, CDK should synthesize the resource policy principal in a form that matches what CloudFormation actually stores after deployment, so that CloudFormation drift detection does not report a persistent false positive.

Specifically, the synthesized principal should be the full root ARN:

{ "AWS": "arn:aws:iam::211125612616:root" }

This is the form CloudFormation normalizes to on the server side, and it is what drift detection compares against.

Current Behavior

LogGroup.addToResourcePolicy routes every principal through convertArnPrincipalToAccountId, which replaces the principal with a bare account ID string wrapped in iam.ArnPrincipal:

https://github.com/aws/aws-cdk/blob/main/packages/aws-cdk-lib/aws-logs/lib/log-group.ts#L308-L324

That emits the following into the synthesized CloudWatch Logs ResourcePolicyDocument:

{ "AWS": "211125612616" }

CloudFormation accepts this, but on the deployed resource it stores the canonical form:

{ "AWS": "arn:aws:iam::211125612616:root" }

CloudFormation drift detection performs a literal string comparison between the template principal ("211125612616") and the deployed principal ("arn:aws:iam::211125612616:root") and reports the stack as DRIFTED on every evaluation cycle. There is no way to suppress this from the consumer side because any addToResourcePolicy / grantRead call is rewritten by convertArnPrincipalToAccountId before synthesis.

Observed impact: every stack using logGroup.grantRead(crossAccountRole) is permanently marked drifted, generating recurring drift alarms across multiple production stacks.

No stack trace — this is a template correctness / drift-detection issue, not a runtime error.

Reproduction Steps

Minimal SSCCE:

import { App, Stack } from 'aws-cdk-lib';
import { LogGroup } from 'aws-cdk-lib/aws-logs';
import { ArnPrincipal } from 'aws-cdk-lib/aws-iam';

const app = new App();
const stack = new Stack(app, 'DriftRepro', {
  env: { account: '111111111111', region: 'us-east-1' },
});

const lg = new LogGroup(stack, 'LG');

// Cross-account role in another account
lg.grantRead(new ArnPrincipal('arn:aws:iam::211125612616:role/Reader'));

app.synth();

Synthesized AWS::Logs::ResourcePolicy contains:

{
  "PolicyDocument": {
    "Statement": [
      {
        "Principal": { "AWS": "211125612616" }
      }
    ]
  }
}

Deploy the stack, then run CloudFormation drift detection (aws cloudformation detect-stack-driftdescribe-stack-resource-drifts). The AWS::Logs::ResourcePolicy resource reports MODIFIED with:

  • Expected: "AWS": "211125612616"
  • Actual: "AWS": "arn:aws:iam::211125612616:root"

This persists forever — redeploying does not clear it because CDK keeps emitting the short form.

Possible Solution

Change convertArnPrincipalToAccountId in packages/aws-cdk-lib/aws-logs/lib/log-group.ts so that when it reduces a principal to an account, it emits the canonical root ARN rather than the bare account ID:

private convertArnPrincipalToAccountId(principal: iam.IPrincipal) {
  if (principal.principalAccount) {
    return new iam.ArnPrincipal(`arn:${Aws.PARTITION}:iam::${principal.principalAccount}:root`);
  }
  if (principal instanceof iam.ArnPrincipal && principal.arn !== '*') {
    const parsedArn = Arn.split(principal.arn, ArnFormat.SLASH_RESOURCE_NAME);
    if (parsedArn.account) {
      return new iam.ArnPrincipal(`arn:${parsedArn.partition ?? Aws.PARTITION}:iam::${parsedArn.account}:root`);
    }
  }
  return principal;
}

This produces { "AWS": "arn:aws:iam::<acct>:root" }, which is the form CloudFormation stores, so drift detection becomes a no-op. CloudWatch Logs Resource Policies accept this form (IAM/STS treats bare "<acct>" and "arn:aws:iam::<acct>:root" as equivalent references to the account root).

Existing snapshot tests under aws-logs would need regenerating, and an integration test that asserts no drift on a deployed cross-account resource policy would guard against regression.

Additional Information/Context

No response

AWS CDK Library version (aws-cdk-lib)

2.221.0 (declared range ^2.173.2)

AWS CDK CLI version

2.1023.0 (build 45ceb89)

Node.js Version

v22.22.2

OS

macOS (darwin)

Language

TypeScript

Language Version

No response

Other information

No response

Contributor guide