| package s3 |
| |
| // Source: https://github.com/pivotal-golang/s3cli |
| |
| // Copyright (c) 2013 Damien Le Berrigaud and Nick Wade |
| |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| |
| // The above copyright notice and this permission notice shall be included in |
| // all copies or substantial portions of the Software. |
| |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| // THE SOFTWARE. |
| |
| import ( |
| "crypto/hmac" |
| "crypto/sha1" |
| "encoding/base64" |
| "net/http" |
| "net/url" |
| "sort" |
| "strings" |
| "time" |
| |
| "github.com/aws/aws-sdk-go/aws/corehandlers" |
| "github.com/aws/aws-sdk-go/aws/credentials" |
| "github.com/aws/aws-sdk-go/aws/request" |
| "github.com/aws/aws-sdk-go/service/s3" |
| log "github.com/sirupsen/logrus" |
| ) |
| |
| const ( |
| signatureVersion = "2" |
| signatureMethod = "HmacSHA1" |
| timeFormat = "2006-01-02T15:04:05Z" |
| ) |
| |
| type signer struct { |
| // Values that must be populated from the request |
| Request *http.Request |
| Time time.Time |
| Credentials *credentials.Credentials |
| Query url.Values |
| stringToSign string |
| signature string |
| } |
| |
| var s3ParamsToSign = map[string]bool{ |
| "acl": true, |
| "location": true, |
| "logging": true, |
| "notification": true, |
| "partNumber": true, |
| "policy": true, |
| "requestPayment": true, |
| "torrent": true, |
| "uploadId": true, |
| "uploads": true, |
| "versionId": true, |
| "versioning": true, |
| "versions": true, |
| "response-content-type": true, |
| "response-content-language": true, |
| "response-expires": true, |
| "response-cache-control": true, |
| "response-content-disposition": true, |
| "response-content-encoding": true, |
| "website": true, |
| "delete": true, |
| } |
| |
| // setv2Handlers will setup v2 signature signing on the S3 driver |
| func setv2Handlers(svc *s3.S3) { |
| svc.Handlers.Build.PushBack(func(r *request.Request) { |
| parsedURL, err := url.Parse(r.HTTPRequest.URL.String()) |
| if err != nil { |
| log.Fatalf("Failed to parse URL: %v", err) |
| } |
| r.HTTPRequest.URL.Opaque = parsedURL.Path |
| }) |
| |
| svc.Handlers.Sign.Clear() |
| svc.Handlers.Sign.PushBack(Sign) |
| svc.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler) |
| } |
| |
| // Sign requests with signature version 2. |
| // |
| // Will sign the requests with the service config's Credentials object |
| // Signing is skipped if the credentials is the credentials.AnonymousCredentials |
| // object. |
| func Sign(req *request.Request) { |
| // If the request does not need to be signed ignore the signing of the |
| // request if the AnonymousCredentials object is used. |
| if req.Config.Credentials == credentials.AnonymousCredentials { |
| return |
| } |
| |
| v2 := signer{ |
| Request: req.HTTPRequest, |
| Time: req.Time, |
| Credentials: req.Config.Credentials, |
| } |
| v2.Sign() |
| } |
| |
| func (v2 *signer) Sign() error { |
| credValue, err := v2.Credentials.Get() |
| if err != nil { |
| return err |
| } |
| accessKey := credValue.AccessKeyID |
| var ( |
| md5, ctype, date, xamz string |
| xamzDate bool |
| sarray []string |
| smap map[string]string |
| sharray []string |
| ) |
| |
| headers := v2.Request.Header |
| params := v2.Request.URL.Query() |
| parsedURL, err := url.Parse(v2.Request.URL.String()) |
| if err != nil { |
| return err |
| } |
| host, canonicalPath := parsedURL.Host, parsedURL.Path |
| v2.Request.Header["Host"] = []string{host} |
| v2.Request.Header["date"] = []string{v2.Time.In(time.UTC).Format(time.RFC1123)} |
| if credValue.SessionToken != "" { |
| v2.Request.Header["x-amz-security-token"] = []string{credValue.SessionToken} |
| } |
| |
| smap = make(map[string]string) |
| for k, v := range headers { |
| k = strings.ToLower(k) |
| switch k { |
| case "content-md5": |
| md5 = v[0] |
| case "content-type": |
| ctype = v[0] |
| case "date": |
| if !xamzDate { |
| date = v[0] |
| } |
| default: |
| if strings.HasPrefix(k, "x-amz-") { |
| vall := strings.Join(v, ",") |
| smap[k] = k + ":" + vall |
| if k == "x-amz-date" { |
| xamzDate = true |
| date = "" |
| } |
| sharray = append(sharray, k) |
| } |
| } |
| } |
| if len(sharray) > 0 { |
| sort.StringSlice(sharray).Sort() |
| for _, h := range sharray { |
| sarray = append(sarray, smap[h]) |
| } |
| xamz = strings.Join(sarray, "\n") + "\n" |
| } |
| |
| expires := false |
| if v, ok := params["Expires"]; ok { |
| expires = true |
| date = v[0] |
| params["AWSAccessKeyId"] = []string{accessKey} |
| } |
| |
| sarray = sarray[0:0] |
| for k, v := range params { |
| if s3ParamsToSign[k] { |
| for _, vi := range v { |
| if vi == "" { |
| sarray = append(sarray, k) |
| } else { |
| sarray = append(sarray, k+"="+vi) |
| } |
| } |
| } |
| } |
| if len(sarray) > 0 { |
| sort.StringSlice(sarray).Sort() |
| canonicalPath = canonicalPath + "?" + strings.Join(sarray, "&") |
| } |
| |
| v2.stringToSign = strings.Join([]string{ |
| v2.Request.Method, |
| md5, |
| ctype, |
| date, |
| xamz + canonicalPath, |
| }, "\n") |
| hash := hmac.New(sha1.New, []byte(credValue.SecretAccessKey)) |
| hash.Write([]byte(v2.stringToSign)) |
| v2.signature = base64.StdEncoding.EncodeToString(hash.Sum(nil)) |
| |
| if expires { |
| params["Signature"] = []string{v2.signature} |
| } else { |
| headers["Authorization"] = []string{"AWS " + accessKey + ":" + v2.signature} |
| } |
| |
| log.WithFields(log.Fields{ |
| "string-to-sign": v2.stringToSign, |
| "signature": v2.signature, |
| }).Debugln("request signature") |
| return nil |
| } |