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.59k stars 626 forks source link

EndpointResolverWIthOptions encoding issue with custom endpoint #2190

Closed Senui closed 1 year ago

Senui commented 1 year ago

Describe the bug

I'm trying to perform an action (e.g. list all objects) on an S3 bucket using a custom S3-compatible endpoint that serves a Openstack SWIFT object store. Using the aws-sdk-go-v2 module I am unable to do so because the HTTP request (retrieved from logs) is not correctly generated from my configuration (an encoded forward slash is prepending to the GET request).

The same configuration is used in the boto3 python package, which works fine.

Expected Behavior

GET /mybucket?list-type=2 HTTP/1.1
Host: myswift.domain.com
...

Current Behavior

SDK 2023/07/14 10:51:29 DEBUG retrying request S3/ListObjectsV2, attempt 3
SDK 2023/07/14 10:51:29 DEBUG Request
GET /%2Fmybucket?list-type=2 HTTP/1.1
Host: myswift.domain.com
User-Agent: aws-sdk-go-v2/1.19.0 os/linux lang/go#1.20.6 md/GOOS#linux md/GOARCH#amd64 api/s3#1.37.0
Accept-Encoding: identity
Amz-Sdk-Invocation-Id: 44cd6e58-1761-4537-8f5e-c2cbad41e055
Amz-Sdk-Request: attempt=3; max=3
Authorization: AWS4-HMAC-SHA256 Credential=<secret>/20230714/NL/s3/aws4_request, SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date, Signature=<signature>
X-Amz-Content-Sha256: <secret>
X-Amz-Date: 20230714T105129Z

exceeded maximum number of attempts, 3, https response error StatusCode: 500, RequestID: tx272dab4ad00241dd9cf6d-0064b128b1, HostID: tx272dab4ad00241dd9cf6d-0064b128b1, api error InternalError: unexpected status code 412

Reproduction Steps

Here is the reproducing code. Note that if I were to remove the forward slash in front of the bucket name, the bucket name would be prepended to the host and would mess up the request completely:

GET /?list-type=2 HTTP/1.1
Host: mybucket.myswift.domain.com
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"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

func main() {
    endpoint := "https://myswift.domain.com"
    region := "NL"
    bucketName := "/mybucket"
    accesskey := "<secret>"
    secretkey := "<secret>"

    // Create a custom endpoint resolver function
    endpointResolver := aws.EndpointResolverFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
        return aws.Endpoint{
            URL:           endpoint,
            SigningRegion: region,
        }, nil
    })

    // Create the AWS configuration
    cfg, err := config.LoadDefaultConfig(context.TODO(),
        config.WithRegion(region),
        config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accesskey, secretkey, "")),
        config.WithEndpointResolverWithOptions(endpointResolver),
        config.WithClientLogMode(aws.LogRetries | aws.LogRequest | aws.LogRequestWithBody),
    )
    if err != nil {
        fmt.Println("Failed to load AWS configuration:", err)
        return
    }

    // Create an S3 client
    client := s3.NewFromConfig(cfg)

    // List objects in a bucket
    resp, err := client.ListObjectsV2(context.TODO(), &s3.ListObjectsV2Input{
        Bucket: &bucketName,
    })
    if err != nil {
        fmt.Println("Failed to list objects:", err)
        return
    }

    fmt.Println("Objects in the bucket:")
    for _, obj := range resp.Contents {
        fmt.Println(*obj.Key)
    }
}

Possible Solution

Perhaps there are options that I can set in my endpointResolver to fix the encoding issue, but there is no documentation on that AFAIK

Additional Information/Context

No response

AWS Go SDK V2 Module Versions Used

v1.19.0

Compiler and Version used

go version go1.20.6 linux/amd64

Operating System and version

Debian GNU/Linux 12 (bookworm)

RanVaknin commented 1 year ago

Hi @Senui ,

Thanks for reaching out.

This is not an issue with encoding. S3's API specifies that a / character cannot be used to describe a bucket name:

image

This causes the SDK to encode the invalid character to conform to S3's API requirements.

The issue at hand is that the formatting the SDK uses by default to serialize requests to S3 is of a virtual-hosted URL that conforms to: bucket.s3.region.amazonaws.com whereas your local test server expects a path-style url like myswift.domain.com/bucket.

You can force path style by passing it as a functional options to your api call:

func main() {
    bucketName := "mybucket"
    endpoint := "https://myswift.domain.com"
    region := "NL"

    customResolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
        return aws.Endpoint{
            URL:           endpoint,
            SigningRegion: region,
        }, nil
    })

    cfg, err := config.LoadDefaultConfig(context.TODO(),
        config.WithRegion(region),
        config.WithEndpointResolverWithOptions(customResolver),
        config.WithClientLogMode(aws.LogRetries|aws.LogRequest|aws.LogRequestWithBody),
    )
    if err != nil {
        fmt.Println("Failed to load AWS configuration:", err)
        return
    }

    client := s3.NewFromConfig(cfg)

    resp, err := client.ListObjectsV2(context.TODO(), &s3.ListObjectsV2Input{
        Bucket: aws.String(bucketName),
    }, func(options *s3.Options) {
        options.UsePathStyle = true
    })
// more code...

Will result in the expected serialized request:

SDK 2023/07/14 13:56:01 DEBUG Request
GET /mybucket?list-type=2 HTTP/1.1
Host: myswift.domain.com

Let me know if this helps.

Thanks, Ran~

Senui commented 1 year ago

Dear @RanVaknin,

Thank you so much for your quick reply. You are exactly right - setting the HostnameImmutable to true resolves my issue. I didn't know about the virtual-hosted URLs vs path-style URLs! It now makes complete sense as to why the bucket name was automatically prepended to the hostname without a prepending forward slash in the bucket's name.

Really great to see that this was taken into account in the SDK and that such a convenient flag is all it takes to switch between these two styles.

I hereby close the ticket. Thanks again for taking your time to look into this.

Ahmad

github-actions[bot] commented 1 year ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.