Skip to content

Commit

Permalink
Merge pull request #3802 from terraform-providers/f/storage-sdk
Browse files Browse the repository at this point in the history
New Resource: `azurerm_storage_share_directory`
  • Loading branch information
tombuildsstuff authored Jul 9, 2019
2 parents 6b1dba4 + 472d6eb commit 3c14444
Show file tree
Hide file tree
Showing 275 changed files with 22,541 additions and 3,282 deletions.
4 changes: 4 additions & 0 deletions azurerm/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ import (
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/servicebus"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/servicefabric"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/signalr"
intStor "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/storage"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/trafficmanager"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils"
"github.com/terraform-providers/terraform-provider-azurerm/version"
Expand Down Expand Up @@ -149,6 +150,7 @@ type ArmClient struct {
servicebus *servicebus.Client
serviceFabric *servicefabric.Client
signalr *signalr.Client
storage *intStor.Client
trafficManager *trafficmanager.Client

// TODO: refactor
Expand Down Expand Up @@ -1096,6 +1098,8 @@ func (c *ArmClient) registerStorageClients(endpoint, subscriptionId string, auth
usageClient := storage.NewUsagesClientWithBaseURI(endpoint, subscriptionId)
c.configureClient(&usageClient.Client, auth)
c.storageUsageClient = usageClient

c.storage = intStor.BuildClient(accountsClient)
}

func (c *ArmClient) registerStreamAnalyticsClients(endpoint, subscriptionId string, auth autorest.Authorizer) {
Expand Down
24 changes: 24 additions & 0 deletions azurerm/helpers/validate/storage.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package validate

import (
"fmt"
"regexp"
"strings"
)

func StorageShareDirectoryName(v interface{}, k string) (warnings []string, errors []error) {
value := v.(string)

// File share names can contain only lowercase letters, numbers, and hyphens,
// and must begin and end with a letter or a number.
if !regexp.MustCompile(`^[a-z0-9][a-z0-9-]+[a-z0-9]$`).MatchString(value) {
errors = append(errors, fmt.Errorf("%s must contain only lowercase alphanumeric characters, numbers and hyphens. It must start and end with a letter and end only with a number or letter", k))
}

// The name cannot contain two consecutive hyphens.
if strings.Contains(value, "--") {
errors = append(errors, fmt.Errorf("%s cannot contain two concecutive hyphens", k))
}

return warnings, errors
}
53 changes: 53 additions & 0 deletions azurerm/helpers/validate/storage_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package validate

import "testing"

func TestValidateStorageShareDirectoryName(t *testing.T) {
testCases := []struct {
Input string
Expected bool
}{
{
Input: "",
Expected: false,
},
{
Input: "abc123",
Expected: true,
},
{
Input: "123abc",
Expected: true,
},
{
Input: "123-abc",
Expected: true,
},
{
Input: "-123-abc",
Expected: false,
},
{
Input: "123-abc-",
Expected: false,
},
{
Input: "123--abc",
Expected: false,
},
}

for _, v := range testCases {
t.Logf("[DEBUG] Test Input %q", v.Input)

warnings, errors := StorageShareDirectoryName(v.Input, "name")
if len(warnings) != 0 {
t.Fatalf("Expected no warnings but got %d", len(warnings))
}

result := len(errors) == 0
if result != v.Expected {
t.Fatalf("Expected the result to be %t but got %t (and %d errors)", v.Expected, result, len(errors))
}
}
}
90 changes: 90 additions & 0 deletions azurerm/internal/authorizers/authorizer_shared_key_lite.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package authorizers

import (
"net/http"
"strings"

"github.com/Azure/go-autorest/autorest"
)

// TODO: switch to using the version from github.com/Azure/go-autorest
// once https://github.com/Azure/go-autorest/pull/416 has been merged

// SharedKeyLiteAuthorizer implements an authorization for Shared Key Lite
// this can be used for interaction with Blob, File and Queue Storage Endpoints
type SharedKeyLiteAuthorizer struct {
storageAccountName string
storageAccountKey string
}

// NewSharedKeyLiteAuthorizer crates a SharedKeyLiteAuthorizer using the given credentials
func NewSharedKeyLiteAuthorizer(accountName, accountKey string) *SharedKeyLiteAuthorizer {
return &SharedKeyLiteAuthorizer{
storageAccountName: accountName,
storageAccountKey: accountKey,
}
}

// WithAuthorization returns a PrepareDecorator that adds an HTTP Authorization header whose
// value is "SharedKeyLite " followed by the computed key.
// This can be used for the Blob, Queue, and File Services
//
// from: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
// You may use Shared Key Lite authorization to authorize a request made against the
// 2009-09-19 version and later of the Blob and Queue services,
// and version 2014-02-14 and later of the File services.
func (skl *SharedKeyLiteAuthorizer) WithAuthorization() autorest.PrepareDecorator {
return func(p autorest.Preparer) autorest.Preparer {
return autorest.PreparerFunc(func(r *http.Request) (*http.Request, error) {
r, err := p.Prepare(r)
if err != nil {
return r, err
}

key, err := buildSharedKeyLite(skl.storageAccountName, skl.storageAccountKey, r)
if err != nil {
return r, err
}

sharedKeyHeader := formatSharedKeyLiteAuthorizationHeader(skl.storageAccountName, *key)
return autorest.Prepare(r, autorest.WithHeader(HeaderAuthorization, sharedKeyHeader))
})
}
}
func buildSharedKeyLite(accountName, storageAccountKey string, r *http.Request) (*string, error) {
// first ensure the relevant headers are configured
prepareHeadersForRequest(r)

sharedKey, err := computeSharedKeyLite(r.Method, r.URL.String(), accountName, r.Header)
if err != nil {
return nil, err
}

// we then need to HMAC that value
hmacdValue := hmacValue(storageAccountKey, *sharedKey)
return &hmacdValue, nil
}

// computeSharedKeyLite computes the Shared Key Lite required for Storage Authentication
// NOTE: this function assumes that the `x-ms-date` field is set
func computeSharedKeyLite(verb, url string, accountName string, headers http.Header) (*string, error) {
canonicalizedResource, err := buildCanonicalizedResource(url, accountName)
if err != nil {
return nil, err
}

canonicalizedHeaders := buildCanonicalizedHeader(headers)
canonicalizedString := buildCanonicalizedStringForSharedKeyLite(verb, headers, canonicalizedHeaders, *canonicalizedResource)
return &canonicalizedString, nil
}

func buildCanonicalizedStringForSharedKeyLite(verb string, headers http.Header, canonicalizedHeaders, canonicalizedResource string) string {
return strings.Join([]string{
verb,
headers.Get(HeaderContentMD5), // TODO: this appears to always be empty?
headers.Get(HeaderContentType),
"", // date should be nil, apparently :shrug:
canonicalizedHeaders,
canonicalizedResource,
}, "\n")
}
36 changes: 36 additions & 0 deletions azurerm/internal/authorizers/authorizer_shared_key_lite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package authorizers

import (
"testing"
)

func TestBuildCanonicalizedStringForSharedKeyLite(t *testing.T) {
testData := []struct {
name string
headers map[string][]string
canonicalizedHeaders string
canonicalizedResource string
verb string
expected string
}{
{
name: "completed",
verb: "NOM",
headers: map[string][]string{
"Content-MD5": {"abc123"},
"Content-Type": {"vnd/panda-pops+v1"},
},
canonicalizedHeaders: "all-the-headers",
canonicalizedResource: "all-the-resources",
expected: "NOM\n\nvnd/panda-pops+v1\n\nall-the-headers\nall-the-resources",
},
}

for _, test := range testData {
t.Logf("Test: %q", test.name)
actual := buildCanonicalizedStringForSharedKeyLite(test.verb, test.headers, test.canonicalizedHeaders, test.canonicalizedResource)
if actual != test.expected {
t.Fatalf("Expected %q but got %q", test.expected, actual)
}
}
}
19 changes: 19 additions & 0 deletions azurerm/internal/authorizers/consts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package authorizers

var (
HeaderAuthorization = "Authorization"
HeaderContentLength = "Content-Length"
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"
HeaderMSDate = "X-Ms-Date"
HeaderRange = "Range"

StorageEmulatorAccountName = "devstoreaccount1"
StorageEmulatorAccountKey = "Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw=="
)
120 changes: 120 additions & 0 deletions azurerm/internal/authorizers/helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package authorizers

import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"
"net/url"
"sort"
"strings"
"time"
)

// buildCanonicalizedHeader builds the Canonicalized Header required to sign Storage Requests
func buildCanonicalizedHeader(headers http.Header) 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[0]
}
}

if len(cm) == 0 {
return ""
}

var 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(ch.String(), "\n")
}

// buildCanonicalizedResource builds the Canonical Resource required for to sign Storage Account requests
func buildCanonicalizedResource(uri, accountName string) (*string, error) {
u, err := url.Parse(uri)
if err != nil {
return nil, err
}

cr := bytes.NewBufferString("")
if accountName != StorageEmulatorAccountName {
cr.WriteString("/")
cr.WriteString(primaryStorageAccountName(accountName))
}

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())
}

// TODO: replace this with less of a hack
if comp := u.Query().Get("comp"); comp != "" {
cr.WriteString(fmt.Sprintf("?comp=%s", comp))
}

out := cr.String()
return &out, nil
}

func formatSharedKeyLiteAuthorizationHeader(accountName, key string) string {
canonicalizedAccountName := primaryStorageAccountName(accountName)
return fmt.Sprintf("SharedKeyLite %s:%s", canonicalizedAccountName, key)
}

// hmacValue base-64 decodes the storageAccountKey, then signs the string with it
// as outlined here: https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
func hmacValue(storageAccountKey, canonicalizedString string) string {
key, err := base64.StdEncoding.DecodeString(storageAccountKey)
if err != nil {
return ""
}

encr := hmac.New(sha256.New, key)
_, _ = encr.Write([]byte(canonicalizedString))
return base64.StdEncoding.EncodeToString(encr.Sum(nil))
}

// prepareHeadersForRequest prepares a request so that it can be signed
// by ensuring the `date` and `x-ms-date` headers are set
func prepareHeadersForRequest(r *http.Request) {
if r.Header == nil {
r.Header = http.Header{}
}

date := time.Now().UTC().Format(http.TimeFormat)

// a date must be set, X-Ms-Date should be used when both are set; but let's set both for completeness
r.Header.Set("date", date)
r.Header.Set("x-ms-date", date)
}

// primaryStorageAccountName returns the name of the primary for a given Storage Account
func primaryStorageAccountName(input string) string {
// from https://docs.microsoft.com/en-us/rest/api/storageservices/authorize-with-shared-key
// If you are accessing the secondary location in a storage account for which
// read-access geo-replication (RA-GRS) is enabled, do not include the
// -secondary designation in the authorization header.
// For authorization purposes, the account name is always the name of the primary location,
// even for secondary access.
return strings.TrimSuffix(input, "-secondary")
}
Loading

0 comments on commit 3c14444

Please sign in to comment.