Skip to content

allow using assumeRoleChain with IAM resources - #451

Merged
hasheddan merged 1 commit into
crossplane-contrib:mainfrom
ctkeyser:assumeroleregion
Feb 1, 2023
Merged

allow using assumeRoleChain with IAM resources#451
hasheddan merged 1 commit into
crossplane-contrib:mainfrom
ctkeyser:assumeroleregion

Conversation

@ctkeyser

@ctkeyser ctkeyser commented Jan 17, 2023

Copy link
Copy Markdown
Contributor

Description of your changes

Fixes #215

currently assumeRoleChain doesn't work for IAM resources because IAM resources don't require a region. the error is

cannot get terraform setup: failed to retrieve aws credentials from aws config: failed to refresh cached credentials, operation error STS: AssumeRole, failed to resolve service endpoint, an AWS region is required, but was not found

this change adds a fallback to "aws-global" region for STS requests in cases where no region is set in the managed resource

I have:

  • Run make reviewable test to ensure this PR is ready for review.

How has this code been tested

I built the provider w/ the fix, installed it in my environment, and confirmed I no longer get the 'region is required' error

@Upbound-CLA

Upbound-CLA commented Jan 17, 2023

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@ulucinar
ulucinar self-requested a review January 23, 2023 21:57
Comment thread apis/v1beta1/types.go Outdated

// AssumeRoleSTSRegion defines the region used for STS requests. If not defined,
// the managed resource region will be used if available
AssumeRoleSTSRegion *string `json:"assumeRoleSTSRegion,omitempty"`

@ulucinar ulucinar Jan 24, 2023

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

An alternative to consider would be to add the the STS region option to the v1beta1.AssumeRoleOptions type. Then, the ProviderConfig would look like:

...
spec:
  assumeRoleChain:
  - stsRegion: us-west-1
    roleARN: <role ARN 1>
  - stsRegion: us-west-1
    roleARN: <role ARN 2>
  credentials:
    secretRef:
      key: creds
      name: aws-creds
      namespace: upbound-system
    source: Secret

However, you'd need to configure an STS region for each role in the chain if you are provisioning a resource without a region (e.g., IAM role) and not sure if there's utility in configuring different STS regions for different roles in the chain. Of course, if needed, we could have both levels of API exposed, e.g., a spec.assumeRoleSTSRegion and spec.assumeRoleChain[].stsRegion (which we could add later if needed). So, I think the proposed API is fine. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

my first idea was also to put it in v1beta1.AssumeRoleOptions, but then I moved it because I didn't think there would be a need to chain assume role requests across different regions. I definitely would have rather put it inside an assumeRole config, but the current structure doesn't really support that. so I think this is probably the best option

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

As a side note for documentation purposes, we had also previously discussed defaulting to a region specified in the ProviderConfig if a managed resource does not specify an optional spec.forProvider.region. The discussion's context was not this PR's target issue (but rather the UX around provider-aws MR APIs). But looks like it's also related to the current discussion.

Comment thread apis/v1beta1/types.go Outdated

// AssumeRoleSTSRegion defines the region used for STS requests. If not defined,
// the managed resource region will be used if available
AssumeRoleSTSRegion *string `json:"assumeRoleSTSRegion,omitempty"`

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: We may give an example of the new API under examples/providerconfig/, e.g., in examples/providerconfig/user-creds-with-assume-role.yaml or in a new example file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah definitely

@ulucinar

Copy link
Copy Markdown
Collaborator

Hi @ctkeyser,
Thank you for the proposal. I think it looks fine but left some review comments so that we discuss some alternatives. Also left a comment here regarding the underlying issue.

@ulucinar

Copy link
Copy Markdown
Collaborator

/test-examples="examples/ec2/vpc.yaml"

@ctkeyser

Copy link
Copy Markdown
Contributor Author

thank you for the review @ulucinar. let me know if you have more thoughts on this

@ulucinar

Copy link
Copy Markdown
Collaborator

/test-examples="examples/ec2/vpc.yaml"

@ulucinar ulucinar left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you @ctkeyser for fixing the issue with provisioning IAM resources when using STS role chaining, lgtm.

Comment on lines +267 to +271
regionOpt := func(o *sts.Options) {
if pcs.AssumeRoleSTSRegion != nil {
o.Region = *pcs.AssumeRoleSTSRegion
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@ctkeyser @ulucinar I wonder if it would make sense to fall back to the AWS global partition here if an STS region is not defined? The AWS SDK itself will do so if it does not recognize the region that is passed, but will error if no region is passed. Thoughts?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Hi @hasheddan,
Makes sense to me.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

that sounds good to me, but I'm not sure what that change looks like. I tried changing region to global and aws-global and I ended up getting errors saying dns doesn't resolve for sts.aws-global.amazonaws.com or sts.global.amazonaws.com. dns does resolve for sts.us-west-1.amazonaws.com though

I also don't see STS in this list of global endpoints https://docs.aws.amazon.com/general/latest/gr/rande.html#global-endpoints

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok I tested just falling back to aws-global if no region set, and that does work in my environment:

--- a/internal/clients/provider_config.go
+++ b/internal/clients/provider_config.go
@@ -265,8 +265,8 @@ func UseProviderSecret(ctx context.Context, data []byte, profile, region string)
 func GetRoleChainConfig(ctx context.Context, pcs *v1beta1.ProviderConfigSpec, cfg *aws.Config) (*aws.Config, error) {
        pCfg := cfg
        regionOpt := func(o *sts.Options) {
-               if pcs.AssumeRoleSTSRegion != nil {
-                       o.Region = *pcs.AssumeRoleSTSRegion
+               if cfg.Region == "" {
+                       o.Region = GlobalRegion
                }
        }
        for _, aro := range pcs.AssumeRoleChain {

I was able to assume a role and create an IAM resource with that
I also tried changing getRegion to return GlobalRegion if no region was found in the managed resource, but that didn't work. got the error:

managed/iam.aws.upbound.io/v1beta1, kind=role  cannot run refresh: refresh failed: Invalid AWS Region: aws-global

do you guys think this is good enough and we don't need the providerconfig option? so basically any region-less managed resource will use aws-global for STS. that seems simple enough to me

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This seems reasonable to me -- we can always expand the API of ProviderConfig if needed. So in summary:

  • If an assume role chain is being used and the MR specifies a region, we will use that region endpoint for the STS operations.
  • If chain is being used and the MR does not specify a region, we use the global STS endpoint.

I think potentially using a separate region's STS endpoint could be desired by a user, but let's wait until it is requested since it involves API expansion. Are you aligned @ulucinar?

@ulucinar ulucinar Feb 1, 2023

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I don't know much about the use cases here. But one hypothetical scenario I may imagine is what happens if networking access to the host sts.amazonaws.com is blocked (due to law, temporary issue, etc.). But my understanding is that it (role chaining) was not working up to now for region-less resources and if we make a backward-compatible change (i.e., only revert back to sts.amazonaws.com for role chaining if the MR does not specify a region), we should not be breaking any existing (already working) scenarios and would be solving the problem for at least a subset of the users (of provider-aws).

So, I think we are good to go. But to be on the safe side, even if, currently, we will not allow configuration of the STS region for region-less resources, let's scope the change only for region-less resources (so that the change is backward-compatible as discussed above).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ok I pushed the change. it only sets the region to aws-global for STS requests when no region is set, so it should only occur in situations where it currently doesn't work at all

@ulucinar

ulucinar commented Feb 1, 2023

Copy link
Copy Markdown
Collaborator

/test-examples="examples/ec2/vpc.yaml"

@hasheddan hasheddan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One comment but non-blocking -- thanks @ctkeyser! Might also be worth squashing commit history here 👍🏻

Comment on lines +267 to +271
regionOpt := func(o *sts.Options) {
if cfg.Region == "" {
o.Region = GlobalRegion
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

might be nice to pull this out into something like:

NewRegionFallbackOpt(region string) func(*sts.Options) {
  if region != "" {
    return func(*sts.Options) {}
  }
  return func(o *sts.Options) {
    o.Region = GlobalRegion
  }
}

But I am fine with deferring doing so for now.

@ctkeyser

ctkeyser commented Feb 1, 2023

Copy link
Copy Markdown
Contributor Author

@hasheddan should I squash and force-push or is squash+merge preferred?

@hasheddan

Copy link
Copy Markdown
Member

@ctkeyser squash and force push would be great if you don't mind!

@hasheddan

Copy link
Copy Markdown
Member

I'll go ahead and merge here since @ulucinar previously approved and indicated alignment on the updated direction 👍🏻

@hasheddan
hasheddan merged commit 4d3ead6 into crossplane-contrib:main Feb 1, 2023
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: Role CRD is not working with ProviderConfig with assumeRoleChain

4 participants