...

Source file src/github.com/docker/distribution/registry/storage/driver/s3-aws/s3_v2_signer.go

Documentation: github.com/docker/distribution/registry/storage/driver/s3-aws

     1  package s3
     2  
     3  // Source: https://github.com/pivotal-golang/s3cli
     4  
     5  // Copyright (c) 2013 Damien Le Berrigaud and Nick Wade
     6  
     7  // Permission is hereby granted, free of charge, to any person obtaining a copy
     8  // of this software and associated documentation files (the "Software"), to deal
     9  // in the Software without restriction, including without limitation the rights
    10  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    11  // copies of the Software, and to permit persons to whom the Software is
    12  // furnished to do so, subject to the following conditions:
    13  
    14  // The above copyright notice and this permission notice shall be included in
    15  // all copies or substantial portions of the Software.
    16  
    17  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    18  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    19  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    20  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    21  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    22  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    23  // THE SOFTWARE.
    24  
    25  import (
    26  	"crypto/hmac"
    27  	"crypto/sha1"
    28  	"encoding/base64"
    29  	"net/http"
    30  	"net/url"
    31  	"sort"
    32  	"strings"
    33  	"time"
    34  
    35  	"github.com/aws/aws-sdk-go/aws/corehandlers"
    36  	"github.com/aws/aws-sdk-go/aws/credentials"
    37  	"github.com/aws/aws-sdk-go/aws/request"
    38  	"github.com/aws/aws-sdk-go/service/s3"
    39  	log "github.com/sirupsen/logrus"
    40  )
    41  
    42  type signer struct {
    43  	// Values that must be populated from the request
    44  	Request      *http.Request
    45  	Time         time.Time
    46  	Credentials  *credentials.Credentials
    47  	Query        url.Values
    48  	stringToSign string
    49  	signature    string
    50  }
    51  
    52  var s3ParamsToSign = map[string]bool{
    53  	"acl":                          true,
    54  	"location":                     true,
    55  	"logging":                      true,
    56  	"notification":                 true,
    57  	"partNumber":                   true,
    58  	"policy":                       true,
    59  	"requestPayment":               true,
    60  	"torrent":                      true,
    61  	"uploadId":                     true,
    62  	"uploads":                      true,
    63  	"versionId":                    true,
    64  	"versioning":                   true,
    65  	"versions":                     true,
    66  	"response-content-type":        true,
    67  	"response-content-language":    true,
    68  	"response-expires":             true,
    69  	"response-cache-control":       true,
    70  	"response-content-disposition": true,
    71  	"response-content-encoding":    true,
    72  	"website":                      true,
    73  	"delete":                       true,
    74  }
    75  
    76  // setv2Handlers will setup v2 signature signing on the S3 driver
    77  func setv2Handlers(svc *s3.S3) {
    78  	svc.Handlers.Build.PushBack(func(r *request.Request) {
    79  		parsedURL, err := url.Parse(r.HTTPRequest.URL.String())
    80  		if err != nil {
    81  			log.Fatalf("Failed to parse URL: %v", err)
    82  		}
    83  		r.HTTPRequest.URL.Opaque = parsedURL.Path
    84  	})
    85  
    86  	svc.Handlers.Sign.Clear()
    87  	svc.Handlers.Sign.PushBack(Sign)
    88  	svc.Handlers.Sign.PushBackNamed(corehandlers.BuildContentLengthHandler)
    89  }
    90  
    91  // Sign requests with signature version 2.
    92  //
    93  // Will sign the requests with the service config's Credentials object
    94  // Signing is skipped if the credentials is the credentials.AnonymousCredentials
    95  // object.
    96  func Sign(req *request.Request) {
    97  	// If the request does not need to be signed ignore the signing of the
    98  	// request if the AnonymousCredentials object is used.
    99  	if req.Config.Credentials == credentials.AnonymousCredentials {
   100  		return
   101  	}
   102  
   103  	v2 := signer{
   104  		Request:     req.HTTPRequest,
   105  		Time:        req.Time,
   106  		Credentials: req.Config.Credentials,
   107  	}
   108  	v2.Sign()
   109  }
   110  
   111  func (v2 *signer) Sign() error {
   112  	credValue, err := v2.Credentials.Get()
   113  	if err != nil {
   114  		return err
   115  	}
   116  	accessKey := credValue.AccessKeyID
   117  	var (
   118  		md5, ctype, date, xamz string
   119  		xamzDate               bool
   120  		sarray                 []string
   121  		smap                   map[string]string
   122  		sharray                []string
   123  	)
   124  
   125  	headers := v2.Request.Header
   126  	params := v2.Request.URL.Query()
   127  	parsedURL, err := url.Parse(v2.Request.URL.String())
   128  	if err != nil {
   129  		return err
   130  	}
   131  	host, canonicalPath := parsedURL.Host, parsedURL.Path
   132  	v2.Request.Header["Host"] = []string{host}
   133  	v2.Request.Header["date"] = []string{v2.Time.In(time.UTC).Format(time.RFC1123)}
   134  	if credValue.SessionToken != "" {
   135  		v2.Request.Header["x-amz-security-token"] = []string{credValue.SessionToken}
   136  	}
   137  
   138  	smap = make(map[string]string)
   139  	for k, v := range headers {
   140  		k = strings.ToLower(k)
   141  		switch k {
   142  		case "content-md5":
   143  			md5 = v[0]
   144  		case "content-type":
   145  			ctype = v[0]
   146  		case "date":
   147  			if !xamzDate {
   148  				date = v[0]
   149  			}
   150  		default:
   151  			if strings.HasPrefix(k, "x-amz-") {
   152  				vall := strings.Join(v, ",")
   153  				smap[k] = k + ":" + vall
   154  				if k == "x-amz-date" {
   155  					xamzDate = true
   156  					date = ""
   157  				}
   158  				sharray = append(sharray, k)
   159  			}
   160  		}
   161  	}
   162  	if len(sharray) > 0 {
   163  		sort.StringSlice(sharray).Sort()
   164  		for _, h := range sharray {
   165  			sarray = append(sarray, smap[h])
   166  		}
   167  		xamz = strings.Join(sarray, "\n") + "\n"
   168  	}
   169  
   170  	expires := false
   171  	if v, ok := params["Expires"]; ok {
   172  		expires = true
   173  		date = v[0]
   174  		params["AWSAccessKeyId"] = []string{accessKey}
   175  	}
   176  
   177  	sarray = sarray[0:0]
   178  	for k, v := range params {
   179  		if s3ParamsToSign[k] {
   180  			for _, vi := range v {
   181  				if vi == "" {
   182  					sarray = append(sarray, k)
   183  				} else {
   184  					sarray = append(sarray, k+"="+vi)
   185  				}
   186  			}
   187  		}
   188  	}
   189  	if len(sarray) > 0 {
   190  		sort.StringSlice(sarray).Sort()
   191  		canonicalPath = canonicalPath + "?" + strings.Join(sarray, "&")
   192  	}
   193  
   194  	v2.stringToSign = strings.Join([]string{
   195  		v2.Request.Method,
   196  		md5,
   197  		ctype,
   198  		date,
   199  		xamz + canonicalPath,
   200  	}, "\n")
   201  	hash := hmac.New(sha1.New, []byte(credValue.SecretAccessKey))
   202  	hash.Write([]byte(v2.stringToSign))
   203  	v2.signature = base64.StdEncoding.EncodeToString(hash.Sum(nil))
   204  
   205  	if expires {
   206  		params["Signature"] = []string{v2.signature}
   207  	} else {
   208  		headers["Authorization"] = []string{"AWS " + accessKey + ":" + v2.signature}
   209  	}
   210  
   211  	log.WithFields(log.Fields{
   212  		"string-to-sign": v2.stringToSign,
   213  		"signature":      v2.signature,
   214  	}).Debugln("request signature")
   215  	return nil
   216  }
   217  

View as plain text