Blame vendor/github.com/Azure/go-autorest/autorest/authorization_storage.go

Packit 63bb0d
package autorest
Packit 63bb0d
Packit 63bb0d
// Copyright 2017 Microsoft Corporation
Packit 63bb0d
//
Packit 63bb0d
//  Licensed under the Apache License, Version 2.0 (the "License");
Packit 63bb0d
//  you may not use this file except in compliance with the License.
Packit 63bb0d
//  You may obtain a copy of the License at
Packit 63bb0d
//
Packit 63bb0d
//      http://www.apache.org/licenses/LICENSE-2.0
Packit 63bb0d
//
Packit 63bb0d
//  Unless required by applicable law or agreed to in writing, software
Packit 63bb0d
//  distributed under the License is distributed on an "AS IS" BASIS,
Packit 63bb0d
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Packit 63bb0d
//  See the License for the specific language governing permissions and
Packit 63bb0d
//  limitations under the License.
Packit 63bb0d
Packit 63bb0d
import (
Packit 63bb0d
	"bytes"
Packit 63bb0d
	"crypto/hmac"
Packit 63bb0d
	"crypto/sha256"
Packit 63bb0d
	"encoding/base64"
Packit 63bb0d
	"fmt"
Packit 63bb0d
	"net/http"
Packit 63bb0d
	"net/url"
Packit 63bb0d
	"sort"
Packit 63bb0d
	"strings"
Packit 63bb0d
	"time"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
// SharedKeyType defines the enumeration for the various shared key types.
Packit 63bb0d
// See https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key for details on the shared key types.
Packit 63bb0d
type SharedKeyType string
Packit 63bb0d
Packit 63bb0d
const (
Packit 63bb0d
	// SharedKey is used to authorize against blobs, files and queues services.
Packit 63bb0d
	SharedKey SharedKeyType = "sharedKey"
Packit 63bb0d
Packit 63bb0d
	// SharedKeyForTable is used to authorize against the table service.
Packit 63bb0d
	SharedKeyForTable SharedKeyType = "sharedKeyTable"
Packit 63bb0d
Packit 63bb0d
	// SharedKeyLite is used to authorize against blobs, files and queues services.  It's provided for
Packit 63bb0d
	// backwards compatibility with API versions before 2009-09-19.  Prefer SharedKey instead.
Packit 63bb0d
	SharedKeyLite SharedKeyType = "sharedKeyLite"
Packit 63bb0d
Packit 63bb0d
	// SharedKeyLiteForTable is used to authorize against the table service.  It's provided for
Packit 63bb0d
	// backwards compatibility with older table API versions.  Prefer SharedKeyForTable instead.
Packit 63bb0d
	SharedKeyLiteForTable SharedKeyType = "sharedKeyLiteTable"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
const (
Packit 63bb0d
	headerAccept            = "Accept"
Packit 63bb0d
	headerAcceptCharset     = "Accept-Charset"
Packit 63bb0d
	headerContentEncoding   = "Content-Encoding"
Packit 63bb0d
	headerContentLength     = "Content-Length"
Packit 63bb0d
	headerContentMD5        = "Content-MD5"
Packit 63bb0d
	headerContentLanguage   = "Content-Language"
Packit 63bb0d
	headerIfModifiedSince   = "If-Modified-Since"
Packit 63bb0d
	headerIfMatch           = "If-Match"
Packit 63bb0d
	headerIfNoneMatch       = "If-None-Match"
Packit 63bb0d
	headerIfUnmodifiedSince = "If-Unmodified-Since"
Packit 63bb0d
	headerDate              = "Date"
Packit 63bb0d
	headerXMSDate           = "X-Ms-Date"
Packit 63bb0d
	headerXMSVersion        = "x-ms-version"
Packit 63bb0d
	headerRange             = "Range"
Packit 63bb0d
)
Packit 63bb0d
Packit 63bb0d
const storageEmulatorAccountName = "devstoreaccount1"
Packit 63bb0d
Packit 63bb0d
// SharedKeyAuthorizer implements an authorization for Shared Key
Packit 63bb0d
// this can be used for interaction with Blob, File and Queue Storage Endpoints
Packit 63bb0d
type SharedKeyAuthorizer struct {
Packit 63bb0d
	accountName string
Packit 63bb0d
	accountKey  []byte
Packit 63bb0d
	keyType     SharedKeyType
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// NewSharedKeyAuthorizer creates a SharedKeyAuthorizer using the provided credentials and shared key type.
Packit 63bb0d
func NewSharedKeyAuthorizer(accountName, accountKey string, keyType SharedKeyType) (*SharedKeyAuthorizer, error) {
Packit 63bb0d
	key, err := base64.StdEncoding.DecodeString(accountKey)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return nil, fmt.Errorf("malformed storage account key: %v", err)
Packit 63bb0d
	}
Packit 63bb0d
	return &SharedKeyAuthorizer{
Packit 63bb0d
		accountName: accountName,
Packit 63bb0d
		accountKey:  key,
Packit 63bb0d
		keyType:     keyType,
Packit 63bb0d
	}, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
Packit 63bb0d
// value is "<SharedKeyType> " followed by the computed key.
Packit 63bb0d
// This can be used for the Blob, Queue, and File Services
Packit 63bb0d
//
Packit 63bb0d
// from: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
Packit 63bb0d
// You may use Shared Key authorization to authorize a request made against the
Packit 63bb0d
// 2009-09-19 version and later of the Blob and Queue services,
Packit 63bb0d
// and version 2014-02-14 and later of the File services.
Packit 63bb0d
func (sk *SharedKeyAuthorizer) WithAuthorization() PrepareDecorator {
Packit 63bb0d
	return func(p Preparer) Preparer {
Packit 63bb0d
		return PreparerFunc(func(r *http.Request) (*http.Request, error) {
Packit 63bb0d
			r, err := p.Prepare(r)
Packit 63bb0d
			if err != nil {
Packit 63bb0d
				return r, err
Packit 63bb0d
			}
Packit 63bb0d
Packit 63bb0d
			sk, err := buildSharedKey(sk.accountName, sk.accountKey, r, sk.keyType)
Packit 63bb0d
			if err != nil {
Packit 63bb0d
				return r, err
Packit 63bb0d
			}
Packit 63bb0d
			return Prepare(r, WithHeader(headerAuthorization, sk))
Packit 63bb0d
		})
Packit 63bb0d
	}
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func buildSharedKey(accName string, accKey []byte, req *http.Request, keyType SharedKeyType) (string, error) {
Packit 63bb0d
	canRes, err := buildCanonicalizedResource(accName, req.URL.String(), keyType)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return "", err
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if req.Header == nil {
Packit 63bb0d
		req.Header = http.Header{}
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// ensure date is set
Packit 63bb0d
	if req.Header.Get(headerDate) == "" && req.Header.Get(headerXMSDate) == "" {
Packit 63bb0d
		date := time.Now().UTC().Format(http.TimeFormat)
Packit 63bb0d
		req.Header.Set(headerXMSDate, date)
Packit 63bb0d
	}
Packit 63bb0d
	canString, err := buildCanonicalizedString(req.Method, req.Header, canRes, keyType)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return "", err
Packit 63bb0d
	}
Packit 63bb0d
	return createAuthorizationHeader(accName, accKey, canString, keyType), nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func buildCanonicalizedResource(accountName, uri string, keyType SharedKeyType) (string, error) {
Packit 63bb0d
	errMsg := "buildCanonicalizedResource error: %s"
Packit 63bb0d
	u, err := url.Parse(uri)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return "", fmt.Errorf(errMsg, err.Error())
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	cr := bytes.NewBufferString("")
Packit 63bb0d
	if accountName != storageEmulatorAccountName {
Packit 63bb0d
		cr.WriteString("/")
Packit 63bb0d
		cr.WriteString(getCanonicalizedAccountName(accountName))
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if len(u.Path) > 0 {
Packit 63bb0d
		// Any portion of the CanonicalizedResource string that is derived from
Packit 63bb0d
		// the resource's URI should be encoded exactly as it is in the URI.
Packit 63bb0d
		// -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
Packit 63bb0d
		cr.WriteString(u.EscapedPath())
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	params, err := url.ParseQuery(u.RawQuery)
Packit 63bb0d
	if err != nil {
Packit 63bb0d
		return "", fmt.Errorf(errMsg, err.Error())
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	// See https://github.com/Azure/azure-storage-net/blob/master/Lib/Common/Core/Util/AuthenticationUtility.cs#L277
Packit 63bb0d
	if keyType == SharedKey {
Packit 63bb0d
		if len(params) > 0 {
Packit 63bb0d
			cr.WriteString("\n")
Packit 63bb0d
Packit 63bb0d
			keys := []string{}
Packit 63bb0d
			for key := range params {
Packit 63bb0d
				keys = append(keys, key)
Packit 63bb0d
			}
Packit 63bb0d
			sort.Strings(keys)
Packit 63bb0d
Packit 63bb0d
			completeParams := []string{}
Packit 63bb0d
			for _, key := range keys {
Packit 63bb0d
				if len(params[key]) > 1 {
Packit 63bb0d
					sort.Strings(params[key])
Packit 63bb0d
				}
Packit 63bb0d
Packit 63bb0d
				completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ",")))
Packit 63bb0d
			}
Packit 63bb0d
			cr.WriteString(strings.Join(completeParams, "\n"))
Packit 63bb0d
		}
Packit 63bb0d
	} else {
Packit 63bb0d
		// search for "comp" parameter, if exists then add it to canonicalizedresource
Packit 63bb0d
		if v, ok := params["comp"]; ok {
Packit 63bb0d
			cr.WriteString("?comp=" + v[0])
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return string(cr.Bytes()), nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func getCanonicalizedAccountName(accountName string) string {
Packit 63bb0d
	// since we may be trying to access a secondary storage account, we need to
Packit 63bb0d
	// remove the -secondary part of the storage name
Packit 63bb0d
	return strings.TrimSuffix(accountName, "-secondary")
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func buildCanonicalizedString(verb string, headers http.Header, canonicalizedResource string, keyType SharedKeyType) (string, error) {
Packit 63bb0d
	contentLength := headers.Get(headerContentLength)
Packit 63bb0d
	if contentLength == "0" {
Packit 63bb0d
		contentLength = ""
Packit 63bb0d
	}
Packit 63bb0d
	date := headers.Get(headerDate)
Packit 63bb0d
	if v := headers.Get(headerXMSDate); v != "" {
Packit 63bb0d
		if keyType == SharedKey || keyType == SharedKeyLite {
Packit 63bb0d
			date = ""
Packit 63bb0d
		} else {
Packit 63bb0d
			date = v
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
	var canString string
Packit 63bb0d
	switch keyType {
Packit 63bb0d
	case SharedKey:
Packit 63bb0d
		canString = strings.Join([]string{
Packit 63bb0d
			verb,
Packit 63bb0d
			headers.Get(headerContentEncoding),
Packit 63bb0d
			headers.Get(headerContentLanguage),
Packit 63bb0d
			contentLength,
Packit 63bb0d
			headers.Get(headerContentMD5),
Packit 63bb0d
			headers.Get(headerContentType),
Packit 63bb0d
			date,
Packit 63bb0d
			headers.Get(headerIfModifiedSince),
Packit 63bb0d
			headers.Get(headerIfMatch),
Packit 63bb0d
			headers.Get(headerIfNoneMatch),
Packit 63bb0d
			headers.Get(headerIfUnmodifiedSince),
Packit 63bb0d
			headers.Get(headerRange),
Packit 63bb0d
			buildCanonicalizedHeader(headers),
Packit 63bb0d
			canonicalizedResource,
Packit 63bb0d
		}, "\n")
Packit 63bb0d
	case SharedKeyForTable:
Packit 63bb0d
		canString = strings.Join([]string{
Packit 63bb0d
			verb,
Packit 63bb0d
			headers.Get(headerContentMD5),
Packit 63bb0d
			headers.Get(headerContentType),
Packit 63bb0d
			date,
Packit 63bb0d
			canonicalizedResource,
Packit 63bb0d
		}, "\n")
Packit 63bb0d
	case SharedKeyLite:
Packit 63bb0d
		canString = strings.Join([]string{
Packit 63bb0d
			verb,
Packit 63bb0d
			headers.Get(headerContentMD5),
Packit 63bb0d
			headers.Get(headerContentType),
Packit 63bb0d
			date,
Packit 63bb0d
			buildCanonicalizedHeader(headers),
Packit 63bb0d
			canonicalizedResource,
Packit 63bb0d
		}, "\n")
Packit 63bb0d
	case SharedKeyLiteForTable:
Packit 63bb0d
		canString = strings.Join([]string{
Packit 63bb0d
			date,
Packit 63bb0d
			canonicalizedResource,
Packit 63bb0d
		}, "\n")
Packit 63bb0d
	default:
Packit 63bb0d
		return "", fmt.Errorf("key type '%s' is not supported", keyType)
Packit 63bb0d
	}
Packit 63bb0d
	return canString, nil
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func buildCanonicalizedHeader(headers http.Header) string {
Packit 63bb0d
	cm := make(map[string]string)
Packit 63bb0d
Packit 63bb0d
	for k := range headers {
Packit 63bb0d
		headerName := strings.TrimSpace(strings.ToLower(k))
Packit 63bb0d
		if strings.HasPrefix(headerName, "x-ms-") {
Packit 63bb0d
			cm[headerName] = headers.Get(k)
Packit 63bb0d
		}
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	if len(cm) == 0 {
Packit 63bb0d
		return ""
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	keys := []string{}
Packit 63bb0d
	for key := range cm {
Packit 63bb0d
		keys = append(keys, key)
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	sort.Strings(keys)
Packit 63bb0d
Packit 63bb0d
	ch := bytes.NewBufferString("")
Packit 63bb0d
Packit 63bb0d
	for _, key := range keys {
Packit 63bb0d
		ch.WriteString(key)
Packit 63bb0d
		ch.WriteRune(':')
Packit 63bb0d
		ch.WriteString(cm[key])
Packit 63bb0d
		ch.WriteRune('\n')
Packit 63bb0d
	}
Packit 63bb0d
Packit 63bb0d
	return strings.TrimSuffix(string(ch.Bytes()), "\n")
Packit 63bb0d
}
Packit 63bb0d
Packit 63bb0d
func createAuthorizationHeader(accountName string, accountKey []byte, canonicalizedString string, keyType SharedKeyType) string {
Packit 63bb0d
	h := hmac.New(sha256.New, accountKey)
Packit 63bb0d
	h.Write([]byte(canonicalizedString))
Packit 63bb0d
	signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
Packit 63bb0d
	var key string
Packit 63bb0d
	switch keyType {
Packit 63bb0d
	case SharedKey, SharedKeyForTable:
Packit 63bb0d
		key = "SharedKey"
Packit 63bb0d
	case SharedKeyLite, SharedKeyLiteForTable:
Packit 63bb0d
		key = "SharedKeyLite"
Packit 63bb0d
	}
Packit 63bb0d
	return fmt.Sprintf("%s %s:%s", key, getCanonicalizedAccountName(accountName), signature)
Packit 63bb0d
}