...

Source file src/github.com/Azure/go-autorest/autorest/authorization_storage.go

Documentation: github.com/Azure/go-autorest/autorest

     1  package autorest
     2  
     3  // Copyright 2017 Microsoft Corporation
     4  //
     5  //  Licensed under the Apache License, Version 2.0 (the "License");
     6  //  you may not use this file except in compliance with the License.
     7  //  You may obtain a copy of the License at
     8  //
     9  //      http://www.apache.org/licenses/LICENSE-2.0
    10  //
    11  //  Unless required by applicable law or agreed to in writing, software
    12  //  distributed under the License is distributed on an "AS IS" BASIS,
    13  //  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14  //  See the License for the specific language governing permissions and
    15  //  limitations under the License.
    16  
    17  import (
    18  	"bytes"
    19  	"crypto/hmac"
    20  	"crypto/sha256"
    21  	"encoding/base64"
    22  	"fmt"
    23  	"net/http"
    24  	"net/url"
    25  	"sort"
    26  	"strings"
    27  	"time"
    28  )
    29  
    30  // SharedKeyType defines the enumeration for the various shared key types.
    31  // See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key for details on the shared key types.
    32  type SharedKeyType string
    33  
    34  const (
    35  	// SharedKey is used to authorize against blobs, files and queues services.
    36  	SharedKey SharedKeyType = "sharedKey"
    37  
    38  	// SharedKeyForTable is used to authorize against the table service.
    39  	SharedKeyForTable SharedKeyType = "sharedKeyTable"
    40  
    41  	// SharedKeyLite is used to authorize against blobs, files and queues services.  It's provided for
    42  	// backwards compatibility with API versions before 2009-09-19.  Prefer SharedKey instead.
    43  	SharedKeyLite SharedKeyType = "sharedKeyLite"
    44  
    45  	// SharedKeyLiteForTable is used to authorize against the table service.  It's provided for
    46  	// backwards compatibility with older table API versions.  Prefer SharedKeyForTable instead.
    47  	SharedKeyLiteForTable SharedKeyType = "sharedKeyLiteTable"
    48  )
    49  
    50  const (
    51  	headerAccept            = "Accept"
    52  	headerAcceptCharset     = "Accept-Charset"
    53  	headerContentEncoding   = "Content-Encoding"
    54  	headerContentLength     = "Content-Length"
    55  	headerContentMD5        = "Content-MD5"
    56  	headerContentLanguage   = "Content-Language"
    57  	headerIfModifiedSince   = "If-Modified-Since"
    58  	headerIfMatch           = "If-Match"
    59  	headerIfNoneMatch       = "If-None-Match"
    60  	headerIfUnmodifiedSince = "If-Unmodified-Since"
    61  	headerDate              = "Date"
    62  	headerXMSDate           = "X-Ms-Date"
    63  	headerXMSVersion        = "x-ms-version"
    64  	headerRange             = "Range"
    65  )
    66  
    67  const storageEmulatorAccountName = "devstoreaccount1"
    68  
    69  // SharedKeyAuthorizer implements an authorization for Shared Key
    70  // this can be used for interaction with Blob, File and Queue Storage Endpoints
    71  type SharedKeyAuthorizer struct {
    72  	accountName string
    73  	accountKey  []byte
    74  	keyType     SharedKeyType
    75  }
    76  
    77  // NewSharedKeyAuthorizer creates a SharedKeyAuthorizer using the provided credentials and shared key type.
    78  func NewSharedKeyAuthorizer(accountName, accountKey string, keyType SharedKeyType) (*SharedKeyAuthorizer, error) {
    79  	key, err := base64.StdEncoding.DecodeString(accountKey)
    80  	if err != nil {
    81  		return nil, fmt.Errorf("malformed storage account key: %v", err)
    82  	}
    83  	return &SharedKeyAuthorizer{
    84  		accountName: accountName,
    85  		accountKey:  key,
    86  		keyType:     keyType,
    87  	}, nil
    88  }
    89  
    90  // WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
    91  // value is "<SharedKeyType> " followed by the computed key.
    92  // This can be used for the Blob, Queue, and File Services
    93  //
    94  // from: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
    95  // You may use Shared Key authorization to authorize a request made against the
    96  // 2009-09-19 version and later of the Blob and Queue services,
    97  // and version 2014-02-14 and later of the File services.
    98  func (sk *SharedKeyAuthorizer) WithAuthorization() PrepareDecorator {
    99  	return func(p Preparer) Preparer {
   100  		return PreparerFunc(func(r *http.Request) (*http.Request, error) {
   101  			r, err := p.Prepare(r)
   102  			if err != nil {
   103  				return r, err
   104  			}
   105  
   106  			sk, err := buildSharedKey(sk.accountName, sk.accountKey, r, sk.keyType)
   107  			if err != nil {
   108  				return r, err
   109  			}
   110  			return Prepare(r, WithHeader(headerAuthorization, sk))
   111  		})
   112  	}
   113  }
   114  
   115  func buildSharedKey(accName string, accKey []byte, req *http.Request, keyType SharedKeyType) (string, error) {
   116  	canRes, err := buildCanonicalizedResource(accName, req.URL.String(), keyType)
   117  	if err != nil {
   118  		return "", err
   119  	}
   120  
   121  	if req.Header == nil {
   122  		req.Header = http.Header{}
   123  	}
   124  
   125  	// ensure date is set
   126  	if req.Header.Get(headerDate) == "" && req.Header.Get(headerXMSDate) == "" {
   127  		date := time.Now().UTC().Format(http.TimeFormat)
   128  		req.Header.Set(headerXMSDate, date)
   129  	}
   130  	canString, err := buildCanonicalizedString(req.Method, req.Header, canRes, keyType)
   131  	if err != nil {
   132  		return "", err
   133  	}
   134  	return createAuthorizationHeader(accName, accKey, canString, keyType), nil
   135  }
   136  
   137  func buildCanonicalizedResource(accountName, uri string, keyType SharedKeyType) (string, error) {
   138  	errMsg := "buildCanonicalizedResource error: %s"
   139  	u, err := url.Parse(uri)
   140  	if err != nil {
   141  		return "", fmt.Errorf(errMsg, err.Error())
   142  	}
   143  
   144  	cr := bytes.NewBufferString("")
   145  	if accountName != storageEmulatorAccountName {
   146  		cr.WriteString("/")
   147  		cr.WriteString(getCanonicalizedAccountName(accountName))
   148  	}
   149  
   150  	if len(u.Path) > 0 {
   151  		// Any portion of the CanonicalizedResource string that is derived from
   152  		// the resource's URI should be encoded exactly as it is in the URI.
   153  		// -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
   154  		cr.WriteString(u.EscapedPath())
   155  	} else {
   156  		// a slash is required to indicate the root path
   157  		cr.WriteString("/")
   158  	}
   159  
   160  	params, err := url.ParseQuery(u.RawQuery)
   161  	if err != nil {
   162  		return "", fmt.Errorf(errMsg, err.Error())
   163  	}
   164  
   165  	// See https://github.com/Azure/azure-storage-net/blob/master/Lib/Common/Core/Util/AuthenticationUtility.cs#L277
   166  	if keyType == SharedKey {
   167  		if len(params) > 0 {
   168  			cr.WriteString("\n")
   169  
   170  			keys := []string{}
   171  			for key := range params {
   172  				keys = append(keys, key)
   173  			}
   174  			sort.Strings(keys)
   175  
   176  			completeParams := []string{}
   177  			for _, key := range keys {
   178  				if len(params[key]) > 1 {
   179  					sort.Strings(params[key])
   180  				}
   181  
   182  				completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ",")))
   183  			}
   184  			cr.WriteString(strings.Join(completeParams, "\n"))
   185  		}
   186  	} else {
   187  		// search for "comp" parameter, if exists then add it to canonicalizedresource
   188  		if v, ok := params["comp"]; ok {
   189  			cr.WriteString("?comp=" + v[0])
   190  		}
   191  	}
   192  
   193  	return string(cr.Bytes()), nil
   194  }
   195  
   196  func getCanonicalizedAccountName(accountName string) string {
   197  	// since we may be trying to access a secondary storage account, we need to
   198  	// remove the -secondary part of the storage name
   199  	return strings.TrimSuffix(accountName, "-secondary")
   200  }
   201  
   202  func buildCanonicalizedString(verb string, headers http.Header, canonicalizedResource string, keyType SharedKeyType) (string, error) {
   203  	contentLength := headers.Get(headerContentLength)
   204  	if contentLength == "0" {
   205  		contentLength = ""
   206  	}
   207  	date := headers.Get(headerDate)
   208  	if v := headers.Get(headerXMSDate); v != "" {
   209  		if keyType == SharedKey || keyType == SharedKeyLite {
   210  			date = ""
   211  		} else {
   212  			date = v
   213  		}
   214  	}
   215  	var canString string
   216  	switch keyType {
   217  	case SharedKey:
   218  		canString = strings.Join([]string{
   219  			verb,
   220  			headers.Get(headerContentEncoding),
   221  			headers.Get(headerContentLanguage),
   222  			contentLength,
   223  			headers.Get(headerContentMD5),
   224  			headers.Get(headerContentType),
   225  			date,
   226  			headers.Get(headerIfModifiedSince),
   227  			headers.Get(headerIfMatch),
   228  			headers.Get(headerIfNoneMatch),
   229  			headers.Get(headerIfUnmodifiedSince),
   230  			headers.Get(headerRange),
   231  			buildCanonicalizedHeader(headers),
   232  			canonicalizedResource,
   233  		}, "\n")
   234  	case SharedKeyForTable:
   235  		canString = strings.Join([]string{
   236  			verb,
   237  			headers.Get(headerContentMD5),
   238  			headers.Get(headerContentType),
   239  			date,
   240  			canonicalizedResource,
   241  		}, "\n")
   242  	case SharedKeyLite:
   243  		canString = strings.Join([]string{
   244  			verb,
   245  			headers.Get(headerContentMD5),
   246  			headers.Get(headerContentType),
   247  			date,
   248  			buildCanonicalizedHeader(headers),
   249  			canonicalizedResource,
   250  		}, "\n")
   251  	case SharedKeyLiteForTable:
   252  		canString = strings.Join([]string{
   253  			date,
   254  			canonicalizedResource,
   255  		}, "\n")
   256  	default:
   257  		return "", fmt.Errorf("key type '%s' is not supported", keyType)
   258  	}
   259  	return canString, nil
   260  }
   261  
   262  func buildCanonicalizedHeader(headers http.Header) string {
   263  	cm := make(map[string]string)
   264  
   265  	for k := range headers {
   266  		headerName := strings.TrimSpace(strings.ToLower(k))
   267  		if strings.HasPrefix(headerName, "x-ms-") {
   268  			cm[headerName] = headers.Get(k)
   269  		}
   270  	}
   271  
   272  	if len(cm) == 0 {
   273  		return ""
   274  	}
   275  
   276  	keys := []string{}
   277  	for key := range cm {
   278  		keys = append(keys, key)
   279  	}
   280  
   281  	sort.Strings(keys)
   282  
   283  	ch := bytes.NewBufferString("")
   284  
   285  	for _, key := range keys {
   286  		ch.WriteString(key)
   287  		ch.WriteRune(':')
   288  		ch.WriteString(cm[key])
   289  		ch.WriteRune('\n')
   290  	}
   291  
   292  	return strings.TrimSuffix(string(ch.Bytes()), "\n")
   293  }
   294  
   295  func createAuthorizationHeader(accountName string, accountKey []byte, canonicalizedString string, keyType SharedKeyType) string {
   296  	h := hmac.New(sha256.New, accountKey)
   297  	h.Write([]byte(canonicalizedString))
   298  	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
   299  	var key string
   300  	switch keyType {
   301  	case SharedKey, SharedKeyForTable:
   302  		key = "SharedKey"
   303  	case SharedKeyLite, SharedKeyLiteForTable:
   304  		key = "SharedKeyLite"
   305  	}
   306  	return fmt.Sprintf("%s %s:%s", key, getCanonicalizedAccountName(accountName), signature)
   307  }
   308  

View as plain text