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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
|
"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
|
||||||
|
@ -25,6 +28,7 @@ type ArmClient struct {
|
||||||
environment azure.Environment
|
environment azure.Environment
|
||||||
resourceGroupName string
|
resourceGroupName string
|
||||||
storageAccountName string
|
storageAccountName string
|
||||||
|
sasToken string
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildArmClient(config BackendConfig) (*ArmClient, error) {
|
func buildArmClient(config BackendConfig) (*ArmClient, error) {
|
||||||
|
@ -44,6 +48,12 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) {
|
||||||
return &client, nil
|
return &client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// likewise with a SAS token
|
||||||
|
if config.SasToken != "" {
|
||||||
|
client.sasToken = config.SasToken
|
||||||
|
return &client, nil
|
||||||
|
}
|
||||||
|
|
||||||
builder := authentication.Builder{
|
builder := authentication.Builder{
|
||||||
ClientID: config.ClientID,
|
ClientID: config.ClientID,
|
||||||
ClientSecret: config.ClientSecret,
|
ClientSecret: config.ClientSecret,
|
||||||
|
@ -85,6 +95,7 @@ func buildArmClient(config BackendConfig) (*ArmClient, error) {
|
||||||
|
|
||||||
func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClient, error) {
|
func (c ArmClient) getBlobClient(ctx context.Context) (*storage.BlobStorageClient, error) {
|
||||||
if c.accessKey != "" {
|
if c.accessKey != "" {
|
||||||
|
log.Printf("Building the Blob Client from an Access Token")
|
||||||
storageClient, err := storage.NewBasicClientOnSovereignCloud(c.storageAccountName, c.accessKey, c.environment)
|
storageClient, err := storage.NewBasicClientOnSovereignCloud(c.storageAccountName, c.accessKey, c.environment)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error creating storage client for storage account %q: %s", c.storageAccountName, err)
|
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
|
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)
|
keys, err := c.storageAccountsClient.ListKeys(ctx, c.resourceGroupName, c.storageAccountName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Error retrieving keys for Storage Account %q: %s", c.storageAccountName, err)
|
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", ""),
|
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": {
|
"resource_group_name": {
|
||||||
Type: schema.TypeString,
|
Type: schema.TypeString,
|
||||||
Optional: true,
|
Optional: true,
|
||||||
|
@ -122,6 +129,7 @@ type BackendConfig struct {
|
||||||
Environment string
|
Environment string
|
||||||
MsiEndpoint string
|
MsiEndpoint string
|
||||||
ResourceGroupName string
|
ResourceGroupName string
|
||||||
|
SasToken string
|
||||||
SubscriptionID string
|
SubscriptionID string
|
||||||
TenantID string
|
TenantID string
|
||||||
UseMsi bool
|
UseMsi bool
|
||||||
|
@ -145,6 +153,7 @@ func (b *Backend) configure(ctx context.Context) error {
|
||||||
Environment: data.Get("environment").(string),
|
Environment: data.Get("environment").(string),
|
||||||
MsiEndpoint: data.Get("msi_endpoint").(string),
|
MsiEndpoint: data.Get("msi_endpoint").(string),
|
||||||
ResourceGroupName: data.Get("resource_group_name").(string),
|
ResourceGroupName: data.Get("resource_group_name").(string),
|
||||||
|
SasToken: data.Get("sas_token").(string),
|
||||||
StorageAccountName: data.Get("storage_account_name").(string),
|
StorageAccountName: data.Get("storage_account_name").(string),
|
||||||
SubscriptionID: data.Get("arm_subscription_id").(string),
|
SubscriptionID: data.Get("arm_subscription_id").(string),
|
||||||
TenantID: data.Get("arm_tenant_id").(string),
|
TenantID: data.Get("arm_tenant_id").(string),
|
||||||
|
@ -156,8 +165,8 @@ func (b *Backend) configure(ctx context.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.AccessKey == "" && config.ResourceGroupName == "" {
|
if config.AccessKey == "" && config.SasToken == "" && config.ResourceGroupName == "" {
|
||||||
return fmt.Errorf("Either an Access Key or the Resource Group for the Storage Account must be specified")
|
return fmt.Errorf("Either an Access Key / SAS Token or the Resource Group for the Storage Account must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
b.armClient = armClient
|
b.armClient = armClient
|
||||||
|
|
|
@ -43,17 +43,18 @@ func TestBackendAccessKeyBasic(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
armClient.destroyTestResources(ctx, res)
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
"container_name": res.storageContainerName,
|
"container_name": res.storageContainerName,
|
||||||
"key": res.storageKeyName,
|
"key": res.storageKeyName,
|
||||||
"access_key": res.storageAccountAccessKey,
|
"access_key": res.storageAccountAccessKey,
|
||||||
|
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||||
})).(*Backend)
|
})).(*Backend)
|
||||||
|
|
||||||
backend.TestBackendStates(t, b)
|
backend.TestBackendStates(t, b)
|
||||||
|
@ -67,11 +68,10 @@ func TestBackendManagedServiceIdentityBasic(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
|
@ -87,6 +87,35 @@ func TestBackendManagedServiceIdentityBasic(t *testing.T) {
|
||||||
backend.TestBackendStates(t, b)
|
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) {
|
func TestBackendServicePrincipalBasic(t *testing.T) {
|
||||||
testAccAzureBackend(t)
|
testAccAzureBackend(t)
|
||||||
rs := acctest.RandString(4)
|
rs := acctest.RandString(4)
|
||||||
|
@ -95,11 +124,10 @@ func TestBackendServicePrincipalBasic(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
|
@ -124,17 +152,17 @@ func TestBackendAccessKeyLocked(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
"container_name": res.storageContainerName,
|
"container_name": res.storageContainerName,
|
||||||
"key": res.storageKeyName,
|
"key": res.storageKeyName,
|
||||||
"access_key": res.storageAccountAccessKey,
|
"access_key": res.storageAccountAccessKey,
|
||||||
|
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||||
})).(*Backend)
|
})).(*Backend)
|
||||||
|
|
||||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
|
@ -142,6 +170,7 @@ func TestBackendAccessKeyLocked(t *testing.T) {
|
||||||
"container_name": res.storageContainerName,
|
"container_name": res.storageContainerName,
|
||||||
"key": res.storageKeyName,
|
"key": res.storageKeyName,
|
||||||
"access_key": res.storageAccountAccessKey,
|
"access_key": res.storageAccountAccessKey,
|
||||||
|
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||||
})).(*Backend)
|
})).(*Backend)
|
||||||
|
|
||||||
backend.TestBackendStateLocks(t, b1, b2)
|
backend.TestBackendStateLocks(t, b1, b2)
|
||||||
|
@ -156,11 +185,10 @@ func TestBackendServicePrincipalLocked(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
|
|
|
@ -24,17 +24,17 @@ func TestRemoteClientAccessKeyBasic(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
"container_name": res.storageContainerName,
|
"container_name": res.storageContainerName,
|
||||||
"key": res.storageKeyName,
|
"key": res.storageKeyName,
|
||||||
"access_key": res.storageAccountAccessKey,
|
"access_key": res.storageAccountAccessKey,
|
||||||
|
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||||
})).(*Backend)
|
})).(*Backend)
|
||||||
|
|
||||||
state, err := b.StateMgr(backend.DefaultStateName)
|
state, err := b.StateMgr(backend.DefaultStateName)
|
||||||
|
@ -53,11 +53,10 @@ func TestRemoteClientManagedServiceIdentityBasic(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
|
@ -78,6 +77,40 @@ func TestRemoteClientManagedServiceIdentityBasic(t *testing.T) {
|
||||||
remote.TestClient(t, state.(*remote.State).Client)
|
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) {
|
func TestRemoteClientServicePrincipalBasic(t *testing.T) {
|
||||||
testAccAzureBackend(t)
|
testAccAzureBackend(t)
|
||||||
rs := acctest.RandString(4)
|
rs := acctest.RandString(4)
|
||||||
|
@ -86,11 +119,10 @@ func TestRemoteClientServicePrincipalBasic(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
|
@ -120,17 +152,17 @@ func TestRemoteClientAccessKeyLocks(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
"container_name": res.storageContainerName,
|
"container_name": res.storageContainerName,
|
||||||
"key": res.storageKeyName,
|
"key": res.storageKeyName,
|
||||||
"access_key": res.storageAccountAccessKey,
|
"access_key": res.storageAccountAccessKey,
|
||||||
|
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||||
})).(*Backend)
|
})).(*Backend)
|
||||||
|
|
||||||
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
|
@ -138,6 +170,7 @@ func TestRemoteClientAccessKeyLocks(t *testing.T) {
|
||||||
"container_name": res.storageContainerName,
|
"container_name": res.storageContainerName,
|
||||||
"key": res.storageKeyName,
|
"key": res.storageKeyName,
|
||||||
"access_key": res.storageAccountAccessKey,
|
"access_key": res.storageAccountAccessKey,
|
||||||
|
"environment": os.Getenv("ARM_ENVIRONMENT"),
|
||||||
})).(*Backend)
|
})).(*Backend)
|
||||||
|
|
||||||
s1, err := b1.StateMgr(backend.DefaultStateName)
|
s1, err := b1.StateMgr(backend.DefaultStateName)
|
||||||
|
@ -161,11 +194,10 @@ func TestRemoteClientServicePrincipalLocks(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
|
||||||
"storage_account_name": res.storageAccountName,
|
"storage_account_name": res.storageAccountName,
|
||||||
|
@ -212,11 +244,10 @@ func TestPutMaintainsMetaData(t *testing.T) {
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.TODO()
|
||||||
err := armClient.buildTestResources(ctx, &res)
|
err := armClient.buildTestResources(ctx, &res)
|
||||||
|
defer armClient.destroyTestResources(ctx, res)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
armClient.destroyTestResources(ctx, res)
|
|
||||||
t.Fatalf("Error creating Test Resources: %q", err)
|
t.Fatalf("Error creating Test Resources: %q", err)
|
||||||
}
|
}
|
||||||
defer armClient.destroyTestResources(ctx, res)
|
|
||||||
|
|
||||||
headerName := "acceptancetest"
|
headerName := "acceptancetest"
|
||||||
expectedValue := "f3b56bad-33ad-4b93-a600-7a66e9cbd1eb"
|
expectedValue := "f3b56bad-33ad-4b93-a600-7a66e9cbd1eb"
|
||||||
|
|
|
@ -7,10 +7,16 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/resources/mgmt/resources"
|
"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"
|
armStorage "github.com/Azure/azure-sdk-for-go/profiles/2017-03-09/storage/mgmt/storage"
|
||||||
"github.com/Azure/azure-sdk-for-go/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
|
// 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
|
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 {
|
type resourceNames struct {
|
||||||
resourceGroup string
|
resourceGroup string
|
||||||
location 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/grpc-ecosystem/grpc-gateway v1.5.1 // indirect
|
||||||
github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089
|
github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089
|
||||||
github.com/hashicorp/errwrap v1.0.0
|
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-checkpoint v0.0.0-20171009173528-1545e56e46de
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.0
|
github.com/hashicorp/go-cleanhttp v0.5.0
|
||||||
github.com/hashicorp/go-getter v0.0.0-20180327010114-90bb99a48d86
|
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/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 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-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 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-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=
|
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/consul/testutil/retry
|
||||||
# github.com/hashicorp/errwrap v1.0.0
|
# github.com/hashicorp/errwrap v1.0.0
|
||||||
github.com/hashicorp/errwrap
|
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/authentication
|
||||||
|
github.com/hashicorp/go-azure-helpers/storage
|
||||||
# github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de
|
# github.com/hashicorp/go-checkpoint v0.0.0-20171009173528-1545e56e46de
|
||||||
github.com/hashicorp/go-checkpoint
|
github.com/hashicorp/go-checkpoint
|
||||||
# github.com/hashicorp/go-cleanhttp v0.5.0
|
# 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.
|
-> **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
|
## Example Referencing
|
||||||
|
@ -106,6 +122,22 @@ data "terraform_remote_state" "foo" {
|
||||||
access_key = "abcdefghijklmnopqrstuvwxyz0123456789..."
|
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
|
## 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:
|
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.
|
* `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