99 lines
3.0 KiB
Go
99 lines
3.0 KiB
Go
|
package storage
|
||
|
|
||
|
import (
|
||
|
"crypto/hmac"
|
||
|
"crypto/sha256"
|
||
|
"encoding/base64"
|
||
|
"fmt"
|
||
|
"net/url"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
connStringAccountKeyKey = "AccountKey"
|
||
|
connStringAccountNameKey = "AccountName"
|
||
|
)
|
||
|
|
||
|
// ComputeSASToken computes the SAS Token for a Storage Account based on the
|
||
|
// access key & given permissions
|
||
|
func ComputeSASToken(accountName string,
|
||
|
accountKey string,
|
||
|
permissions string,
|
||
|
services string,
|
||
|
resourceTypes string,
|
||
|
start string,
|
||
|
expiry string,
|
||
|
signedProtocol string,
|
||
|
signedIp string, // nolint: unparam
|
||
|
signedVersion string, // nolint: unparam
|
||
|
) (string, error) {
|
||
|
|
||
|
// UTF-8 by default...
|
||
|
stringToSign := accountName + "\n"
|
||
|
stringToSign += permissions + "\n"
|
||
|
stringToSign += services + "\n"
|
||
|
stringToSign += resourceTypes + "\n"
|
||
|
stringToSign += start + "\n"
|
||
|
stringToSign += expiry + "\n"
|
||
|
stringToSign += signedIp + "\n"
|
||
|
stringToSign += signedProtocol + "\n"
|
||
|
stringToSign += signedVersion + "\n"
|
||
|
|
||
|
binaryKey, err := base64.StdEncoding.DecodeString(accountKey)
|
||
|
if err != nil {
|
||
|
return "", err
|
||
|
}
|
||
|
hasher := hmac.New(sha256.New, binaryKey)
|
||
|
hasher.Write([]byte(stringToSign))
|
||
|
signature := hasher.Sum(nil)
|
||
|
|
||
|
// Trial and error to determine which fields the Azure portal
|
||
|
// URL encodes for a query string and which it does not.
|
||
|
sasToken := "?sv=" + url.QueryEscape(signedVersion)
|
||
|
sasToken += "&ss=" + url.QueryEscape(services)
|
||
|
sasToken += "&srt=" + url.QueryEscape(resourceTypes)
|
||
|
sasToken += "&sp=" + url.QueryEscape(permissions)
|
||
|
sasToken += "&se=" + (expiry)
|
||
|
sasToken += "&st=" + (start)
|
||
|
sasToken += "&spr=" + (signedProtocol)
|
||
|
|
||
|
// this is consistent with how the Azure portal builds these.
|
||
|
if len(signedIp) > 0 {
|
||
|
sasToken += "&sip=" + signedIp
|
||
|
}
|
||
|
|
||
|
sasToken += "&sig=" + url.QueryEscape(base64.StdEncoding.EncodeToString(signature))
|
||
|
|
||
|
return sasToken, nil
|
||
|
}
|
||
|
|
||
|
// ParseStorageAccountConnectionString parses the Connection String for a Storage Account
|
||
|
func ParseStorageAccountConnectionString(connString string) (map[string]string, error) {
|
||
|
// This connection string was for a real storage account which has been deleted
|
||
|
// so its safe to include here for reference to understand the format.
|
||
|
// DefaultEndpointsProtocol=https;AccountName=azurermtestsa0;AccountKey=2vJrjEyL4re2nxCEg590wJUUC7PiqqrDHjAN5RU304FNUQieiEwS2bfp83O0v28iSfWjvYhkGmjYQAdd9x+6nw==;EndpointSuffix=core.windows.net
|
||
|
validKeys := map[string]bool{"DefaultEndpointsProtocol": true, "BlobEndpoint": true,
|
||
|
"AccountName": true, "AccountKey": true, "EndpointSuffix": true}
|
||
|
// The k-v pairs are separated with semi-colons
|
||
|
tokens := strings.Split(connString, ";")
|
||
|
|
||
|
kvp := make(map[string]string)
|
||
|
|
||
|
for _, atoken := range tokens {
|
||
|
// The individual k-v are separated by an equals sign.
|
||
|
kv := strings.SplitN(atoken, "=", 2)
|
||
|
key := kv[0]
|
||
|
val := kv[1]
|
||
|
if _, present := validKeys[key]; !present {
|
||
|
return nil, fmt.Errorf("[ERROR] Unknown Key: %s", key)
|
||
|
}
|
||
|
kvp[key] = val
|
||
|
}
|
||
|
|
||
|
if _, present := kvp[connStringAccountKeyKey]; !present {
|
||
|
return nil, fmt.Errorf("[ERROR] Storage Account Key not found in connection string: %s", connString)
|
||
|
}
|
||
|
|
||
|
return kvp, nil
|
||
|
}
|