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.
This commit is contained in:
Nick Craig-Wood 2020-06-10 21:15:34 +01:00
parent a2fa1370c5
commit c85438d34b
2 changed files with 25 additions and 4 deletions

View file

@ -1523,7 +1523,7 @@ func s3Connection(opt *Options) (*s3.S3, *session.Session, error) {
if req.Config.Credentials == credentials.AnonymousCredentials { if req.Config.Credentials == credentials.AnonymousCredentials {
return return
} }
sign(v.AccessKeyID, v.SecretAccessKey, req.HTTPRequest) v2sign(opt, req.HTTPRequest)
} }
c.Handlers.Sign.Clear() c.Handlers.Sign.Clear()
c.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler) c.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)

View file

@ -9,7 +9,10 @@ import (
"net/http" "net/http"
"sort" "sort"
"strings" "strings"
"sync"
"time" "time"
"github.com/rclone/rclone/fs"
) )
// URL parameters that need to be added to the signature // URL parameters that need to be added to the signature
@ -35,10 +38,13 @@ var s3ParamsToSign = map[string]struct{}{
"response-content-encoding": {}, "response-content-encoding": {},
} }
// Warn once about empty endpoint
var warnEmptyEndpointOnce sync.Once
// sign signs requests using v2 auth // sign signs requests using v2 auth
// //
// Cobbled together from goamz and aws-sdk-go // 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 // Set date
date := time.Now().UTC().Format(time.RFC1123) date := time.Now().UTC().Format(time.RFC1123)
req.Header.Set("Date", date) req.Header.Set("Date", date)
@ -48,6 +54,21 @@ func sign(AccessKey, SecretKey string, req *http.Request) {
if uri == "" { if uri == "" {
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 // Look through headers of interest
var md5 string var md5 string
@ -96,11 +117,11 @@ func sign(AccessKey, SecretKey string, req *http.Request) {
// Make signature // Make signature
payload := req.Method + "\n" + md5 + "\n" + contentType + "\n" + date + "\n" + joinedHeadersToSign + uri 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)) _, _ = hash.Write([]byte(payload))
signature := make([]byte, base64.StdEncoding.EncodedLen(hash.Size())) signature := make([]byte, base64.StdEncoding.EncodedLen(hash.Size()))
base64.StdEncoding.Encode(signature, hash.Sum(nil)) base64.StdEncoding.Encode(signature, hash.Sum(nil))
// Set signature in request // Set signature in request
req.Header.Set("Authorization", "AWS "+AccessKey+":"+string(signature)) req.Header.Set("Authorization", "AWS "+opt.AccessKeyID+":"+string(signature))
} }