mirror of
https://github.com/rclone/rclone.git
synced 2025-12-06 00:03:32 +00:00
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.
128 lines
3.4 KiB
Go
128 lines
3.4 KiB
Go
// v2 signing
|
|
|
|
package s3
|
|
|
|
import (
|
|
"crypto/hmac"
|
|
"crypto/sha1"
|
|
"encoding/base64"
|
|
"net/http"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/rclone/rclone/fs"
|
|
)
|
|
|
|
// URL parameters that need to be added to the signature
|
|
var s3ParamsToSign = map[string]struct{}{
|
|
"acl": {},
|
|
"location": {},
|
|
"logging": {},
|
|
"notification": {},
|
|
"partNumber": {},
|
|
"policy": {},
|
|
"requestPayment": {},
|
|
"torrent": {},
|
|
"uploadId": {},
|
|
"uploads": {},
|
|
"versionId": {},
|
|
"versioning": {},
|
|
"versions": {},
|
|
"response-content-type": {},
|
|
"response-content-language": {},
|
|
"response-expires": {},
|
|
"response-cache-control": {},
|
|
"response-content-disposition": {},
|
|
"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 v2sign(opt *Options, req *http.Request) {
|
|
// Set date
|
|
date := time.Now().UTC().Format(time.RFC1123)
|
|
req.Header.Set("Date", date)
|
|
|
|
// Sort out URI
|
|
uri := req.URL.EscapedPath()
|
|
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
|
|
var contentType string
|
|
var headersToSign []string
|
|
for k, v := range req.Header {
|
|
k = strings.ToLower(k)
|
|
switch k {
|
|
case "content-md5":
|
|
md5 = v[0]
|
|
case "content-type":
|
|
contentType = v[0]
|
|
default:
|
|
if strings.HasPrefix(k, "x-amz-") {
|
|
vall := strings.Join(v, ",")
|
|
headersToSign = append(headersToSign, k+":"+vall)
|
|
}
|
|
}
|
|
}
|
|
// Make headers of interest into canonical string
|
|
var joinedHeadersToSign string
|
|
if len(headersToSign) > 0 {
|
|
sort.StringSlice(headersToSign).Sort()
|
|
joinedHeadersToSign = strings.Join(headersToSign, "\n") + "\n"
|
|
}
|
|
|
|
// Look for query parameters which need to be added to the signature
|
|
params := req.URL.Query()
|
|
var queriesToSign []string
|
|
for k, vs := range params {
|
|
if _, ok := s3ParamsToSign[k]; ok {
|
|
for _, v := range vs {
|
|
if v == "" {
|
|
queriesToSign = append(queriesToSign, k)
|
|
} else {
|
|
queriesToSign = append(queriesToSign, k+"="+v)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Add query parameters to URI
|
|
if len(queriesToSign) > 0 {
|
|
sort.StringSlice(queriesToSign).Sort()
|
|
uri += "?" + strings.Join(queriesToSign, "&")
|
|
}
|
|
|
|
// Make signature
|
|
payload := req.Method + "\n" + md5 + "\n" + contentType + "\n" + date + "\n" + joinedHeadersToSign + uri
|
|
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 "+opt.AccessKeyID+":"+string(signature))
|
|
}
|