Skip to content

Commit

Permalink
Merge pull request #4195 from r0bnet/data-storage-account-blob-contai…
Browse files Browse the repository at this point in the history
…ner-sas

New data source: azurerm_storage_account_blob_container_sas
  • Loading branch information
tombuildsstuff authored Sep 4, 2019
2 parents 25fb4d4 + 24eec92 commit c0e99ec
Show file tree
Hide file tree
Showing 6 changed files with 531 additions and 0 deletions.
206 changes: 206 additions & 0 deletions azurerm/data_source_storage_account_blob_container_sas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package azurerm

import (
"crypto/sha256"
"encoding/hex"

"github.com/hashicorp/go-azure-helpers/storage"
"github.com/hashicorp/terraform/helper/schema"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate"
)

func dataSourceArmStorageAccountBlobContainerSharedAccessSignature() *schema.Resource {
return &schema.Resource{
Read: dataSourceArmStorageContainerSasRead,

Schema: map[string]*schema.Schema{
"connection_string": {
Type: schema.TypeString,
Required: true,
Sensitive: true,
ValidateFunc: validate.NoEmptyStrings,
},

"container_name": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validate.NoEmptyStrings,
},

"https_only": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},

"ip_address": {
Type: schema.TypeString,
Optional: true,
ValidateFunc: validate.SharedAccessSignatureIP,
},

"start": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validate.ISO8601DateTime,
},

"expiry": {
Type: schema.TypeString,
Required: true,
ValidateFunc: validate.ISO8601DateTime,
},

"permissions": {
Type: schema.TypeList,
Required: true,
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"read": {
Type: schema.TypeBool,
Required: true,
},

"add": {
Type: schema.TypeBool,
Required: true,
},

"create": {
Type: schema.TypeBool,
Required: true,
},

"write": {
Type: schema.TypeBool,
Required: true,
},

"delete": {
Type: schema.TypeBool,
Required: true,
},

"list": {
Type: schema.TypeBool,
Required: true,
},
},
},
},

"cache_control": {
Type: schema.TypeString,
Optional: true,
},

"content_disposition": {
Type: schema.TypeString,
Optional: true,
},

"content_encoding": {
Type: schema.TypeString,
Optional: true,
},

"content_language": {
Type: schema.TypeString,
Optional: true,
},

"content_type": {
Type: schema.TypeString,
Optional: true,
},

"sas": {
Type: schema.TypeString,
Computed: true,
Sensitive: true,
},
},
}

}

func dataSourceArmStorageContainerSasRead(d *schema.ResourceData, _ interface{}) error {

connString := d.Get("connection_string").(string)
containerName := d.Get("container_name").(string)
httpsOnly := d.Get("https_only").(bool)
ip := d.Get("ip_address").(string)
start := d.Get("start").(string)
expiry := d.Get("expiry").(string)
permissionsIface := d.Get("permissions").([]interface{})

// response headers
cacheControl := d.Get("cache_control").(string)
contentDisposition := d.Get("content_disposition").(string)
contentEncoding := d.Get("content_encoding").(string)
contentLanguage := d.Get("content_language").(string)
contentType := d.Get("content_type").(string)

permissions := buildContainerPermissionsString(permissionsIface[0].(map[string]interface{}))

// Parse the connection string
kvp, err := storage.ParseAccountSASConnectionString(connString)
if err != nil {
return err
}

// Create the string to sign with the key...
accountName := kvp[connStringAccountNameKey]
accountKey := kvp[connStringAccountKeyKey]
var signedProtocol = "https,http"
if httpsOnly {
signedProtocol = "https"
}
signedIp := ip
signedIdentifier := ""
signedSnapshotTime := ""

sasToken, err := storage.ComputeContainerSASToken(permissions, start, expiry, accountName, accountKey,
containerName, signedIdentifier, signedIp, signedProtocol, signedSnapshotTime, cacheControl,
contentDisposition, contentEncoding, contentLanguage, contentType)
if err != nil {
return err
}

d.Set("sas", sasToken)
tokenHash := sha256.Sum256([]byte(sasToken))
d.SetId(hex.EncodeToString(tokenHash[:]))

return nil
}

func buildContainerPermissionsString(perms map[string]interface{}) string {
retVal := ""

if val, pres := perms["read"].(bool); pres && val {
retVal += "r"
}

if val, pres := perms["add"].(bool); pres && val {
retVal += "a"
}

if val, pres := perms["create"].(bool); pres && val {
retVal += "c"
}

if val, pres := perms["write"].(bool); pres && val {
retVal += "w"
}

if val, pres := perms["delete"].(bool); pres && val {
retVal += "d"
}

if val, pres := perms["list"].(bool); pres && val {
retVal += "l"
}

return retVal
}
123 changes: 123 additions & 0 deletions azurerm/data_source_storage_account_blob_container_sas_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package azurerm

import (
"fmt"
"testing"
"time"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
)

func TestAccDataSourceArmStorageAccountBlobContainerSas_basic(t *testing.T) {
dataSourceName := "data.azurerm_storage_account_blob_container_sas.test"
rInt := tf.AccRandTimeInt()
rString := acctest.RandString(4)
location := testLocation()
utcNow := time.Now().UTC()
startDate := utcNow.Format(time.RFC3339)
endDate := utcNow.Add(time.Hour * 24).Format(time.RFC3339)

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccDataSourceAzureRMStorageAccountBlobContainerSas_basic(rInt, rString, location, startDate, endDate),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceName, "https_only", "true"),
resource.TestCheckResourceAttr(dataSourceName, "start", startDate),
resource.TestCheckResourceAttr(dataSourceName, "expiry", endDate),
resource.TestCheckResourceAttr(dataSourceName, "ip_address", "168.1.5.65"),
resource.TestCheckResourceAttr(dataSourceName, "permissions.#", "1"),
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.read", "true"),
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.add", "true"),
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.create", "false"),
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.write", "false"),
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.delete", "true"),
resource.TestCheckResourceAttr(dataSourceName, "permissions.0.list", "true"),
resource.TestCheckResourceAttr(dataSourceName, "cache_control", "max-age=5"),
resource.TestCheckResourceAttr(dataSourceName, "content_disposition", "inline"),
resource.TestCheckResourceAttr(dataSourceName, "content_encoding", "deflate"),
resource.TestCheckResourceAttr(dataSourceName, "content_language", "en-US"),
resource.TestCheckResourceAttr(dataSourceName, "content_type", "application/json"),
resource.TestCheckResourceAttrSet(dataSourceName, "sas"),
),
},
},
})
}

func testAccDataSourceAzureRMStorageAccountBlobContainerSas_basic(rInt int, rString string, location string, startDate string, endDate string) string {
return fmt.Sprintf(`
resource "azurerm_resource_group" "rg" {
name = "acctestsa-%d"
location = "%s"
}
resource "azurerm_storage_account" "storage" {
name = "acctestsads%s"
resource_group_name = "${azurerm_resource_group.rg.name}"
location = "${azurerm_resource_group.rg.location}"
account_tier = "Standard"
account_replication_type = "LRS"
}
resource "azurerm_storage_container" "container" {
name = "sas-test"
resource_group_name = "${azurerm_resource_group.rg.name}"
storage_account_name = "${azurerm_storage_account.storage.name}"
container_access_type = "private"
}
data "azurerm_storage_account_blob_container_sas" "test" {
connection_string = "${azurerm_storage_account.storage.primary_connection_string}"
container_name = "${azurerm_storage_container.container.name}"
https_only = true
ip_address = "168.1.5.65"
start = "%s"
expiry = "%s"
permissions {
read = true
add = true
create = false
write = false
delete = true
list = true
}
cache_control = "max-age=5"
content_disposition = "inline"
content_encoding = "deflate"
content_language = "en-US"
content_type = "application/json"
}
`, rInt, location, rString, startDate, endDate)
}

func TestAccDataSourceArmStorageAccountBlobContainerSas_permissionsString(t *testing.T) {
testCases := []struct {
input map[string]interface{}
expected string
}{
{map[string]interface{}{"read": true}, "r"},
{map[string]interface{}{"add": true}, "a"},
{map[string]interface{}{"create": true}, "c"},
{map[string]interface{}{"write": true}, "w"},
{map[string]interface{}{"delete": true}, "d"},
{map[string]interface{}{"list": true}, "l"},
{map[string]interface{}{"add": true, "write": true, "read": true, "delete": true}, "rawd"},
}

for _, test := range testCases {
result := buildContainerPermissionsString(test.input)
if test.expected != result {
t.Fatalf("Failed to build resource type string: expected: %s, result: %s", test.expected, result)
}
}
}
32 changes: 32 additions & 0 deletions azurerm/helpers/validate/shared_access_signature.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package validate

import (
"fmt"
"net"
"strings"
)

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

if net.ParseIP(value) != nil {
return warnings, errors
}

ipRange := strings.Split(value, "-")

if len(ipRange) != 2 || net.ParseIP(ipRange[0]) == nil || net.ParseIP(ipRange[1]) == nil {
errors = append(errors, fmt.Errorf("%q must be a valid ipv4 address or a range of ipv4 addresses separated by a hyphen", k))
return warnings, errors
}

ip1 := ipRange[0]
ip2 := ipRange[1]

if ip1 == ip2 {
errors = append(errors, fmt.Errorf("IP addresses in a range for %q must be not be identical", k))
return warnings, errors
}

return warnings, errors
}
Loading

0 comments on commit c0e99ec

Please sign in to comment.