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.66k stars 640 forks source link

SignatureDoesNotMatch error when used with GCS #1816

Closed boraberke closed 1 year ago

boraberke commented 2 years ago

Describe the bug

I'm facing SignatureDoesNotMatch error when I use endpoint url as google cloud storage https://storage.googleapis.com.

I didn't have this error when using aws-sdk-go.

Expected Behavior

To be able to send requests successfully just like in aws-sdk-go.

Current Behavior

I used a simple code where I try to ListBuckets with default settings. Following is the debug output and error:

SDK 2022/08/25 10:01:40 DEBUG Request Signature:
---[ CANONICAL STRING  ]-----------------------------
GET
/

accept-encoding:identity
amz-sdk-invocation-id:159ecffe-dd09-43e5-8f3a-1c3a02e3da5b
amz-sdk-request:attempt=1; max=3
host:storage.googleapis.com
x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-date:20220825T070140Z

accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
---[ STRING TO SIGN ]--------------------------------
AWS4-HMAC-SHA256
20220825T070140Z
20220825/auto/s3/aws4_request
97655a46f48a0bab4d36278d984931d1a4c1bc6fa8cdf77d4f3e70465a62fa6f
-----------------------------------------------------
SDK 2022/08/25 10:01:40 DEBUG Request
GET / HTTP/1.1
Host: storage.googleapis.com
User-Agent: aws-sdk-go-v2/1.16.7 os/macos lang/go/1.18.3 md/GOOS/darwin md/GOARCH/amd64 api/s3/1.27.1
Accept-Encoding: identity
Amz-Sdk-Invocation-Id: 159ecffe-dd09-43e5-8f3a-1c3a02e3da5b
Amz-Sdk-Request: attempt=1; max=3
Authorization: AWS4-HMAC-SHA256 Credential=GOOGXXXXCM3H4LRVOSWKUFX/20220825/auto/s3/aws4_request, SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date, Signature=4613a0c21bfa65cbe2ca3895943c570dc14ae938d9059acb605bea8efc5aa81f
X-Amz-Content-Sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Amz-Date: 20220825T070140Z

SDK 2022/08/25 10:01:40 DEBUG Response
HTTP/2.0 403 Forbidden
Content-Length: 859
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
Cache-Control: private, max-age=0
Content-Type: application/xml; charset=UTF-8
Date: Thu, 25 Aug 2022 07:01:40 GMT
Expires: Thu, 25 Aug 2022 07:01:40 GMT
Server: UploadServer
X-Guploader-Uploadid: ADPycds0ijOgnsfw23HvSlSotY-STzCuGu6pj3GoXj49NNJeUCziHkT4sXz4vIcn_syis54UIon8h-O4KOcaR--G7Eq3lDhmrQmr

ListBuckets: operation error S3: ListBuckets, https response error StatusCode: 403, RequestID: , HostID: , api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.

Similarly, when I try to use aws-sdk-go, it is successful:

2022/08/25 10:12:18 DEBUG: Request Signature:
---[ CANONICAL STRING  ]-----------------------------
GET
/

host:storage.googleapis.com
x-amz-content-sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
x-amz-date:20220825T071218Z

host;x-amz-content-sha256;x-amz-date
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
---[ STRING TO SIGN ]--------------------------------
AWS4-HMAC-SHA256
20220825T071218Z
20220825/auto/s3/aws4_request
802442955334a38685a8630a467ad8c046e350beb19662a3f4d2fdb8b5120511
-----------------------------------------------------
2022/08/25 10:12:18 DEBUG: Request s3/ListBuckets Details:
---[ REQUEST POST-SIGN ]-----------------------------
GET / HTTP/1.1
Host: storage.googleapis.com
User-Agent: aws-sdk-go/1.40.25 (go1.18.3; darwin; amd64)
Authorization: AWS4-HMAC-SHA256 Credential=GOOGXXXXVCM3H4LRVOSWKUFX/20220825/auto/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=d211e86e8a77e5db71fcb99dc62f5c47ff6487a051169bf5fd787084800d8f13
X-Amz-Content-Sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
X-Amz-Date: 20220825T071218Z
Accept-Encoding: gzip

-----------------------------------------------------
2022/08/25 10:12:18 DEBUG: Response s3/ListBuckets Details:
---[ RESPONSE ]--------------------------------------
HTTP/2.0 200 OK
Content-Length: 251
Alt-Svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000,h3-Q050=":443"; ma=2592000,h3-Q046=":443"; ma=2592000,h3-Q043=":443"; ma=2592000,quic=":443"; ma=2592000; v="46,43"
Cache-Control: private, max-age=0
Content-Type: application/xml; charset=UTF-8
Date: Thu, 25 Aug 2022 07:12:18 GMT
Expires: Thu, 25 Aug 2022 07:12:18 GMT
Server: UploadServer
X-Guploader-Uploadid: ADPycdsAazh0HYg9mOjQrtoUryuKndpaFtZeRbadNnaiaCccL-7DAshcQzVbFcXqGHG1iLwWMWHmzNY_FK8koXLQBvgxRYe-FfXR

-----------------------------------------------------
Buckets:bbs-test-bucket

Reproduction Steps

Below there are two gists, identically doing the same thing using aws-sdk-go and aws-sdk-go-v2 respectively.

using aws-sdk-go

using aws-sdk-go-v2

Possible Solution

Some additional headers such as amz-sdk-request and amz-sdk-invocation-id are added to SignedHeaders in v2. Issue might be related to that, but I don't have a possible solution in mind.

Additional Information/Context

GCS gives an example usage with aws-sdk-go here but there is no example with aws-sdk-go-v2.

AWS Go SDK V2 Module Versions Used

    "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"

Compiler and Version used

go version go1.18.3 darwin/amd64

Operating System and version

macos monterey 12.4

RanVaknin commented 2 years ago

Hi @boraberke,

Thanks for opening the issue. The SDKs don't guarantee compatibility with 3rd party platforms like in your case. I don't know the exact reason v1 works and v2 doesn't - it could be because of some headers that you pointed out. You could try and remove them by writing your own middleware for the client and see if the signature will match but again this is a speculation.

I wish I could help. Ran~

boraberke commented 2 years ago

Hi @RanVaknin,

I found the issue of the problem. When I remove accept-encoding header from signed headers, problem is solved. Instead of

SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date,

I used

SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date,

Do you know the reason why accept-encoding is added as one of the SignedHeaders in aws-sdk-go-v2? It is not used in aws-sdk-go.

RanVaknin commented 2 years ago

Hi @boraberke ,

Im not entirely sure. The V2 of the SDK was released to conform to requirements from the AWS service teams. I know that in the V2 of the SDK the signature is the very last step in the middleware stack and there are no un-signed headers, something that was not guaranteed in V1.

I'm happy you were able to figure it out and make it work! Hope I could be more help in the future. Ran~

github-actions[bot] commented 2 years 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.

szabolcsgelencser commented 2 years ago

Hi @RanVaknin,

I found the issue of the problem. When I remove accept-encoding header from signed headers, problem is solved. Instead of

SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date,

I used

SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date,

Do you know the reason why accept-encoding is added as one of the SignedHeaders in aws-sdk-go-v2? It is not used in aws-sdk-go.

Hi @boraberke, could you please share how you excluded the accept-encoding header exactly (what is the code for this, can it be done without modifying the lib's codebase itself)?

boraberke commented 2 years ago

Hi @RanVaknin, I found the issue of the problem. When I remove accept-encoding header from signed headers, problem is solved. Instead of

SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date,

I used

SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date,

Do you know the reason why accept-encoding is added as one of the SignedHeaders in aws-sdk-go-v2? It is not used in aws-sdk-go.

Hi @boraberke, could you please share how you excluded the accept-encoding header exactly (what is the code for this, can it be done without modifying the lib's codebase itself)?

Hi @szabolcsgelencser,

Unfortunately I couldn't find a way to exclude accept-encoding without changing the source code of the sdk.

RanVaknin commented 1 year ago

Hi @boraberke ,@szabolcsgelencser, and anyone else who is also experiencing this:

I don't have experience in GCS, and in order to take a deeper look I need some detailed repro steps. If you can help with that I could try and find a workaround (probably implementing custom middleware to remove that header before signing)

Thanks, Ran~

github-actions[bot] commented 1 year 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.

air3ijai commented 1 year ago

@RanVaknin, we experience same issue with our code to test GCP bucket

Steps to reproduce

  1. Create a directory

    mkdir test
    cd test
  2. Init module

    go mod init test
  3. Install modules

    go get github.com/aws/aws-sdk-go-v2/config
    go get github.com/aws/aws-sdk-go-v2/credentials
    go get github.com/aws/aws-sdk-go-v2/service/s3
  4. Create file with code for SDK v2

    main.go ```go package main import ( "context" "errors" "fmt" "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" ) type Config struct { URL string AccessKey string `envconfig:"ACCESS_KEY"` SecretKey string `envconfig:"SECRET_KEY"` Region string Bucket string } func main() { ctx := context.Background() cfg := Config{ URL: "https://storage.googleapis.com", AccessKey: "", SecretKey: "", Region: "auto", Bucket: "", } credentials := credentials.NewStaticCredentialsProvider(cfg.AccessKey, cfg.SecretKey, "") // Obtaining the S3 SDK client configuration based on the passed parameters. cnf, err := config.LoadDefaultConfig( ctx, config.WithCredentialsProvider(credentials), config.WithRegion(cfg.Region), ) if err != nil { panic(err) } // Create a new S3 SDK client instance. s3Client := s3.NewFromConfig( cnf, s3.WithEndpointResolver( s3.EndpointResolverFromURL(cfg.URL), ), func(opts *s3.Options) { opts.UsePathStyle = true }, ) if s3Client == nil { panic(errors.New("creating an S3 SDK client failed")) } res, err := s3Client.GetBucketLocation(ctx, &s3.GetBucketLocationInput{ Bucket: &cfg.Bucket, }) fmt.Println(res, err) } ```
  5. Run the code

    go run .
  6. Get the error

    <nil> operation error S3: GetBucketLocation, https response error StatusCode: 403, RequestID: , HostID: , api error SignatureDoesNotMatch: The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.

Check credentials and permissions

# Version
aws --version
aws-cli/2.9.9 Python/3.9.11

# Authenticate
export AWS_ACCESS_KEY_ID="<AWS_ACCESS_KEY_ID>"
export AWS_SECRET_ACCESS_KEY="<AWS_SECRET_ACCESS_KEY>"

# List
aws s3 --endpoint-url https://storage.googleapis.com ls bucket
2023-03-13 13:32:47          3 check.txt

# Head bucket
aws s3api \
  --endpoint-url https://storage.googleapis.com \
  head-bucket \
  --bucket bucket

# Get Region
aws s3api \
  --endpoint-url https://storage.googleapis.com \
  get-bucket-location \
  --bucket bucket

{
    "LocationConstraint": "ASIA-SOUTHEAST1"
}
runlilong commented 1 year ago

Hi @RanVaknin, I found the issue of the problem. When I remove accept-encoding header from signed headers, problem is solved. Instead of

SignedHeaders=accept-encoding;amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date,

I used

SignedHeaders=amz-sdk-invocation-id;amz-sdk-request;host;x-amz-content-sha256;x-amz-date,

Do you know the reason why accept-encoding is added as one of the SignedHeaders in aws-sdk-go-v2? It is not used in aws-sdk-go.

Hi @boraberke, could you please share how you excluded the accept-encoding header exactly (what is the code for this, can it be done without modifying the lib's codebase itself)?

I found a little hack way to achieve it. the idea is to use go:linkname to access the internal variable v4.IgnoredHeaders and append a new item accept-encoding to it. @szabolcsgelencser hope it can be helpful.

type Rule interface {
    IsValid(value string) bool
}
type Rules []Rule
//go:linkname __ignoredHeaders github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4.IgnoredHeaders
var __ignoredHeaders unsafe.Pointer

// Avoids "go.info.github.com/aws/aws-sdk-go-v2/aws/signer/internal/v4.IgnoredHeaders:
// relocation target go.info.github.com/xxx/xxx/xxx.Rules not defined"
// refer https://github.com/pkujhd/goloader/blob/09f36c84ac85502eb5df4670f1aa7472934ba03a/iface.1.10.go#L31-L36
var ignoredHeaders = (*Rules)(unsafe.Pointer(&__ignoredHeaders))

reflect.ValueOf((*ignoredHeaders)[0]).FieldByName("Rule").Elem().SetMapIndex(
    reflect.ValueOf("Accept-Encoding"), reflect.ValueOf(struct{}{}))
Slach commented 1 year ago

More safe way, https://stackoverflow.com/a/74382598/1204665, but with more code and could be less performance

ganigeorgiev commented 9 months ago

I'm not sure whether it will be useful to others or if it is the best way to handle it, but based on the above stackoverflow question, here is my ignoreSigningHeaders middleware:

Usage pseudo-code:

client := s3.NewFromConfig(cfg, func(o *s3.Options) {
    // Google Cloud Storage alters the Accept-Encoding header, which breaks the v2 request signature
    // (https://github.com/aws/aws-sdk-go-v2/issues/1816)
    if strings.Contains(endpoint, "storage.googleapis.com") {
        ignoreSigningHeaders(o, []string{"Accept-Encoding"})
    }
})

Middleware:

package yourpackage

import (
    "context"
    "fmt"

    v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
    "github.com/aws/aws-sdk-go-v2/service/s3"
    "github.com/aws/smithy-go/middleware"
    smithyhttp "github.com/aws/smithy-go/transport/http"
)

// ignoreSigningHeaders excludes the listed headers
// from the request signature because some providers may alter them.
//
// See https://github.com/aws/aws-sdk-go-v2/issues/1816.
func ignoreSigningHeaders(o *s3.Options, headers []string) {
    o.APIOptions = append(o.APIOptions, func(stack *middleware.Stack) error {
        if err := stack.Finalize.Insert(ignoreHeaders(headers), "Signing", middleware.Before); err != nil {
            return err
        }

        if err := stack.Finalize.Insert(restoreIgnored(), "Signing", middleware.After); err != nil {
            return err
        }

        return nil
    })
}

type ignoredHeadersKey struct{}

func ignoreHeaders(headers []string) middleware.FinalizeMiddleware {
    return middleware.FinalizeMiddlewareFunc(
        "IgnoreHeaders",
        func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (out middleware.FinalizeOutput, metadata middleware.Metadata, err error) {
            req, ok := in.Request.(*smithyhttp.Request)
            if !ok {
                return out, metadata, &v4.SigningError{Err: fmt.Errorf("(ignoreHeaders) unexpected request middleware type %T", in.Request)}
            }

            ignored := make(map[string]string, len(headers))
            for _, h := range headers {
                ignored[h] = req.Header.Get(h)
                req.Header.Del(h)
            }

            ctx = middleware.WithStackValue(ctx, ignoredHeadersKey{}, ignored)

            return next.HandleFinalize(ctx, in)
        },
    )
}

func restoreIgnored() middleware.FinalizeMiddleware {
    return middleware.FinalizeMiddlewareFunc(
        "RestoreIgnored",
        func(ctx context.Context, in middleware.FinalizeInput, next middleware.FinalizeHandler) (out middleware.FinalizeOutput, metadata middleware.Metadata, err error) {
            req, ok := in.Request.(*smithyhttp.Request)
            if !ok {
                return out, metadata, &v4.SigningError{Err: fmt.Errorf("(restoreIgnored) unexpected request middleware type %T", in.Request)}
            }

            ignored, _ := middleware.GetStackValue(ctx, ignoredHeadersKey{}).(map[string]string)
            for k, v := range ignored {
                req.Header.Set(k, v)
            }

            return next.HandleFinalize(ctx, in)
        },
    )
}