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

Add LoadOptions hook to configure STS/SSO credential clients

Open gdavison opened this issue 1 year ago • 10 comments

Acknowledgements

  • [X] I have searched (https://github.com/aws/aws-sdk/issues?q=is%3Aissue) for past instances of this issue
  • [X] I have verified all of my SDK modules are up-to-date (you can perform a bulk update with go get -u github.com/aws/aws-sdk-go-v2/...)

Describe the bug

When

  • using a named profile in the shared config file which assumes a role (i.e. calls STS during credential resolution in config.LoadDefaultConfig),
  • setting the global UseFIPSEndpoint to true, and
  • using a region where STS does not have a FIPS endpoint (e.g. ca-central-1)

resolving credentials fails with the error

Retrieving credentials: failed to refresh cached credentials, operation error STS: AssumeRole, https response error StatusCode: 0, RequestID: , request send failed, Post "https://sts-fips.ca-central-1.amazonaws.com/": dial tcp: lookup sts-fips.ca-central-1.amazonaws.com: no such host

When overriding the endpoint, as suggested in the AWS CLI documentation, it fails with the error

Retrieving credentials: failed to refresh cached credentials, operation error STS: AssumeRole, failed to resolve service endpoint, endpoint rule error, Invalid Configuration: FIPS and custom endpoint are not supported

Expected Behavior

Based on previous discussion (https://github.com/aws/aws-sdk-go-v2/issues/2336#issuecomment-1781797308), the first failure is expected.

When providing a custom endpoint, the UseFIPSEndpoint (and UseDualStackEndpoint) flags should be cleared so that the endpoint can be used.

Current Behavior

Because config.LoadDefaultConfig creates its own STS client internally, there is no way to clear the UseFIPSEndpoint setting when also using a custom endpoint.

Using a global aws.EndpointResolverWithOptions does work to set the endpoint without the Invalid Configuration: FIPS and custom endpoint are not supported error, but aws.EndpointResolverWithOptions is now deprecated. It also doesn't directly support setting endpoints via environment variables or the shared configuration file.

Note that the AWS CLI also fails in this situation:

AWS_PROFILE="<profile-that-assumes-role>" AWS_REGION=ca-central-1 AWS_USE_FIPS_ENDPOINT=true aws s3api list-buckets --endpoint-url="https://s3.ca-central-1.amazonaws.com/"

fails with

Could not connect to the endpoint URL: "https://sts-fips.ca-central-1.amazonaws.com/"

Reproduction Steps

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/aws/aws-sdk-go-v2/config"
)

func main() {
	ctx := context.Background()

	// ca-central-1 does not support FIPS for STS
	os.Setenv("AWS_REGION", "ca-central-1")
	os.Setenv("AWS_USE_FIPS_ENDPOINT", "true")

	os.Setenv("AWS_ENDPOINT_URL_STS", "https://sts.ca-central-1.amazonaws.com/")

	os.Setenv("AWS_PROFILE", "assume-role")

	cfg, err := config.LoadDefaultConfig(ctx)
	if err != nil {
		fmt.Printf("Loading configuration: %s\n", err)
		os.Exit(1)
	}

	_, err = cfg.Credentials.Retrieve(ctx)
	if err != nil {
		fmt.Printf("Retrieving credentials: %s\n", err)
		os.Exit(1)
	}

	fmt.Println("Success.")
}

The config file

[source]
aws_access_key_id     = ****
aws_secret_access_key = ****

[assume-role]
source_profile = source
role_arn       = arn:aws:iam::123456789012:role/OrganizationAccountAccessRole

Possible Solution

No response

Additional Information/Context

No response

AWS Go SDK V2 Module Versions Used

require github.com/aws/aws-sdk-go-v2/config v1.27.19

require ( github.com/aws/aws-sdk-go-v2 v1.28.0 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.17.19 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.6 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.10 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.10 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.2 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.12 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.20.12 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.24.6 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.28.13 // indirect github.com/aws/smithy-go v1.20.2 // indirect )

Compiler and Version used

go version go1.22.1 darwin/arm64

Operating System and version

macOS 13.6.7

gdavison avatar Jun 17 '24 23:06 gdavison

Hi @gdavison ,

Thanks for reaching out. You can pass in WithAssumeRoleCredentialOptions to your config, create an new sts client internally and assign it a new config options that disable fips. Something like this:

package main

import (
	"context"
	"fmt"
	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
	"github.com/aws/aws-sdk-go-v2/service/sts"
	"os"
)

func main() {
	ctx := context.Background()
	os.Setenv("AWS_USE_FIPS_ENDPOINT", "true")
	os.Setenv("AWS_ENDPOINT_URL_STS", "https://sts.ca-central-1.amazonaws.com/")
	os.Setenv("AWS_REGION", "ca-central-1")
	os.Setenv("AWS_PROFILE", "assume-role")

	cfg, err := config.LoadDefaultConfig(ctx,
		config.WithRegion("ca-central-1"),
		config.WithAssumeRoleCredentialOptions(func(o *stscreds.AssumeRoleOptions) {
			secondCfg, err := config.LoadDefaultConfig(
				context.TODO(),
				config.WithRegion("ca-central-1"),
				config.WithClientLogMode(aws.LogRequestWithBody),
				config.WithUseFIPSEndpoint(aws.FIPSEndpointStateDisabled),
			)
			if err != nil {
				panic(err)
			}
			o.Client = sts.NewFromConfig(secondCfg)
		}),
	)
	if err != nil {
		panic(err)
	}

	_, err = cfg.Credentials.Retrieve(ctx)
	if err != nil {
		panic(err)
	}

	fmt.Println("Success.")
}

let me know if this helps.

Thanks, Ran~

RanVaknin avatar Jun 20 '24 21:06 RanVaknin

This issue has not received a response in 1 week. If you want to keep this issue open, please just leave a comment below and auto-close will be canceled.

github-actions[bot] avatar Jul 01 '24 00:07 github-actions[bot]

Please keep open

tmccombs avatar Jul 01 '24 04:07 tmccombs

Hi @tmccombs ,

Can you please elaborate on why this needs to be kept open? Did the workaround I provided not work for you?

Thanks, Ran~

RanVaknin avatar Jul 01 '24 18:07 RanVaknin

Hi @RanVaknin. I haven't had a chance to check this out.

I assume that @tmccombs has asked to keep this open because this issue relates to an issue that he submitted in https://github.com/hashicorp/terraform-provider-aws

gdavison avatar Jul 03 '24 02:07 gdavison

Ugh, I wrote a response, and it appears to have been lost somehow.

Anyway, yes, an application could technically work around this, but doing so is rather complex. Consider an application where credentials could come from multiple sources, such as terraform. For this workaround, the application would need to look at the environment variables, and configuration files that the SDK usually looks at, to figure out if it should disable UseFips because an endpoint is specified. And in your example the region is a constant. What if the region comes from the credential source.

Also, in my opinion it would be better for this to be implemented once in the SDK, rather than every application that uses the SDK having to solve it separately, possibly in inconsistent ways.

tmccombs avatar Jul 03 '24 02:07 tmccombs

Hi @RanVaknin. The sample code doesn't work when retrieving credentials from a shared config file, even without the FIPS setting and custom endpoint. The STS client makes two requests to AssumeRole. The first appears to succeed, and assumes the expected role. However, the second tries to assume the role again, even though it has already been assumed

First request:

SDK 2024/07/03 14:36:08 DEBUG Request
POST / HTTP/1.1
Host: sts.ca-central-1.amazonaws.com
User-Agent: m/E aws-sdk-go-v2/1.30.1 os/macos lang/go#1.22.1 md/GOOS#darwin md/GOARCH#arm64 api/sts#1.30.1
Authorization: AWS4-HMAC-SHA256 Credential=<base role>/**** *****

Action=AssumeRole&DurationSeconds=900&RoleArn=arn%3Aaws%3Aiam%3A%3A123456789012%3Arole%2FOrganizationAccountAccessRole&RoleSessionName=SomeSessionName&Version=2011-06-15
SDK 2024/07/03 14:36:08 DEBUG Response
HTTP/1.1 200 OK
Content-Length: 1540
Content-Type: text/xml
Date: Wed, 03 Jul 2024 21:36:08 GMT

<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
  <AssumeRoleResult>
    <AssumedRoleUser>
      <AssumedRoleId>AROAEXAMPLE:SomeSessionName</AssumedRoleId>
      <Arn>arn:aws:sts::123456789012:assumed-role/OrganizationAccountAccessRole/SomeSessionName </Arn>
    </AssumedRoleUser>
    <Credentials>
      <AccessKeyId><</AccessKeyId>
      <SecretAccessKey>EXAMPLEACCESSKEY</SecretAccessKey>
      <SessionToken>*******</SessionToken>
      <Expiration>2024-07-03T21:51:08Z</Expiration>
    </Credentials>
  </AssumeRoleResult>
  <ResponseMetadata>
    <RequestId>****</RequestId>
  </ResponseMetadata>
</AssumeRoleResponse>

The second request, issued immediately after

SDK 2024/07/03 14:36:08 DEBUG Request
POST / HTTP/1.1
Host: sts.ca-central-1.amazonaws.com
User-Agent: m/E aws-sdk-go-v2/1.30.1 os/macos lang/go#1.22.1 md/GOOS#darwin md/GOARCH#arm64 api/sts#1.30.1
Authorization: AWS4-HMAC-SHA256 Credential= EXAMPLEACCESSKEY/**** ****

Action=AssumeRole&DurationSeconds=900&RoleArn=arn%3Aaws%3Aiam%3A%3A123456789012%3Arole%2FOrganizationAccountAccessRole&RoleSessionName=ADifferentSessionName&Version=2011-06-15
SDK 2024/07/03 14:36:08 DEBUG Response
HTTP/1.1 403 Forbidden
Content-Length: 468
Content-Type: text/xml
Date: Wed, 03 Jul 2024 21:36:08 GMT

<ErrorResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
  <Error>
    <Type>Sender</Type>
    <Code>AccessDenied</Code>
    <Message>User: arn:aws:sts::123456789012:assumed-role/OrganizationAccountAccessRole/SomeSessionName is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam:: 123456789012:role/OrganizationAccountAccessRole</Message>
  </Error>
  <RequestId>****</RequestId>
</ErrorResponse>

gdavison avatar Jul 03 '24 21:07 gdavison

Also, to amplify @tmccombs's point above, see https://github.com/hashicorp/terraform-provider-aws/pull/38057 which overrides the EndpointResolverV2 for every service to allow falling back to the non-FIPS endpoint if the FIPS endpoint does not exist. It doesn't handle the Dual Stack case yet.

There would have been more changes, but not all services have an AWS SDK for Go v2 implementation yet 🙂

gdavison avatar Jul 03 '24 21:07 gdavison

The reason you're getting two AssumeRoles with that snippet is because the inner default config also resolves to using STS credentials. - so your outer config tries to assume role, and then the inner config does the same. You could patch over this with more code but I think we'd start overcomplicating the solution.

It seems like we're basically just missing a simple functional option helper e.g. config.WithSTSClientOptions() etc. that would enable you to set this behavior e.g.

cfg, err := config.LoadDefaultConfig(ctx, confg.WithSTSClientOptions(func(o *sts.Options)) {
    o.EndpointOptions.UseFIPSEndpoint = blah
})

lucix-aws avatar Jul 10 '24 17:07 lucix-aws

@lucix-aws, yes, I think that would work

gdavison avatar Jul 17 '24 23:07 gdavison