From c85438d34bbc93ecf07d4c5a2ae0fe5433c5c5d4 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 10 Jun 2020 21:15:34 +0100 Subject: [PATCH] s3: fix v2 auth when using force_path_style = false The V2 auth was failing with AWS and KS3 when used with force_path_style = false (which is the default for both providers now). The V2 Auth needed the bucket prepended onto the string that gets signed. Note that endpoint must be set when using v2 auth with AWS. The code warns about this. This was worked out by observing the behaviour of s3cmd. --- backend/s3/s3.go | 2 +- backend/s3/v2sign.go | 27 ++++++++++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/backend/s3/s3.go b/backend/s3/s3.go index d3694cc2c..4cbf6fa0f 100644 --- a/backend/s3/s3.go +++ b/backend/s3/s3.go @@ -1523,7 +1523,7 @@ func s3Connection(opt *Options) (*s3.S3, *session.Session, error) { if req.Config.Credentials == credentials.AnonymousCredentials { return } - sign(v.AccessKeyID, v.SecretAccessKey, req.HTTPRequest) + v2sign(opt, req.HTTPRequest) } c.Handlers.Sign.Clear() c.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler) diff --git a/backend/s3/v2sign.go b/backend/s3/v2sign.go index 2c22378e3..d6e638431 100644 --- a/backend/s3/v2sign.go +++ b/backend/s3/v2sign.go @@ -9,7 +9,10 @@ import ( "net/http" "sort" "strings" + "sync" "time" + + "github.com/rclone/rclone/fs" ) // URL parameters that need to be added to the signature @@ -35,10 +38,13 @@ var s3ParamsToSign = map[string]struct{}{ "response-content-encoding": {}, } +// Warn once about empty endpoint +var warnEmptyEndpointOnce sync.Once + // sign signs requests using v2 auth // // Cobbled together from goamz and aws-sdk-go -func sign(AccessKey, SecretKey string, req *http.Request) { +func v2sign(opt *Options, req *http.Request) { // Set date date := time.Now().UTC().Format(time.RFC1123) req.Header.Set("Date", date) @@ -48,6 +54,21 @@ func sign(AccessKey, SecretKey string, req *http.Request) { if uri == "" { uri = "/" } + // If not using path style then need to stick the bucket on + // the start of the requests if doing a bucket based query + if !opt.ForcePathStyle { + if opt.Endpoint == "" { + warnEmptyEndpointOnce.Do(func() { + fs.Logf(nil, "If using v2 auth with AWS and force_path_style=false, endpoint must be set in the config") + }) + } else if req.URL.Host != opt.Endpoint { + // read the bucket off the start of the hostname + i := strings.IndexRune(req.URL.Host, '.') + if i >= 0 { + uri = "/" + req.URL.Host[:i] + uri + } + } + } // Look through headers of interest var md5 string @@ -96,11 +117,11 @@ func sign(AccessKey, SecretKey string, req *http.Request) { // Make signature payload := req.Method + "\n" + md5 + "\n" + contentType + "\n" + date + "\n" + joinedHeadersToSign + uri - hash := hmac.New(sha1.New, []byte(SecretKey)) + hash := hmac.New(sha1.New, []byte(opt.SecretAccessKey)) _, _ = hash.Write([]byte(payload)) signature := make([]byte, base64.StdEncoding.EncodedLen(hash.Size())) base64.StdEncoding.Encode(signature, hash.Sum(nil)) // Set signature in request - req.Header.Set("Authorization", "AWS "+AccessKey+":"+string(signature)) + req.Header.Set("Authorization", "AWS "+opt.AccessKeyID+":"+string(signature)) }