-
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
fix(iam): no trust policy added for cross-account roles #5812
Conversation
Trust policies should be added to resources when trying to grant permissions to a principal in a different account. Typically this is a Service Principal, but it could also be an imported Role from a different account (and the same should not be done for a Role in the same account). Our previous API (`addToPolicy` returning `true|false`) was designed for in-account Roles and cross-account ServicePrincipals, but was not rich enough to make the distinction that Roles could be cross-account. Add a function to `IPrincipal` to ask it whether it is in the same or a different account from a given scope, and have `Grant` respect that. Also in this commit: a bugfix for `ImmutableRole` to make it work correctly with `Grant` Also in this commit: `parseArn()` used to behave the same for `parseArn(TOKEN)` and `parseArn("arn:{TOKEN}:svc:1234:...")`. Make it (opportunistically) do the right thing in the latter case, parsing out the concrete components and the TOKEN components separately. This makes it possible to parse out the concrete account number from an ARN that contains tokenized parts we don't care about. Fixes #5740.
This change might be contentious, in the addition of Would appreciate some advice here. Also see the set of test cases for coverage. |
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.
I am OK with this.
Adding the 'do-not-merge' label so that others can chime in as well.
@@ -341,7 +341,8 @@ describe('IAM polocy document', () => { | |||
get grantPrincipal() { return this; }, | |||
assumeRoleAction: 'sts:AssumeRole', | |||
policyFragment: new PrincipalPolicyFragment({ AWS: ['foo', 'bar'] }), | |||
addToPolicy() { return false; } | |||
addToPolicy() { return false; }, | |||
sameAccount() { return false; } |
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.
Trailing comma, to avoid the diff problem in the future? :)
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've actually changed my mind on this, and I want to explore an alternative solution. I think we should develop something here that's usable across all construct libraries, not specific to IAM. We have a related issue about this around the necessity of having access to account
and region
on a resource level, instead of just stack level, but I can't find it :/.
I didn't have time yet to dive deep into this issue, but I plan on doing it.
@rix0rrr are you OK not merging this for the time being, and giving me some time to work on it?
I'm always in favor of better solutions for anything. As long as we can solve the same issue as mentioned here, go ahead.
I guess because of the nature of our deployment mechanism:
That seems to be the crux of it, and you're right it holds for all imported resources, not just roles.
As long as you incorporate the tests of this PR into whatever you come up with, sure! |
@@ -77,5 +77,5 @@ export class ScopedAws { | |||
} | |||
|
|||
function pseudoString(name: string): string { | |||
return Token.asString({ Ref: name }, { displayHint: name }); | |||
return Token.asString({ Ref: name }, { displayHint: name.replace('::', '.') }); |
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.
why is this needed?
* Returns undefined if one is agnostic and the other one isn't. | ||
*/ | ||
export function sameAccount(account1: string | undefined, account2: string | undefined): boolean | undefined { | ||
// Both agnostic in 99% of cases means they will be deployed to the same environment, |
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.
Mmmmmokay...
// so treat as the same. | ||
if (Token.isUnresolved(account1) && Token.isUnresolved(account2)) { return true; } | ||
|
||
// One agnostic and the other one not means "shug". |
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.
// One agnostic and the other one not means "shug". | |
// One agnostic and the other one not means "shrug". |
@@ -43,4 +43,8 @@ export class UnknownPrincipal implements IPrincipal { | |||
this.resource.node.addWarning(`Add statement to this resource's role: ${repr}`); | |||
return true; // Pretend we did the work. The human will do it for us, eventually. | |||
} | |||
|
|||
public sameAccount(_scope: IConstruct): boolean | undefined { |
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.
Maybe return an enum instead of using undefined
as a tri-state so this will be easier to reason about and avoid mistakes like (!sameAccount())
).
* | ||
* Returns `undefined` if it is unknown whether the accounts are the same. | ||
*/ | ||
sameAccount(scope: cdk.IConstruct): boolean | undefined; |
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 feel that this API should accept a
cdk.Stack
and not acdk.IConstruct
. It will make this API much clearer. - Would it make sense to make this
_internal
? It's used only inside this module and TBH it feels like it's polluting the interface.
@rix0rrr sorry this took so long, but this was a tricky one! I've opened #8280 with my proposed solution. It includes your tests from this PR. I would appreciate if you could take a look, and let me know what you think! |
Trust policies should be added to resources when trying to grant
permissions to a principal in a different account. Typically this is a
Service Principal, but it could also be an imported Role from a
different account (and the same should not be done for a Role in the
same account).
Our previous API (
addToPolicy
returningtrue|false
) was designed forin-account Roles and cross-account ServicePrincipals, but was not rich
enough to make the distinction that Roles could be cross-account.
Add a function to
IPrincipal
to ask it whether it is in the same or adifferent account from a given scope, and have
Grant
respect that.Also in this commit: a bugfix for
ImmutableRole
to make it workcorrectly with
Grant
Also in this commit:
parseArn()
used to behave the same forparseArn(TOKEN)
andparseArn("arn:{TOKEN}:svc:1234:...")
.Make it (opportunistically) do the right thing in the latter case,
parsing out the concrete components and the TOKEN components
separately. This makes it possible to parse out the concrete
account number from an ARN that contains tokenized parts we don't
care about.
Fixes #5740, #2807.
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license