-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(iam): avoid duplicate statements in policy documents #2254
Changes from 8 commits
4c24fb3
dec38e4
fb71270
0baa26a
8a0c36c
185edd0
2afd5d3
94028c1
c421907
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,15 +3,15 @@ import { Default, RegionInfo } from '@aws-cdk/region-info'; | |
import { IPrincipal } from './principals'; | ||
import { mergePrincipal } from './util'; | ||
|
||
export class PolicyDocument extends cdk.Token { | ||
export class PolicyDocument extends cdk.Token implements cdk.IResolvedValuePostProcessor { | ||
private statements = new Array<PolicyStatement>(); | ||
|
||
/** | ||
* Creates a new IAM policy document. | ||
* @param defaultDocument An IAM policy document to use as an initial | ||
* policy. All statements of this document will be copied in. | ||
*/ | ||
constructor(private readonly baseDocument?: any) { | ||
constructor(private readonly baseDocument: any = {}) { | ||
super(); | ||
} | ||
|
||
|
@@ -20,13 +20,40 @@ export class PolicyDocument extends cdk.Token { | |
return undefined; | ||
} | ||
|
||
const doc = this.baseDocument || { }; | ||
doc.Statement = doc.Statement || [ ]; | ||
doc.Version = doc.Version || '2012-10-17'; | ||
doc.Statement = doc.Statement.concat(this.statements); | ||
const doc = { | ||
...this.baseDocument, | ||
Statement: (this.baseDocument.Statement || []).concat(this.statements), | ||
Version: this.baseDocument.Version || '2012-10-17' | ||
}; | ||
|
||
return doc; | ||
} | ||
|
||
/** | ||
* Removes duplicate statements | ||
*/ | ||
public postProcess(input: any, _context: cdk.ResolveContext): any { | ||
if (!input || !input.Statement) { | ||
return input; | ||
} | ||
|
||
const jsonStatements = new Set<string>(); | ||
const uniqueStatements: PolicyStatement[] = []; | ||
|
||
for (const statement of input.Statement) { | ||
const jsonStatement = JSON.stringify(statement); | ||
if (!jsonStatements.has(jsonStatement)) { | ||
uniqueStatements.push(statement); | ||
jsonStatements.add(jsonStatement); | ||
} | ||
} | ||
|
||
return { | ||
...input, | ||
Statement: uniqueStatements | ||
}; | ||
} | ||
|
||
get isEmpty(): boolean { | ||
return this.statements.length === 0; | ||
} | ||
|
@@ -39,6 +66,11 @@ export class PolicyDocument extends cdk.Token { | |
return this.statements.length; | ||
} | ||
|
||
/** | ||
* Adds a statement to the policy document. | ||
* | ||
* @param statement the statement to add. | ||
*/ | ||
public addStatement(statement: PolicyStatement): PolicyDocument { | ||
this.statements.push(statement); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of doing this, implement This is guaranteed to receive the post-resolution Statement, and you can do safe pairwise comparisons based on |
||
return this; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -39,16 +39,14 @@ export class LogRetention extends cdk.Construct { | |
lambdaPurpose: 'LogRetention', | ||
}); | ||
|
||
if (provider.role && !provider.role.node.tryFindChild('DefaultPolicy')) { // Avoid duplicate statements | ||
provider.role.addToPolicy( | ||
new iam.PolicyStatement() | ||
.addActions('logs:PutRetentionPolicy', 'logs:DeleteRetentionPolicy') | ||
// We need '*' here because we will also put a retention policy on | ||
// the log group of the provider function. Referencing it's name | ||
// creates a CF circular dependency. | ||
.addAllResources() | ||
); | ||
} | ||
provider.addToRolePolicy( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add a note here that duplicate statements will be deduplicated by |
||
new iam.PolicyStatement() | ||
.addActions('logs:PutRetentionPolicy', 'logs:DeleteRetentionPolicy') | ||
// We need '*' here because we will also put a retention policy on | ||
// the log group of the provider function. Referencing it's name | ||
// creates a CF circular dependency. | ||
.addAllResources() | ||
); | ||
|
||
// Need to use a CfnResource here to prevent lerna dependency cycles | ||
// @aws-cdk/aws-cloudformation -> @aws-cdk/aws-lambda -> @aws-cdk/aws-cloudformation | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This concatenation happens at construction time - when
this.statements
is guaranteed to be empty. You need to wrap this in aToken
so it is done at synthesis time.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It happens in
resolve()
notconstructor()
, it was like that before.https://github.com/awslabs/aws-cdk/blob/b61707896582373c4526129945ddaac20055f313/packages/%40aws-cdk/aws-iam/lib/policy-document.ts#L23-L27