Skip to content
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

Merged
merged 20 commits into from
Apr 6, 2020

Conversation

zakwalters
Copy link
Contributor

@zakwalters zakwalters commented Mar 25, 2020

Commit Message

Closes #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.

End Commit Message


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

- 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.
@mergify
Copy link
Contributor

mergify bot commented Mar 25, 2020

Title does not follow the guidelines of Conventional Commits. Please adjust title before merge.

@zakwalters zakwalters changed the title feat(iam) add a wrapper for principals with conditions feat(iam): add a wrapper for principals with conditions Mar 25, 2020
@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: ea625aa
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 05a5997
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 9a32cd6
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 748f275
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

Copy link
Contributor

@rix0rrr rix0rrr left a 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[] }) {
Copy link
Contributor

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.

Copy link
Contributor Author

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[] };
Copy link
Contributor

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,
Copy link
Contributor

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.

Copy link
Contributor Author

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';
Copy link
Contributor

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?

Copy link
Contributor Author

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));
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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",
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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".

Copy link
Contributor

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".
@mergify mergify bot dismissed rix0rrr’s stale review April 2, 2020 21:37

Pull request has been modified.

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: b452592
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 410e004
  • Result: FAILED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

zakwalters and others added 2 commits April 2, 2020 23:39
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-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: ffc2bdc
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: f9119f3
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

rix0rrr
rix0rrr previously requested changes Apr 3, 2020
Copy link
Contributor

@rix0rrr rix0rrr left a 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. If jsii-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>;
Copy link
Contributor

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?

Copy link
Contributor Author

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 {
Copy link
Contributor

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).

Copy link
Contributor

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.

Copy link
Contributor Author

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];
Copy link
Contributor

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.
@mergify mergify bot dismissed rix0rrr’s stale review April 6, 2020 07:56

Pull request has been modified.

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 9fad94c
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 6b20828
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

rix0rrr
rix0rrr previously approved these changes Apr 6, 2020
@rix0rrr rix0rrr changed the title feat(iam): add a wrapper for principals with conditions feat(iam): add arbitrary conditions to existing principals Apr 6, 2020
@mergify mergify bot dismissed rix0rrr’s stale review April 6, 2020 12:11

Pull request has been modified.

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: d955b9c
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: f18db86
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify
Copy link
Contributor

mergify bot commented Apr 6, 2020

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-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: b88bc98
  • Result: SUCCEEDED
  • Build Logs (available for 30 days)

Powered by github-codebuild-logs, available on the AWS Serverless Application Repository

@mergify
Copy link
Contributor

mergify bot commented Apr 6, 2020

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).

@mergify mergify bot merged commit 64bad91 into aws:master Apr 6, 2020
horsmand pushed a commit to horsmand/aws-cdk that referenced this pull request Apr 8, 2020
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[IAM] Ability to add conditions to AccountPrincipal
3 participants