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: make imported resources account/region-aware #8280

Merged
merged 1 commit into from
Aug 19, 2020

Conversation

skinny85
Copy link
Contributor

@skinny85 skinny85 commented May 29, 2020

Add the account and region properties to the IResource interface and Resource class.
By default, these are equal to the account and region of the Stack the resource belongs to;
however, they can be set to different values in resources that are imported.

Use those new properties in two places:

  • In CodePipeline, to determine whether a given action is cross-account
    (with support for specifying the account and region in S3's BucketAttributes,
    as a first use case).
  • IAM's addToPrincipalOrResource(), to correctly know when to modify the receiver's resource policy.
    This is aided by adding an optional principalAccount property to IPrincipal,
    as a way to compare to the account present in the passed IResource instance.

Fixes #2807
Fixes #5740
Fixes #7012


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

@skinny85 skinny85 requested review from rix0rrr and eladb May 29, 2020 22:52
@mergify mergify bot added the contribution/core This is a PR that came from AWS. label May 29, 2020
@skinny85 skinny85 force-pushed the feat/region-account-in-resources branch from ce52185 to 40f16e9 Compare June 1, 2020 18:24
Copy link
Contributor

@eladb eladb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initial round.

There are quite a few unrelated changes such as spacing and stuff. Ideally those should not be included.

@rix0rrr please review as well.

packages/@aws-cdk/aws-iam/lib/grant.ts Outdated Show resolved Hide resolved
const secondIsUnresolved = cdk.Token.isUnresolved(account2);

if (firstIsUnresolved && secondIsUnresolved) {
return AccountCompare.SAME;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes that if account is unresolved it will definitely resolve to the pseudo reference AWS::Account but it could also be a lazy value for example.

Maybe we should be a bit smarter about this and allow you to “peek” into the token and only consider them “same” if they both resolve to the pseudo.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're correct, but do you have an idea how we can do this "peeking"?

packages/@aws-cdk/aws-route53/test/test.util.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/core/lib/arn.ts Outdated Show resolved Hide resolved
@@ -135,12 +135,16 @@ export class Arn {
* components of the ARN.
*/
public static parse(arn: string, sepIfToken: string = '/', hasName: boolean = true): ArnComponents {
if (Token.isUnresolved(arn)) {
const components = arn.split(':') as Array<string | undefined>;
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 only do that if the string starts with arn:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? It's not likely to throw.

* The AWS account ID that this resource belongs to.
* For resources that are created and managed by the CDK
* (generally, those created by creating new class instances like Role, Bucket, etc.)
* this is always the same as the account of the stack they belong to;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it’s not “always the same”. Phrasing is a bit awkward

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the created ones, it is always the same.

But of course, if you have a better wording, I'm all ears!

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.

I like it! Thanks for improving my half-baked work :)

packages/@aws-cdk/aws-certificatemanager/test/test.util.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-codepipeline/lib/pipeline.ts Outdated Show resolved Hide resolved
// if the resource is from a different stack in another region but the same account,
// use that stack as home for the cross-region support resources
if (pipelineStack.account === actionResourceStack.account) {
if (pipelineStack.account === actionResourceStack.account &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here.

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 believe Aws.REGION does always resolve to the same object, so I think we're good here. Let me know if you disagree.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't necessarily disagree, I'm just scared.

packages/@aws-cdk/aws-iam/lib/role.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-s3/lib/bucket.ts Show resolved Hide resolved
Comment on lines -613 to -654
const crossAccountAccess = this.isGranteeFromAnotherAccount(grantee);
let ret: iam.Grant;
if (crossAccountAccess) {
// if the access is cross-account, we need to trust the accessing principal in the bucket's policy
ret = iam.Grant.addToPrincipalAndResource({
grantee,
actions: bucketActions,
resourceArns: resources,
resource: this,
});
} else {
// if not, we don't need to modify the resource policy if the grantee is an identity principal
ret = iam.Grant.addToPrincipalOrResource({
grantee,
actions: bucketActions,
resourceArns: resources,
resource: this,
});
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woohoo!! \o/

@@ -135,12 +135,16 @@ export class Arn {
* components of the ARN.
*/
public static parse(arn: string, sepIfToken: string = '/', hasName: boolean = true): ArnComponents {
if (Token.isUnresolved(arn)) {
const components = arn.split(':') as Array<string | undefined>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why? It's not likely to throw.

packages/@aws-cdk/core/lib/cfn-pseudo.ts Show resolved Hide resolved
@rix0rrr
Copy link
Contributor

rix0rrr commented Jun 5, 2020

As discussed face-to-face, I think .account and .region members on IResource take up valuable autocomplete real estate for very rare use cases (<1% of CDK users would need this, and I'm guessing even <10% of construct library authors).

Maybe move to a static method instead?

(The comparison logic that takes unresolveds into account can go there as well)

@eladb
Copy link
Contributor

eladb commented Jun 7, 2020

As discussed face-to-face, I think .account and .region members on IResource take up valuable autocomplete real estate for very rare use cases (<1% of CDK users would need this, and I'm guessing even <10% of construct library authors).

Maybe move to a static method instead?

(The comparison logic that takes unresolveds into account can go there as well)

I personally don’t have much of an issue with adding those to resource directly. Those are a natural attribute of a resource so I don’t think a “mixin” is required. Let’s keep things simple and discoverable.

@rix0rrr
Copy link
Contributor

rix0rrr commented Jun 8, 2020

It's not a "mixin", and why should we optimize for the 0.01% use case and pessimize the rest?

I'm arguing it doesn't need to be discoverable, because hardly anyone will use it.

@eladb
Copy link
Contributor

eladb commented Jun 8, 2020

It's not a "mixin", and why should we optimize for the 0.01% use case and pessimize the rest?

I'm arguing it doesn't need to be discoverable, because hardly anyone will use it.

You can make a similar argument for many APIs we expose on many classes. I disagree that this pessimizes the common use case. It adds an api to Resource which makes sense in the context of every AWS resource. A good way to “measure” this is to ask whether these properties might conflict or contradict with something that comes from a subclass. I think the answer is no. I can’t think of an AWS resource that will have other meanings for account or region because those concepts come from the same domain (unlike, for example, the APIs we have under ConstructNode, which come from a different domain).

@skinny85
Copy link
Contributor Author

skinny85 commented Jun 8, 2020

@eladb what is your opinion on account and region in (I)Resource returning an instance of a new class, instead of just simple Strings, that has some logic for intelligent comparisons of the values, including tokens, AWS::Region/AWS::AccountId, etc.? So we don't have to repeat the logic that's currently in IAM, in the accountsAreSameOrUnresolved function?

@eladb
Copy link
Contributor

eladb commented Jun 9, 2020

@eladb what is your opinion on account and region in (I)Resource returning an instance of a new class, instead of just simple Strings, that has some logic for intelligent comparisons of the values, including tokens, AWS::Region/AWS::AccountId, etc.? So we don't have to repeat the logic that's currently in IAM, in the accountsAreSameOrUnresolved function?

I like it. Then maybe we should encapsulate those into Env and also expose a similar object from Stack. I would also add a partition there.

@skinny85 skinny85 added the pr-linter/exempt-readme The PR linter will not require README changes label Jun 29, 2020
@skinny85 skinny85 force-pushed the feat/region-account-in-resources branch from 40f16e9 to 226111d Compare July 1, 2020 01:53
@skinny85
Copy link
Contributor Author

skinny85 commented Jul 1, 2020

@eladb @rix0rrr I've included your comments, and added the classes we talked about in #8280 (comment) , so this is ready for another round of reviews!

@skinny85 skinny85 requested review from rix0rrr and eladb July 1, 2020 01:54
@skinny85 skinny85 removed the pr-linter/exempt-readme The PR linter will not require README changes label Jul 3, 2020
packages/@aws-cdk/core/lib/resource.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/core/lib/resource.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/core/lib/environment-component.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-iam/lib/grant.ts Show resolved Hide resolved
// if the resource is from a different stack in another region but the same account,
// use that stack as home for the cross-region support resources
if (pipelineStack.account === actionResourceStack.account) {
if (pipelineStack.account === actionResourceStack.account &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't necessarily disagree, I'm just scared.

@skinny85 skinny85 requested a review from rix0rrr August 8, 2020 03:18
@skinny85 skinny85 force-pushed the feat/region-account-in-resources branch from 226111d to e44dc18 Compare August 8, 2020 03:18
@mergify
Copy link
Contributor

mergify bot commented Aug 8, 2020

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

@skinny85
Copy link
Contributor Author

skinny85 commented Aug 8, 2020

@rix0rrr included your comments. This is ready for another round of reviews!

@skinny85 skinny85 added pr-linter/exempt-readme The PR linter will not require README changes and removed pr-linter/exempt-readme The PR linter will not require README changes labels Aug 8, 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.

I think we might have a different bug.

This concerns me: #8280 (comment)

@skinny85 skinny85 requested a review from rix0rrr August 13, 2020 00:56
@skinny85
Copy link
Contributor Author

@rix0rrr I need your help with @aws-cdk/pipelines changes. I think the problem is here:

function cfnExpressionFromManifestString(s: string) {
// This implementation relies on the fact that the manifest placeholders are
// '${AWS::Partition}' etc., and so are the same values as those that are
// trivially substituable using a `Fn.sub`.
return Fn.sub(s);
. Because you're wrapping an entire ARN we use to import the role with into an Fn::Sub, our logic cannot take out the account from the ARN (as it's a Token), and thus see that they're in fact in the same account. Hence, there is a diff in the integ tests (we modify the resource policy of the artifact bucket).

Would appreciate any ideas on how to tackle that.

@skinny85
Copy link
Contributor Author

Weird failure:

@aws-cdk/aws-apigateway: error: [awslint:attribute-tag:@aws-cdk/aws-apigateway.ProxyResource.resourceEnv] attribute properties must have an "@attribute" doctag on:  @aws-cdk/core.IResource.resourceEnv
@aws-cdk/aws-apigateway: error: [awslint:attribute-tag:@aws-cdk/aws-apigateway.Resource.resourceEnv] attribute properties must have an "@attribute" doctag on:  @aws-cdk/core.IResource.resourceEnv

I didn't even touch anything in api-gateway...

@skinny85
Copy link
Contributor Author

Oh, I know what this is. The name resource was picked up the linter, as it's the name of the API Gateway Resource resource.

@rix0rrr
Copy link
Contributor

rix0rrr commented Aug 13, 2020

Instead of wrapping in Fn.sub the only solution then is to use cxapi.EnvironmentalPlaceholders.sub (or whatever it's called) to replace the placeholder strings with CDK tokens, so that we end up with a :-delimited string.

@skinny85 skinny85 force-pushed the feat/region-account-in-resources branch 3 times, most recently from cd20abb to 4846304 Compare August 14, 2020 04:22
@@ -55,7 +55,9 @@ class CdkpipelinesDemoPipelineStack extends Stack {

// This is where we add the application stages
// ...
const stage = pipeline.addApplicationStage(new MyStage(this, 'PreProd'));
const stage = pipeline.addApplicationStage(new MyStage(this, 'PreProd', {
env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Why did this work in the first place?

@rix0rrr rix0rrr changed the title feat: add account and region to (I)Resource feat: make imported resources account/region-aware Aug 14, 2020
@skinny85 skinny85 force-pushed the feat/region-account-in-resources branch from 4846304 to b876163 Compare August 14, 2020 18:04
@skinny85
Copy link
Contributor Author

@eladb this is ready to be merged. Let me know if you have any comments on the current state of the code!

@skinny85 skinny85 mentioned this pull request Aug 17, 2020
2 tasks
packages/@aws-cdk/aws-codepipeline-actions/README.md Outdated Show resolved Hide resolved
packages/@aws-cdk/aws-ec2/lib/bastion-host.ts Show resolved Hide resolved
packages/@aws-cdk/core/lib/resource.ts Outdated Show resolved Hide resolved
packages/@aws-cdk/core/lib/resource.ts Outdated Show resolved Hide resolved
* An enum-like class that represents the result of comparing two Tokens.
* The return type of {@link Token.compareStrings}.
*/
export class TokenComparison {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this just a simple enum?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it allows us to add helper methods to it later, like isEqualOrUnresolved() that were here before, which I still think is the correct way to model this.

If we make this an enum, we will lose that flexibility.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels like "backwards-future-compatibility". Not sure I understand the use case of isEqualOfUnresolved(), can you elaborate or show an example?

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 mean in code like this:

        const thisAndPolicyAccountComparison = Token.compareStrings(this.resourceEnv.account, policy.resourceEnv.account);
        const equalOrAnyUnresolved = thisAndPolicyAccountComparison === TokenComparison.SAME ||
          thisAndPolicyAccountComparison === TokenComparison.BOTH_UNRESOLVED ||
          thisAndPolicyAccountComparison === TokenComparison.ONE_UNRESOLVED;
        if (equalOrAnyUnresolved) {
          // ...

I think a much more natural way to write that is:

        if (Token.compareStrings(this.resourceEnv.account, policy.resourceEnv.account)
            .isEqualOrAnyUnresolved()) {
          // ...

I don't want us to block ourselves from achieving the latter by using an enum.

Copy link
Contributor

@eladb eladb Aug 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feels a bit non idiomatic (fluent and such).

I would have done this:

Token.isEqualOrAnyUnresolved(this.resourceEnv.account, policy.resourceEnv.account)

But if you feel strongly, I am fine...

@skinny85 skinny85 force-pushed the feat/region-account-in-resources branch from b876163 to b95828f Compare August 19, 2020 21:03
@skinny85 skinny85 removed the pr/do-not-merge This PR should not be merged at this time. label Aug 19, 2020
@skinny85 skinny85 force-pushed the feat/region-account-in-resources branch from b95828f to 041b808 Compare August 19, 2020 21:21
@aws-cdk-automation
Copy link
Collaborator

AWS CodeBuild CI Report

  • CodeBuild project: AutoBuildProject6AEA49D1-qxepHUsryhcu
  • Commit ID: 041b808
  • 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 Aug 19, 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 d6278b3 into aws:master Aug 19, 2020
@skinny85 skinny85 deleted the feat/region-account-in-resources branch August 19, 2020 21:45
mergify bot pushed a commit that referenced this pull request Aug 24, 2020
#8280 enabled imported resources to be account & region aware.
However, while this set the region on the object itself, it didn't adjust the
various region-aware properties of imported buckets (e.g., regional domain
names). This change makes the regional properties of the imported bucket use the
correct region.

fixes #9556

----

*By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
rix0rrr added a commit that referenced this pull request Sep 7, 2020
In #8280 we made a resource's account/region distinct
from the stack in which the construct was defined, to account for
accounts and regions from imported resources.

The pipelines module used to define imported roles in a separate
in-memory Stack so that the old, broken "cross-environment" logic
would do the right thing. That crutch was removed as part of #8280.

The new logic hasn't been carried through everywhere though. For
example, the logic in the grants of KMS keys had not been updated
to match, leading to cross-account/cross-region deployments
being broken (as reported in #10166) because the cross-region support
stack's KMS key had the wrong permissions.

In fact, it switched from:

```
{
    "Action": [ "kms:Decrypt", "kms:DescribeKey" ],
    "Principal": {
        "AWS": {
            "Fn::Sub": "arn:${AWS::Partition}:iam::561462023695:role/cdk-hnb659fds-deploy-role-561462023695-us-east-2"
        }
    },
    "Resource": "*"
}
```

to

```
{
    "Action": [ "kms:Decrypt", "kms:DescribeKey" ],
    "Principal": {
        "AWS": {
            "Fn::Join": [ "", [
                "arn:", { "Ref": "AWS::Partition" }, ":iam::355421412380:root"
            ] ]
        }
    },
    "Resource": "*"
}
```

Ignoring the switch from `Fn::Sub` to `Fn::Join`, it switched from the
`deploy-role` in a DIFFERENT account to the root principal of the SAME
account.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contribution/core This is a PR that came from AWS.
Projects
None yet
4 participants