backend/azurerm: support for authenticating via SAS Tokens (#19440)
* adding acceptance tests for msi auth * including the resource group name in the tests * backend/azurerm: support for authenticating using a SAS Token * resolving merge conflicts * moving the defer to prior to the error
This commit is contained in:
parent
a0e5ebf3b3
commit
96b1c951fa
|
@ -3,7 +3,10 @@ package azure
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
|
||||
|
@ -25,6 +28,7 @@ type ArmClient struct {
|
|||
environment azure.Environment
|
||||
resourceGroupName string
|
||||
storageAccountName string
|
||||
sasToken string
|
||||
}
|
||||
|
||||
func buildArmClient(config BackendConfig) (*ArmClient, error) {
|
||||
|
@ -44,6 +48,12 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) {
|
|||
return &client, nil
|
||||
}
|
||||
|
||||
// likewise with a SAS token
|
||||
if config.SasToken != "" {
|
||||
client.sasToken = config.SasToken
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
builder := authentication.Builder{
|
||||
ClientID: config.ClientID,
|
||||
ClientSecret: config.ClientSecret,
|
||||
|
@ -85,6 +95,7 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) {
|
|||
|
||||
func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClient, error) {
|
||||
if c.accessKey != "" {
|
||||
log.Printf("Building the Blob Client from an Access Token")
|
||||
storageClient, err := storage.NewBasicClientOnSovereignCloud(c.storageAccountName, c.accessKey, c.environment)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", c.storageAccountName, err)
|
||||
|
@ -93,6 +104,20 @@ func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClien
|
|||
return &client, nil
|
||||
}
|
||||
|
||||
if c.sasToken != "" {
|
||||
log.Printf("Building the Blob Client from a SAS Token")
|
||||
token := strings.TrimPrefix(c.sasToken, "?")
|
||||
uri, err := url.ParseQuery(token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error parsing SAS Token: %+v", err)
|
||||
}
|
||||
|
||||
storageClient := storage.NewAccountSASClient(c.storageAccountName, uri, c.environment)
|
||||
client := storageClient.GetBlobService()
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
log.Printf("Building the Blob Client from an Access Token (using user credentials)")
|
||||
keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err)
|
||||
|
|
|
@ -44,6 +44,13 @@ func New() backend.Backend {
|
|||
DefaultFunc: schema.EnvDefaultFunc("ARM_ACCESS_KEY", ""),
|
||||
},
|
||||
|
||||
"sas_token": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Description: "A SAS Token used to interact with the Blob Storage Account.",
|
||||
DefaultFunc: schema.EnvDefaultFunc("ARM_SAS_TOKEN", ""),
|
||||
},
|
||||
|
||||
"resource_group_name": {
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
|
@ -122,6 +129,7 @@ type BackendConfig struct {
|
|||
Environment string
|
||||
MsiEndpoint string
|
||||
ResourceGroupName string
|
||||
SasToken string
|
||||
SubscriptionID string
|
||||
TenantID string
|
||||
UseMsi bool
|
||||
|
@ -145,6 +153,7 @@ func (b *Backend) configure(ctx context.Context) error {
|
|||
Environment: data.Get("environment").(string),
|
||||
MsiEndpoint: data.Get("msi_endpoint").(string),
|
||||
ResourceGroupName: data.Get("resource_group_name").(string),
|
||||
SasToken: data.Get("sas_token").(string),
|
||||
StorageAccountName: data.Get("storage_account_name").(string),
|
||||
SubscriptionID: data.Get("arm_subscription_id").(string),
|
||||
TenantID: data.Get("arm_tenant_id").(string),
|
||||
|
@ -156,8 +165,8 @@ func (b *Backend) configure(ctx context.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if config.AccessKey == "" && config.ResourceGroupName == "" {
|
||||
return fmt.Errorf("Either an Access Key or the Resource Group for the Storage Account must be specified")
|
||||
if config.AccessKey == "" && config.SasToken == "" && config.ResourceGroupName == "" {
|
||||
return fmt.Errorf("Either an Access Key / SAS Token or the Resource Group for the Storage Account must be specified")
|
||||
}
|
||||
|
||||
b.armClient = armClient
|
||||
|
|
|
@ -43,17 +43,18 @@ func TestBackendAccessKeyBasic(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"access_key": res.storageAccountAccessKey,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStates(t, b)
|
||||
|
@ -67,11 +68,10 @@ func TestBackendManagedServiceIdentityBasic(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
|
@ -87,6 +87,35 @@ func TestBackendManagedServiceIdentityBasic(t *testing.T) {
|
|||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestBackendSASTokenBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
armClient := buildTestClient(t, res)
|
||||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
|
||||
sasToken, err := buildSasToken(res.storageAccountName, res.storageAccountAccessKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Error building SAS Token: %+v", err)
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"sas_token": *sasToken,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStates(t, b)
|
||||
}
|
||||
|
||||
func TestBackendServicePrincipalBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
|
@ -95,11 +124,10 @@ func TestBackendServicePrincipalBasic(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
|
@ -124,17 +152,17 @@ func TestBackendAccessKeyLocked(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"access_key": res.storageAccountAccessKey,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
|
@ -142,6 +170,7 @@ func TestBackendAccessKeyLocked(t *testing.T) {
|
|||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"access_key": res.storageAccountAccessKey,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
})).(*Backend)
|
||||
|
||||
backend.TestBackendStateLocks(t, b1, b2)
|
||||
|
@ -156,11 +185,10 @@ func TestBackendServicePrincipalLocked(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
|
|
|
@ -24,17 +24,17 @@ func TestRemoteClientAccessKeyBasic(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"access_key": res.storageAccountAccessKey,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
})).(*Backend)
|
||||
|
||||
state, err := b.StateMgr(backend.DefaultStateName)
|
||||
|
@ -53,11 +53,10 @@ func TestRemoteClientManagedServiceIdentityBasic(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
|
@ -78,6 +77,40 @@ func TestRemoteClientManagedServiceIdentityBasic(t *testing.T) {
|
|||
remote.TestClient(t, state.(*remote.State).Client)
|
||||
}
|
||||
|
||||
func TestRemoteClientSasTokenBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
res := testResourceNames(rs, "testState")
|
||||
armClient := buildTestClient(t, res)
|
||||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
|
||||
sasToken, err := buildSasToken(res.storageAccountName, res.storageAccountAccessKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Error building SAS Token: %+v", err)
|
||||
}
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"sas_token": *sasToken,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
})).(*Backend)
|
||||
|
||||
state, err := b.StateMgr(backend.DefaultStateName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
remote.TestClient(t, state.(*remote.State).Client)
|
||||
}
|
||||
|
||||
func TestRemoteClientServicePrincipalBasic(t *testing.T) {
|
||||
testAccAzureBackend(t)
|
||||
rs := acctest.RandString(4)
|
||||
|
@ -86,11 +119,10 @@ func TestRemoteClientServicePrincipalBasic(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
|
@ -120,17 +152,17 @@ func TestRemoteClientAccessKeyLocks(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"access_key": res.storageAccountAccessKey,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
})).(*Backend)
|
||||
|
||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
|
@ -138,6 +170,7 @@ func TestRemoteClientAccessKeyLocks(t *testing.T) {
|
|||
"container_name": res.storageContainerName,
|
||||
"key": res.storageKeyName,
|
||||
"access_key": res.storageAccountAccessKey,
|
||||
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||
})).(*Backend)
|
||||
|
||||
s1, err := b1.StateMgr(backend.DefaultStateName)
|
||||
|
@ -161,11 +194,10 @@ func TestRemoteClientServicePrincipalLocks(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||
"storage_account_name": res.storageAccountName,
|
||||
|
@ -212,11 +244,10 @@ func TestPutMaintainsMetaData(t *testing.T) {
|
|||
|
||||
ctx := context.TODO()
|
||||
err := armClient.buildTestResources(ctx, &res)
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
if err != nil {
|
||||
armClient.destroyTestResources(ctx, res)
|
||||
t.Fatalf("Error creating Test Resources: %q", err)
|
||||
}
|
||||
defer armClient.destroyTestResources(ctx, res)
|
||||
|
||||
headerName := "acceptancetest"
|
||||
expectedValue := "f3b56bad-33ad-4b93-a600-7a66e9cbd1eb"
|
||||
|
|
|
@ -7,10 +7,16 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
|
||||
armStorage "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/storage/mgmt/storage"
|
||||
"github.com/Azure/azure-sdk-for-go/storage"
|
||||
sasStorage "github.com/hashicorp/go-azure-helpers/storage"
|
||||
)
|
||||
|
||||
const (
|
||||
sasSignedVersion = "2017-07-29"
|
||||
)
|
||||
|
||||
// verify that we are doing ACC tests or the Azure tests specifically
|
||||
|
@ -80,6 +86,31 @@ func buildTestClient(t *testing.T, res resourceNames) *ArmClient {
|
|||
return armClient
|
||||
}
|
||||
|
||||
func buildSasToken(accountName, accessKey string) (*string, error) {
|
||||
// grant full access to Objects in the Blob Storage Account
|
||||
permissions := "rwdlacup" // full control
|
||||
resourceTypes := "sco" // service, container, object
|
||||
services := "b" // blob
|
||||
|
||||
// Details on how to do this are here:
|
||||
// https://docs.microsoft.com/en-us/rest/api/storageservices/Constructing-an-Account-SAS
|
||||
signedProtocol := "https,http"
|
||||
signedIp := ""
|
||||
signedVersion := sasSignedVersion
|
||||
|
||||
utcNow := time.Now().UTC()
|
||||
startDate := utcNow.Format(time.RFC3339)
|
||||
endDate := utcNow.Add(time.Hour * 24).Format(time.RFC3339)
|
||||
|
||||
sasToken, err := sasStorage.ComputeSASToken(accountName, accessKey, permissions, services, resourceTypes,
|
||||
startDate, endDate, signedProtocol, signedIp, signedVersion)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error computing SAS Token: %+v", err)
|
||||
}
|
||||
log.Printf("SAS Token should be %q", sasToken)
|
||||
return &sasToken, nil
|
||||
}
|
||||
|
||||
type resourceNames struct {
|
||||
resourceGroup string
|
||||
location string
|
||||
|
|
2
go.mod
2
go.mod
|
@ -55,7 +55,7 @@ require (
|
|||
github.com/grpc-ecosystem/grpc-gateway v1.5.1 // indirect
|
||||
github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089
|
||||
github.com/hashicorp/errwrap v1.0.0
|
||||
github.com/hashicorp/go-azure-helpers v0.0.0-20181120094008-dd1e326c8888
|
||||
github.com/hashicorp/go-azure-helpers v0.0.0-20181122151743-c51a3103be3b
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0
|
||||
github.com/hashicorp/go-getter v0.0.0-20180327010114-90bb99a48d86
|
||||
|
|
2
go.sum
2
go.sum
|
@ -124,6 +124,8 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U
|
|||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-azure-helpers v0.0.0-20181120094008-dd1e326c8888 h1:QdenIfqH8llhlVDlGQWVUhg+L8pDT9VteOlMstWRl+w=
|
||||
github.com/hashicorp/go-azure-helpers v0.0.0-20181120094008-dd1e326c8888/go.mod h1:e+GPy2nvD+spqsdjUyw5tbo73rBbu955QBaV9GZoBEA=
|
||||
github.com/hashicorp/go-azure-helpers v0.0.0-20181122151743-c51a3103be3b h1:AJjRaIeZiWlhYVf2skNYOjooaaIOcQzS94a3iSKnXKE=
|
||||
github.com/hashicorp/go-azure-helpers v0.0.0-20181122151743-c51a3103be3b/go.mod h1:e+GPy2nvD+spqsdjUyw5tbo73rBbu955QBaV9GZoBEA=
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de h1:XDCSythtg8aWSRSO29uwhgh7b127fWr+m5SemqjSUL8=
|
||||
github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de/go.mod h1:xIwEieBHERyEvaeKF/TcHh1Hu+lxPM+n2vT1+g9I4m4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
|
||||
|
|
|
@ -0,0 +1,98 @@
|
|||
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
|
||||
}
|
|
@ -302,8 +302,9 @@ github.com/hashicorp/consul/lib/freeport
|
|||
github.com/hashicorp/consul/testutil/retry
|
||||
# github.com/hashicorp/errwrap v1.0.0
|
||||
github.com/hashicorp/errwrap
|
||||
# github.com/hashicorp/go-azure-helpers v0.0.0-20181120094008-dd1e326c8888
|
||||
# github.com/hashicorp/go-azure-helpers v0.0.0-20181122151743-c51a3103be3b
|
||||
github.com/hashicorp/go-azure-helpers/authentication
|
||||
github.com/hashicorp/go-azure-helpers/storage
|
||||
# github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de
|
||||
github.com/hashicorp/go-checkpoint
|
||||
# github.com/hashicorp/go-cleanhttp v0.5.0
|
||||
|
|
|
@ -58,6 +58,22 @@ terraform {
|
|||
}
|
||||
```
|
||||
|
||||
When authenticating using a SAS Token associated with the Storage Account:
|
||||
|
||||
```hcl
|
||||
terraform {
|
||||
backend "azurerm" {
|
||||
storage_account_name = "abcd1234"
|
||||
container_name = "tfstate"
|
||||
key = "prod.terraform.tfstate"
|
||||
|
||||
# rather than defining this inline, the SAS Token can also be sourced
|
||||
# from an Environment Variable - more information is available below.
|
||||
sas_token = "abcdefghijklmnopqrstuvwxyz0123456789..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
-> **NOTE:** When using a Service Principal or an Access Key - we recommend using a [Partial Configuration](/docs/backends/config.html) for the credentials.
|
||||
|
||||
## Example Referencing
|
||||
|
@ -106,6 +122,22 @@ data "terraform_remote_state" "foo" {
|
|||
access_key = "abcdefghijklmnopqrstuvwxyz0123456789..."
|
||||
}
|
||||
}
|
||||
|
||||
When authenticating using a SAS Token associated with the Storage Account:
|
||||
|
||||
```hcl
|
||||
data "terraform_remote_state" "foo" {
|
||||
backend = "azurerm"
|
||||
config = {
|
||||
storage_account_name = "terraform123abc"
|
||||
container_name = "terraform-state"
|
||||
key = "prod.terraform.tfstate"
|
||||
|
||||
# rather than defining this inline, the SAS Token can also be sourced
|
||||
# from an Environment Variable - more information is available below.
|
||||
sas_token = "abcdefghijklmnopqrstuvwxyz0123456789..."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration variables
|
||||
|
@ -134,6 +166,12 @@ When authenticating using the Managed Service Identity (MSI) - the following fie
|
|||
|
||||
---
|
||||
|
||||
When authenticating using a SAS Token associated with the Storage Account - the following fields are also supported:
|
||||
|
||||
* `sas_token` - (Optional) The SAS Token used to access the Blob Storage Account. This can also be sourced from the `ARM_SAS_TOKEN` environment variable.
|
||||
|
||||
---
|
||||
|
||||
When authenticating using the Storage Account's Access Key - the following fields are also supported:
|
||||
|
||||
* `access_key` - (Optional) The Access Key used to access the Blob Storage Account. This can also be sourced from the `ARM_ACCESS_KEY` environment variable.
|
||||
|
|
Loading…
Reference in New Issue