c76ad94492
This commit adds a build step to travis to auto-delete unstable archives older than 14 days (our regular release schedule) from Azure via ci.go purge. The commit also pulls in the latest Azure storage code, also switching over from the old import path (github.com/Azure/azure-sdk-for-go) to the new split one (github.com/Azure/azure-storage-go).
224 lines
5.9 KiB
Go
224 lines
5.9 KiB
Go
// Package storage provides clients for Microsoft Azure Storage Services.
|
|
package storage
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/url"
|
|
"sort"
|
|
"strings"
|
|
)
|
|
|
|
// See: https://docs.microsoft.com/rest/api/storageservices/fileservices/authentication-for-the-azure-storage-services
|
|
|
|
type authentication string
|
|
|
|
const (
|
|
sharedKey authentication = "sharedKey"
|
|
sharedKeyForTable authentication = "sharedKeyTable"
|
|
sharedKeyLite authentication = "sharedKeyLite"
|
|
sharedKeyLiteForTable authentication = "sharedKeyLiteTable"
|
|
|
|
// headers
|
|
headerAuthorization = "Authorization"
|
|
headerContentLength = "Content-Length"
|
|
headerDate = "Date"
|
|
headerXmsDate = "x-ms-date"
|
|
headerXmsVersion = "x-ms-version"
|
|
headerContentEncoding = "Content-Encoding"
|
|
headerContentLanguage = "Content-Language"
|
|
headerContentType = "Content-Type"
|
|
headerContentMD5 = "Content-MD5"
|
|
headerIfModifiedSince = "If-Modified-Since"
|
|
headerIfMatch = "If-Match"
|
|
headerIfNoneMatch = "If-None-Match"
|
|
headerIfUnmodifiedSince = "If-Unmodified-Since"
|
|
headerRange = "Range"
|
|
)
|
|
|
|
func (c *Client) addAuthorizationHeader(verb, url string, headers map[string]string, auth authentication) (map[string]string, error) {
|
|
authHeader, err := c.getSharedKey(verb, url, headers, auth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
headers[headerAuthorization] = authHeader
|
|
return headers, nil
|
|
}
|
|
|
|
func (c *Client) getSharedKey(verb, url string, headers map[string]string, auth authentication) (string, error) {
|
|
canRes, err := c.buildCanonicalizedResource(url, auth)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
canString, err := buildCanonicalizedString(verb, headers, canRes, auth)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return c.createAuthorizationHeader(canString, auth), nil
|
|
}
|
|
|
|
func (c *Client) buildCanonicalizedResource(uri string, auth authentication) (string, error) {
|
|
errMsg := "buildCanonicalizedResource error: %s"
|
|
u, err := url.Parse(uri)
|
|
if err != nil {
|
|
return "", fmt.Errorf(errMsg, err.Error())
|
|
}
|
|
|
|
cr := bytes.NewBufferString("/")
|
|
cr.WriteString(c.getCanonicalizedAccountName())
|
|
|
|
if len(u.Path) > 0 {
|
|
// Any portion of the CanonicalizedResource string that is derived from
|
|
// the resource's URI should be encoded exactly as it is in the URI.
|
|
// -- https://msdn.microsoft.com/en-gb/library/azure/dd179428.aspx
|
|
cr.WriteString(u.EscapedPath())
|
|
}
|
|
|
|
params, err := url.ParseQuery(u.RawQuery)
|
|
if err != nil {
|
|
return "", fmt.Errorf(errMsg, err.Error())
|
|
}
|
|
|
|
// See https://github.com/Azure/azure-storage-net/blob/master/Lib/Common/Core/Util/AuthenticationUtility.cs#L277
|
|
if auth == sharedKey {
|
|
if len(params) > 0 {
|
|
cr.WriteString("\n")
|
|
|
|
keys := []string{}
|
|
for key := range params {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
completeParams := []string{}
|
|
for _, key := range keys {
|
|
if len(params[key]) > 1 {
|
|
sort.Strings(params[key])
|
|
}
|
|
|
|
completeParams = append(completeParams, fmt.Sprintf("%s:%s", key, strings.Join(params[key], ",")))
|
|
}
|
|
cr.WriteString(strings.Join(completeParams, "\n"))
|
|
}
|
|
} else {
|
|
// search for "comp" parameter, if exists then add it to canonicalizedresource
|
|
if v, ok := params["comp"]; ok {
|
|
cr.WriteString("?comp=" + v[0])
|
|
}
|
|
}
|
|
|
|
return string(cr.Bytes()), nil
|
|
}
|
|
|
|
func (c *Client) getCanonicalizedAccountName() string {
|
|
// since we may be trying to access a secondary storage account, we need to
|
|
// remove the -secondary part of the storage name
|
|
return strings.TrimSuffix(c.accountName, "-secondary")
|
|
}
|
|
|
|
func buildCanonicalizedString(verb string, headers map[string]string, canonicalizedResource string, auth authentication) (string, error) {
|
|
contentLength := headers[headerContentLength]
|
|
if contentLength == "0" {
|
|
contentLength = ""
|
|
}
|
|
date := headers[headerDate]
|
|
if v, ok := headers[headerXmsDate]; ok {
|
|
if auth == sharedKey || auth == sharedKeyLite {
|
|
date = ""
|
|
} else {
|
|
date = v
|
|
}
|
|
}
|
|
var canString string
|
|
switch auth {
|
|
case sharedKey:
|
|
canString = strings.Join([]string{
|
|
verb,
|
|
headers[headerContentEncoding],
|
|
headers[headerContentLanguage],
|
|
contentLength,
|
|
headers[headerContentMD5],
|
|
headers[headerContentType],
|
|
date,
|
|
headers[headerIfModifiedSince],
|
|
headers[headerIfMatch],
|
|
headers[headerIfNoneMatch],
|
|
headers[headerIfUnmodifiedSince],
|
|
headers[headerRange],
|
|
buildCanonicalizedHeader(headers),
|
|
canonicalizedResource,
|
|
}, "\n")
|
|
case sharedKeyForTable:
|
|
canString = strings.Join([]string{
|
|
verb,
|
|
headers[headerContentMD5],
|
|
headers[headerContentType],
|
|
date,
|
|
canonicalizedResource,
|
|
}, "\n")
|
|
case sharedKeyLite:
|
|
canString = strings.Join([]string{
|
|
verb,
|
|
headers[headerContentMD5],
|
|
headers[headerContentType],
|
|
date,
|
|
buildCanonicalizedHeader(headers),
|
|
canonicalizedResource,
|
|
}, "\n")
|
|
case sharedKeyLiteForTable:
|
|
canString = strings.Join([]string{
|
|
date,
|
|
canonicalizedResource,
|
|
}, "\n")
|
|
default:
|
|
return "", fmt.Errorf("%s authentication is not supported yet", auth)
|
|
}
|
|
return canString, nil
|
|
}
|
|
|
|
func buildCanonicalizedHeader(headers map[string]string) string {
|
|
cm := make(map[string]string)
|
|
|
|
for k, v := range headers {
|
|
headerName := strings.TrimSpace(strings.ToLower(k))
|
|
if strings.HasPrefix(headerName, "x-ms-") {
|
|
cm[headerName] = v
|
|
}
|
|
}
|
|
|
|
if len(cm) == 0 {
|
|
return ""
|
|
}
|
|
|
|
keys := []string{}
|
|
for key := range cm {
|
|
keys = append(keys, key)
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
ch := bytes.NewBufferString("")
|
|
|
|
for _, key := range keys {
|
|
ch.WriteString(key)
|
|
ch.WriteRune(':')
|
|
ch.WriteString(cm[key])
|
|
ch.WriteRune('\n')
|
|
}
|
|
|
|
return strings.TrimSuffix(string(ch.Bytes()), "\n")
|
|
}
|
|
|
|
func (c *Client) createAuthorizationHeader(canonicalizedString string, auth authentication) string {
|
|
signature := c.computeHmac256(canonicalizedString)
|
|
var key string
|
|
switch auth {
|
|
case sharedKey, sharedKeyForTable:
|
|
key = "SharedKey"
|
|
case sharedKeyLite, sharedKeyLiteForTable:
|
|
key = "SharedKeyLite"
|
|
}
|
|
return fmt.Sprintf("%s %s:%s", key, c.getCanonicalizedAccountName(), signature)
|
|
}
|