From aec09d0b47577f5d5041c449e77aed43e69b0211 Mon Sep 17 00:00:00 2001 From: Baskaran Jeyarajan Date: Sat, 22 Nov 2025 00:55:08 -0500 Subject: [PATCH] fix(cloudformation-diff): show PermissionSet as principal in IAM statement changes --- .../lib/iam/iam-changes.ts | 32 +++++++++++++++++-- .../test/iam/detect-changes.test.ts | 4 +-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/packages/@aws-cdk/cloudformation-diff/lib/iam/iam-changes.ts b/packages/@aws-cdk/cloudformation-diff/lib/iam/iam-changes.ts index 9d1d39469..a5294764b 100644 --- a/packages/@aws-cdk/cloudformation-diff/lib/iam/iam-changes.ts +++ b/packages/@aws-cdk/cloudformation-diff/lib/iam/iam-changes.ts @@ -287,8 +287,15 @@ export class IamChanges { break; case PropertyScrutinyType.InlineResourcePolicy: // Any PolicyDocument on a resource (including AssumeRolePolicyDocument) - this.statements.addOld(...this.readResourceStatements(propertyChange.oldValue, propertyChange.resourceLogicalId)); - this.statements.addNew(...this.readResourceStatements(propertyChange.newValue, propertyChange.resourceLogicalId)); + // Special-case AWS::SSO::PermissionSet as a pseudo-principal in the IAM statement changes output. + if (propertyChange.resourceType === 'AWS::SSO::PermissionSet') { + this.statements.addOld(...this.readPermissionSetInlinePolicy(propertyChange.oldValue, propertyChange.resourceLogicalId)); + this.statements.addNew(...this.readPermissionSetInlinePolicy(propertyChange.newValue, propertyChange.resourceLogicalId)); + } else { + // Existing behaviour for all other resources + this.statements.addOld(...this.readResourceStatements(propertyChange.oldValue, propertyChange.resourceLogicalId)); + this.statements.addNew(...this.readResourceStatements(propertyChange.newValue, propertyChange.resourceLogicalId)); + } break; case PropertyScrutinyType.ManagedPolicies: // Just a list of managed policies @@ -413,6 +420,27 @@ export class IamChanges { })]; } + private readPermissionSetInlinePolicy(policy: any, logicalId: string): Statement[] { + if (policy === undefined) { + return []; + } + + // For PermissionSet inline policies: + // - Resource: still defaulted to the PermissionSet ARN when wildcarded + // - Principal: a pseudo-principal that identifies the PermissionSet + const appliesToResource = '${' + logicalId + '.Arn}'; + const appliesToPrincipal = 'AWS:${' + logicalId + '}'; + + const statements = parseStatements(renderIntrinsics(policy.Statement)); + + // Keeping the existing behaviour for Resource… + defaultResource(appliesToResource, statements); + // …and additionally injecting a pseudo-principal for readability + defaultPrincipal(appliesToPrincipal, statements); + + return statements; + } + private readResourceStatements(policy: any, logicalId: string): Statement[] { if (policy === undefined) { return []; diff --git a/packages/@aws-cdk/cloudformation-diff/test/iam/detect-changes.test.ts b/packages/@aws-cdk/cloudformation-diff/test/iam/detect-changes.test.ts index 8caa7c593..9765e9781 100644 --- a/packages/@aws-cdk/cloudformation-diff/test/iam/detect-changes.test.ts +++ b/packages/@aws-cdk/cloudformation-diff/test/iam/detect-changes.test.ts @@ -564,7 +564,7 @@ test('can summarize negative ssoPermissionSet changes with PermissionsBoundary.C '${MySsoPermissionSet.Arn}', 'Allow', 'iam:CreateServiceLinkedRole', - '', + 'AWS:${MySsoPermissionSet}', '', ].map(s => chalk.red(s)), ], @@ -609,7 +609,7 @@ test('can summarize ssoPermissionSet changes with PermissionsBoundary.CustomerMa '${MySsoPermissionSet.Arn}', 'Allow', 'iam:CreateServiceLinkedRole', - '', + 'AWS:${MySsoPermissionSet}', '', ].map(s => chalk.green(s)), ],