-
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): add arbitrary conditions to existing principals #7015
Conversation
- Test that conditions are merged with those from `PolicyDocument.addCondition`. - Test that the original principal is unmodified after using `principal.withConditions` (this test is more for invariance - the behaviour may be changed to add conditions to the underlying principal).
In the case of both conflicting keys and conflicting operators, the behaviour is inherited from the way that `PolicyStatement.addCondition` already works. When the same operator is specified in two different conditions, the objects will be merged. When the same key is specified under the same operator in two different conditions, the one added later will overwrite the existing one.
Moves the Conditions type into policy-statement.ts as it makes more sense there than under principals.ts. Changes the other functions that work with conditions to use this type rather than the more permissive `{ [key: string]: any }` which could cause failures in CloudFormation. We cannot model the Conditions type more strongly (e.g. with an object that includes all valid operators as optional keys) because of errors thrown by JSII.
`addCondition` and `addConditions` are added to the `PrincipalWithConditions` class to make its interface more consistent with what users are used to from the `PolicyStatement` class.
Title does not follow the guidelines of Conventional Commits. Please adjust title before merge. |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
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.
Thanks for this! Would prefer some slight changes but I like the direction this is going!
@@ -200,15 +200,15 @@ export class PolicyStatement { | |||
/** | |||
* Add a condition to the Policy | |||
*/ | |||
public addCondition(key: string, value: any) { | |||
public addCondition(key: string, value: { [key: string]: string | string[] }) { |
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.
Unfortunately we can't retype this this accurately, because we can't support unions, so retype string | string[]
as any
.
We should probably have the following type aliases, no?
export type Condition = Record<string, any>;
export type Conditions = Record<string, Condition>;
Then this whole argument could be typed as Condition
.
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.
Sounds good, thanks for the pointer.
* See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html | ||
*/ | ||
export interface Conditions { | ||
[operator: string]: { [key: string]: string | string[] }; |
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.
No union type as mentioned.
public readonly assumeRoleAction: string = 'sts:AssumeRole'; | ||
|
||
constructor( | ||
public readonly principal: PrincipalType, |
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.
I would be fine not exposing this.
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.
Sure, can't say I have a strong argument for it so happy to make it private
export class PrincipalWithConditions<PrincipalType extends PrincipalBase> implements IPrincipal { | ||
public readonly conditions: Conditions; | ||
public readonly grantPrincipal: IPrincipal = this; | ||
public readonly assumeRoleAction: string = 'sts:AssumeRole'; |
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.
I think this needs to be this.principal.assumeRoleAction
?
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.
Totally right, thanks
conditions: Conditions, | ||
) { | ||
// Copy the intiial conditions from the principal | ||
this.conditions = JSON.parse(JSON.stringify(this.principal.policyFragment.conditions)); |
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 feels dangerous, I'm not sure tokens will survive this properly. What if we just keep conditions
not as a mutable copy of the original, but as a list of mutations? So it starts out empty, and we deep-merge it upon rendering?
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.
I was umm-ing and ahh-ing a bit over the approach here. I went with the copy largely because it was simpler than having a deep merge happen on principal.policyFragment
and principal.conditions
(so that the conditions
getter returns the same thing the final render will include), but I don't have a good sense of how tokens work. If it's likely to break tokens, I'm happy to make the switch.
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.
Can you please try that? It will make me feel better.
@@ -149,6 +149,9 @@ | |||
"docs-public-apis:@aws-cdk/aws-iam.PrincipalBase.toJSON", | |||
"docs-public-apis:@aws-cdk/aws-iam.PrincipalPolicyFragment.conditions", | |||
"docs-public-apis:@aws-cdk/aws-iam.PrincipalPolicyFragment.principalJson", | |||
"docs-public-apis:@aws-cdk/aws-iam.PrincipalWithConditions.conditions", |
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.
Please document the offending members instead of silencing the errors.
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.
Will do.
I took the silencing of errors for the same/similar fields on PrincipalBase
and PrincipalPolicyFragment
to indicate there had been a decision that there was nothing worth documenting about these fields. If that's not the case, I'll add docs.
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.
In fact the decision was "we don't want to regress docs going forward but we're not going to fix all instances before introducing the check".
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.
So all existing members should be documented but we haven't gotten around to it yet.
- Change the conditions type to be `Record<string, Record<string, any>>` so as not to cause breaking changes once JSII transpiles. The change from an interface to a type alias is necessary because an interface in TS will convert to a named type in languages with nominal rather than structural typing. The change from `string | string[]` to `any` is needed because union types can't be supported. - Rather than copy the conditions from the wrapped principal when the `PrincipalWithConditions` is constructed, just merge the conditions in the getter for `conditions` to avoid any problems with tokens. - Add documentation for all public members. - Make wrapped principal private. - Inherit `assumeRoleAction` from the wrapped principal rather than always using "sts:AssumeRole".
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Using `Record<string, Record<string, any>>` still caused breaking changes so change back to using `Record<string, any>`. At least the docs for it are improved.
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
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.
Thanks for the changes!
I still have some questions about the types.
- I think the additional typing on the
Conditions
/Condition
arguments are valuable. Ifjsii-diff
was complaining on you, don't worry about it. Please put that back, and I will silence the linter. - Unfortunately no generics. Could you take those out?
* For more information, including which operators are supported, see [the IAM | ||
* documentation](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html). | ||
*/ | ||
export type Conditions = Record<string, any>; |
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.
Wait I believe we said two-level types here, right?
type Conditions = Record<string, Condition>;
type Condition = Record<string, any>;
Is the compatibility checker giving you guff if you change this?
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.
Yeah, I tried it and it still flagged as causing breaking changes.
From the failed build logs:
API elements with incompatible changes:
err - INITIALIZER @aws-cdk/aws-iam.FederatedPrincipal.<initializer>: argument conditions, takes Map<string => Map<string => any>> (formerly Map<string => any>): Map<string => any> is not assignable to Map<string => Map<string => any>>, any is not a map type [incompatible-argument:@aws-cdk/aws-iam.FederatedPrincipal.<initializer>]
err - METHOD @aws-cdk/aws-iam.PolicyStatement.addCondition: argument value, takes Map<string => string | Array<string>> (formerly any): any is not a map type [incompatible-argument:@aws-cdk/aws-iam.PolicyStatement.addCondition]
err - METHOD @aws-cdk/aws-iam.PolicyStatement.addConditions: argument conditions, takes Map<string => Map<string => any>> (formerly Map<string => any>): Map<string => any> is not assignable to Map<string => Map<string => any>>, any is not a map type [incompatible-argument:@aws-cdk/aws-iam.PolicyStatement.addConditions]
err - METHOD @aws-cdk/aws-iam.PolicyStatement.addFederatedPrincipal: argument conditions, takes Map<string => Map<string => any>> (formerly Map<string => any>): Map<string => any> is not assignable to Map<string => Map<string => any>>, any is not a map type [incompatible-argument:@aws-cdk/aws-iam.PolicyStatement.addFederatedPrincipal]
err - INITIALIZER @aws-cdk/aws-iam.PrincipalPolicyFragment.<initializer>: argument conditions, takes Optional<Map<string => Map<string => any>>> (formerly Optional<Map<string => any>>): Map<string => any> is not assignable to Map<string => Map<string => any>>, any is not a map type [incompatible-argument:@aws-cdk/aws-iam.PrincipalPolicyFragment.<initializer>]
For reference, here's the commit where I changed it back (and caught the union type I'd somehow missed removing before).
* For more information about conditions, see: | ||
* https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_condition.html | ||
*/ | ||
export class PrincipalWithConditions<PrincipalType extends PrincipalBase> implements IPrincipal { |
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.
Oh oops, was this in here beforehand? We can't have generics in this code base because jsii doesn't support them (because Go doesn't support them).
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.
Since the inner principal is private you don't really need the generics.
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.
Yeah, good point about it being obsolete now. Will get rid of it.
mergedConditions[operator] = condition; | ||
}); | ||
Object.entries(additionalConditions).forEach(([operator, condition]) => { | ||
const existingCondition = mergedConditions[operator]; |
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.
Since it's legal to can splat undefined
in an object context (oh Javascript...), you could avoid the if
and just go:
mergedConditions[operator] = {...mergedConditions[operator], ...condition};
This reverts commit ffc2bdc. Also adds the required ignores for these "breaking" changes to be accepted, and fixes the one remaining union type.
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
AWS CodeBuild CI Report
Powered by github-codebuild-logs, available on the AWS Serverless Application Repository |
Thank you for contributing! Your pull request will be updated from master and then merged automatically (do not update manually, and be sure to allow changes to be pushed to your fork). |
Closes aws#5855 Adds a `PrincipalWithConditions` wrapper that allows conditions to be added to any principal. Behaviour is consistent with the way that `PolicyStatement.addCondition` and `.addConditions` currently work - most notably in that adding an operator that is already present will merge their objects, but adding a condition to an operator/key combination that is already set will overwrite the existing value (rather than merge the values into an array). BREAKING CHANGES: every place an IAM Condition was expected it is now typed as `{[key: string]: any}`, instead of plain `any`. You were always supposed to pass a map/dictionary in these locations, but the type system didn't enforce it. It now does. This will not impact correct programs, but may cause compiler errors in programs that were incorrect.
Commit Message
Closes #5855
Adds a
PrincipalWithConditions
wrapper that allows conditions to be added toany principal.
Behaviour is consistent with the way that
PolicyStatement.addCondition
and
.addConditions
currently work - most notably in that adding an operatorthat is already present will merge their objects, but adding a condition to
an operator/key combination that is already set will overwrite the existing
value (rather than merge the values into an array).
BREAKING CHANGES: every place an IAM Condition was expected it is now typed as
{[key: string]: any}
, instead of plainany
. You were always supposed to pass a map/dictionary in these locations, but the type system didn't enforce it. It now does. This will not impact correct programs, but may cause compiler errors in programs that were incorrect.End Commit Message
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license