aws / aws-sdk-go-v2

AWS SDK for the Go programming language.
https://aws.github.io/aws-sdk-go-v2/docs/
Apache License 2.0
2.68k stars 651 forks source link

Add LoadOptions hook to configure STS/SSO credential clients #2686

Open gdavison opened 5 months ago

gdavison commented 5 months ago

Acknowledgements

Describe the bug

When

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

RanVaknin commented 5 months ago

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~

github-actions[bot] commented 5 months ago

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.

tmccombs commented 5 months ago

Please keep open

RanVaknin commented 5 months ago

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~

gdavison commented 5 months ago

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

tmccombs commented 5 months ago

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.

gdavison commented 5 months ago

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 commented 5 months ago

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 🙂

lucix-aws commented 4 months ago

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
})
gdavison commented 4 months ago

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