aws-sdk-go-v2 icon indicating copy to clipboard operation
aws-sdk-go-v2 copied to clipboard

Add credential provider for role chaining

Open jmcarp opened this issue 3 years ago • 4 comments

Describe the feature

Add a CredentialProvider that implements role chaining: generating ephemeral credentials via sts using a series of iam roles.

Use Case

From the docs:

Role chaining is when you use a role to assume a second role through the AWS CLI or API. For example, RoleA has permission to assume RoleB. You can enable User1 to assume RoleA by using their long-term user credentials in the AssumeRole API operation. This returns RoleA short-term credentials. With role chaining, you can use RoleA's short-term credentials to enable User1 to assume RoleB.

Proposed Solution

The implementation might be a new CredentialProvider, e.g. AssumeChainedRoleProvider, that repeatedly invokes AssumeRoleProvider over a series of iam roles.

Other Information

No response

Acknowledgements

  • [X] I may be able to implement this feature request
  • [ ] This feature might incur a breaking change

AWS Go SDK V2 Module Versions Used

latest

Go version used

latest

jmcarp avatar May 23 '22 03:05 jmcarp

Hi @jmcarp ,

Thanks for the feature request. The Go SDK already supports credential role chaining implicitly. You need to setup your .aws/config file the same way suggested in the docs you have linked.

Are you asking about an explicit credential provider that implements this? Or were you unaware that we implicitly support this?

Thank you! Ran~

RanVaknin avatar Mar 09 '23 22:03 RanVaknin

I'm one of the :+1:s .

We run our services stateless-ly - without a config - and we chain roles programattically. For us, a service has role from web identity, we use that to assume a different role, and keep assuming until we're at the correct role. We have some abstractions to help with that but it's a sufficiently common use-case that I think it could be valuable to have the SDK provide it out-of-the-box.

I'm not sure what that looks like exactly. I'm not particularly keen on our abstraction or I would share it.

JTarasovic avatar Mar 10 '23 20:03 JTarasovic

Hi @JTarasovic ,

I think an explicit role chain provider is a reasonable request but in the spirit of transparency I'll say that it's likely that this wont get implemented anytime soon. The team is prioritizing bug fixes, and feature parity at the moment.

I will obviously keep this open because it had a good amount of engagement, and when we get additional bandwidth this will get addressed.

Thanks again, Ran~

RanVaknin avatar Mar 13 '23 19:03 RanVaknin

This could probably be cleaned up a bit if it were a part of the SDK (e.g., have direct access to the provider options instead of having to rely on the NewAssumeRoleProvider function), but it solves our use cases.

type Role struct {
	Arn     string
	Options []func(*stscreds.AssumeRoleOptions)
}

func NewAssumeRoleChainProvider(cfg aws.Config, roles ...*Role) (*stscreds.AssumeRoleProvider, error) {
	var chain []*stscreds.AssumeRoleProvider

	for idx, r := range roles {
		if idx == 0 {
			p := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), r.Arn, r.Options...)
			chain = append(chain, p)
			continue
		}

		p := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg, func(o *sts.Options) {
			o.Credentials = aws.NewCredentialsCache(chain[idx-1])
		}), r.Arn, r.Options...)

		chain = append(chain, p)
	}

	return chain[len(chain)-1], nil
}

Example usage:

func main() {
	// get background context
	ctx := context.Background()

	// load aws config
	cfg, err := config.LoadDefaultConfig(ctx, func(o *config.LoadOptions) error {
		o.Region = "us-east-1"
		return nil
	})
	if err != nil {
		log.Fatalln(err.Error())
	}

	provider, err := NewAssumeRoleChainProvider(cfg,
		&Role{
			Arn: "arn:aws:iam::123456789012:role/one",
		},
		&Role{
			Arn: "arn:aws:iam::123456789012:role/two",
			Options: []func(*stscreds.AssumeRoleOptions){
				func(o *stscreds.AssumeRoleOptions) {
					o.ExternalID = aws.String("ext-id-1234")
				},
			},
		},
		&Role{
			Arn: "arn:aws:iam::123456789012:role/three",
		},
	)

	if err != nil {
		log.Fatalln(err)
	}

        // attach chained credential provider to config
	cfg.Credentials = provider

	// do something only three can do...
	client := ec2.NewFromConfig(cfg)

	resp, err := client.DescribeInstances(ctx, &ec2.DescribeInstancesInput{})
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println(resp.Reservations)
}

scarbeau avatar Oct 27 '23 01:10 scarbeau