-
Notifications
You must be signed in to change notification settings - Fork 4.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New data source: azurerm_storage_account_blob_container_sas #4195
Merged
tombuildsstuff
merged 13 commits into
hashicorp:master
from
r0bnet:data-storage-account-blob-container-sas
Sep 4, 2019
Merged
Changes from 12 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
44cc35f
add validation function for shared access signature ip
r0bnet c771f5b
add first untested implementation of data source for blob container s…
r0bnet b2f50f9
Merge branch 'master' into data-storage-account-blob-container-sas
r0bnet c6038e8
fix formatting
r0bnet 8d0879b
Merge branch 'master' into data-storage-account-blob-container-sas
r0bnet 738024a
Merge branch 'master' into data-storage-account-blob-container-sas
r0bnet 53e71cc
validate start and expiry properties
r0bnet 43518d0
add tests for blob container sas token
r0bnet 6484896
add documentation
r0bnet 37447b3
Merge branch 'master' into data-storage-account-blob-container-sas
r0bnet 7274112
fixed PR comments
r0bnet dd2c3ca
Merge branch 'master' into data-storage-account-blob-container-sas
r0bnet 24eec92
rename ip field to ip_address
r0bnet File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
206 changes: 206 additions & 0 deletions
206
azurerm/data_source_storage_account_blob_container_sas.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": { | ||
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").(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
123
azurerm/data_source_storage_account_blob_container_sas_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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", "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 = "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) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 on reflection could we make this
ip_address
to match the other resources?