From 24377d99d73ad855caa34ba0a9d11c83877ad1ad Mon Sep 17 00:00:00 2001 From: tombuildsstuff Date: Fri, 18 Mar 2022 07:43:28 +0100 Subject: [PATCH] New Data Source: `azurerm_key_vault_encrypted_value` ``` $ TF_ACC=1 go test -v ./internal/services/keyvault -run=TestAccEncryptedValueDataSource_ === RUN TestAccEncryptedValueDataSource_encryptAndDecrypt === PAUSE TestAccEncryptedValueDataSource_encryptAndDecrypt === CONT TestAccEncryptedValueDataSource_encryptAndDecrypt --- PASS: TestAccEncryptedValueDataSource_encryptAndDecrypt (274.42s) PASS ok github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault 276.443s ``` This commit supersedes #8895 by converting this into a Data Source rather than a Resource which handles the bi-directional conversion of encrypted/decrypted values as necessary. --- internal/provider/services.go | 1 + .../keyvault/encrypted_value_data_source.go | 128 ++++++++++++++++++ .../encrypted_value_data_source_test.go | 98 ++++++++++++++ internal/services/keyvault/registration.go | 11 ++ .../d/key_vault_encrypted_value.html.markdown | 63 +++++++++ 5 files changed, 301 insertions(+) create mode 100644 internal/services/keyvault/encrypted_value_data_source.go create mode 100644 internal/services/keyvault/encrypted_value_data_source_test.go create mode 100644 website/docs/d/key_vault_encrypted_value.html.markdown diff --git a/internal/provider/services.go b/internal/provider/services.go index c16cd58ccc863..4d270ca919283 100644 --- a/internal/provider/services.go +++ b/internal/provider/services.go @@ -123,6 +123,7 @@ func SupportedTypedServices() []sdk.TypedServiceRegistration { costmanagement.Registration{}, disks.Registration{}, eventhub.Registration{}, + keyvault.Registration{}, loadbalancer.Registration{}, loadtest.Registration{}, mssql.Registration{}, diff --git a/internal/services/keyvault/encrypted_value_data_source.go b/internal/services/keyvault/encrypted_value_data_source.go new file mode 100644 index 0000000000000..120dff3be1173 --- /dev/null +++ b/internal/services/keyvault/encrypted_value_data_source.go @@ -0,0 +1,128 @@ +package keyvault + +import ( + "context" + "crypto/sha1" + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/keyvault/v7.1/keyvault" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-provider-azurerm/internal/sdk" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/parse" + "github.com/hashicorp/terraform-provider-azurerm/internal/services/keyvault/validate" + "github.com/hashicorp/terraform-provider-azurerm/internal/tf/validation" + "github.com/hashicorp/terraform-provider-azurerm/internal/timeouts" + "github.com/hashicorp/terraform-provider-azurerm/utils" +) + +var _ sdk.DataSource = EncryptedValueDataSource{} + +type EncryptedValueDataSource struct{} + +type EncryptedValueDataSourceModel struct { + KeyVaultKeyId string `tfschema:"key_vault_key_id"` + Algorithm string `tfschema:"algorithm"` + EncryptedData string `tfschema:"encrypted_data"` + PlainTextValue string `tfschema:"plain_text_value"` +} + +func (EncryptedValueDataSource) Arguments() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "key_vault_key_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.NestedItemId, + }, + "algorithm": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(keyvault.RSA15), + string(keyvault.RSAOAEP), + string(keyvault.RSAOAEP256), + }, false), + }, + "encrypted_data": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + "plain_text_value": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + }, + } +} + +func (EncryptedValueDataSource) Attributes() map[string]*schema.Schema { + return map[string]*schema.Schema{} +} + +func (EncryptedValueDataSource) ModelObject() interface{} { + return &EncryptedValueDataSourceModel{} +} + +func (e EncryptedValueDataSource) ResourceType() string { + return "azurerm_key_vault_encrypted_value" +} + +func (EncryptedValueDataSource) Read() sdk.ResourceFunc { + return sdk.ResourceFunc{ + Func: func(ctx context.Context, metadata sdk.ResourceMetaData) error { + client := metadata.Client.KeyVault.ManagementClient + ctx, cancel := timeouts.ForCreate(metadata.Client.StopContext, metadata.ResourceData) + defer cancel() + + var model EncryptedValueDataSourceModel + if err := metadata.Decode(&model); err != nil { + return fmt.Errorf("decoding: %+v", err) + } + + if model.EncryptedData == "" && model.PlainTextValue == "" { + return fmt.Errorf("one of `encrypted_data` or `plain_text_value` must be specified - both were empty") + } + if model.EncryptedData != "" && model.PlainTextValue != "" { + return fmt.Errorf("only one of `encrypted_data` or `plain_text_value` must be specified - both were specified") + } + + keyVaultKeyId, err := parse.ParseNestedItemID(model.KeyVaultKeyId) + if err != nil { + return err + } + + if model.EncryptedData != "" { + params := keyvault.KeyOperationsParameters{ + Algorithm: keyvault.JSONWebKeyEncryptionAlgorithm(model.Algorithm), + Value: utils.String(model.EncryptedData), + } + result, err := client.Decrypt(ctx, keyVaultKeyId.KeyVaultBaseUrl, keyVaultKeyId.Name, keyVaultKeyId.Version, params) + if err != nil { + return fmt.Errorf("decrypting plain-text value using Key Vault Key ID %q: %+v", model.KeyVaultKeyId, err) + } + if result.Result == nil { + return fmt.Errorf("decrypting plain-text value using Key Vault Key ID %q: `result` was nil", model.KeyVaultKeyId) + } + model.PlainTextValue = *result.Result + } else { + params := keyvault.KeyOperationsParameters{ + Algorithm: keyvault.JSONWebKeyEncryptionAlgorithm(model.Algorithm), + Value: utils.String(model.PlainTextValue), + } + result, err := client.Encrypt(ctx, keyVaultKeyId.KeyVaultBaseUrl, keyVaultKeyId.Name, keyVaultKeyId.Version, params) + if err != nil { + return fmt.Errorf("encrypting plain-text value using Key Vault Key ID %q: %+v", model.KeyVaultKeyId, err) + } + if result.Result == nil { + return fmt.Errorf("encrypting plain-text value using Key Vault Key ID %q: `result` was nil", model.KeyVaultKeyId) + } + model.EncryptedData = *result.Result + } + + metadata.ResourceData.SetId(fmt.Sprintf("%s-%s-%s", model.KeyVaultKeyId, model.Algorithm, sha1.Sum([]byte(model.EncryptedData)))) + return metadata.Encode(&model) + }, + Timeout: 5 * time.Minute, + } +} diff --git a/internal/services/keyvault/encrypted_value_data_source_test.go b/internal/services/keyvault/encrypted_value_data_source_test.go new file mode 100644 index 0000000000000..72976b699b5f1 --- /dev/null +++ b/internal/services/keyvault/encrypted_value_data_source_test.go @@ -0,0 +1,98 @@ +package keyvault_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance" + "github.com/hashicorp/terraform-provider-azurerm/internal/acceptance/check" +) + +type EncryptedValueDataSourceTest struct{} + +func TestAccEncryptedValueDataSource_encryptAndDecrypt(t *testing.T) { + // since this config includes both Encrypted and Decrypted we're testing both use-cases (and comparing the values below) + // so we only need a single test here + data := acceptance.BuildTestData(t, "data.azurerm_key_vault_encrypted_value", "decrypted") + r := EncryptedValueDataSourceTest{} + data.DataSourceTest(t, []acceptance.TestStep{ + { + Config: r.decrypt(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).Key("encrypted_value").MatchesOtherKey(check.That("data.azurerm_key_vault_encrypted_value.encrypted").Key("encrypted_value")), + check.That(data.ResourceName).Key("plain_text_value").MatchesOtherKey(check.That("data.azurerm_key_vault_encrypted_value.encrypted").Key("plain_text_value")), + ), + }, + }) +} + +func (t EncryptedValueDataSourceTest) decrypt(data acceptance.TestData) string { + template := t.template(data) + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +%s + +data "azurerm_key_vault_encrypted_value" "encrypted" { + key_vault_key_id = azurerm_key_vault_key.test.id + algorithm = "RSA1_5" + plain_text_value = "some-encrypted-value" +} + +data "azurerm_key_vault_encrypted_value" "decrypted" { + key_vault_key_id = azurerm_key_vault_key.test.id + algorithm = "RSA1_5" + encrypted_data = data.azurerm_key_vault_encrypted_value.encrypted.encrypted_data +} +`, template) +} + +func (t EncryptedValueDataSourceTest) template(data acceptance.TestData) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%[1]d" + location = "%[2]s" +} + +resource "azurerm_key_vault" "test" { + name = "acctestkv-%[3]s" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + tenant_id = data.azurerm_client_config.current.tenant_id + sku_name = "premium" + soft_delete_retention_days = 7 + + access_policy { + tenant_id = data.azurerm_client_config.current.tenant_id + object_id = data.azurerm_client_config.current.object_id + + key_permissions = [ + "Create", + "Delete", + "Decrypt", + "Encrypt", + "Get", + "Purge", + "Recover", + "Update", + ] + } +} + +resource "azurerm_key_vault_key" "test" { + name = "key-%[3]s" + key_vault_id = azurerm_key_vault.test.id + key_type = "RSA" + key_size = 2048 + + key_opts = [ + "decrypt", + "encrypt", + ] +} +`, data.RandomInteger, data.Locations.Primary, data.RandomString) +} diff --git a/internal/services/keyvault/registration.go b/internal/services/keyvault/registration.go index 975a02e84096c..6e3c4d6769592 100644 --- a/internal/services/keyvault/registration.go +++ b/internal/services/keyvault/registration.go @@ -7,6 +7,7 @@ import ( type Registration struct{} +var _ sdk.TypedServiceRegistrationWithAGitHubLabel = Registration{} var _ sdk.UntypedServiceRegistrationWithAGitHubLabel = Registration{} func (r Registration) AssociatedGitHubLabel() string { @@ -54,3 +55,13 @@ func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { "azurerm_key_vault_managed_storage_account_sas_token_definition": resourceKeyVaultManagedStorageAccountSasTokenDefinition(), } } + +func (r Registration) DataSources() []sdk.DataSource { + return []sdk.DataSource{ + EncryptedValueDataSource{}, + } +} + +func (r Registration) Resources() []sdk.Resource { + return []sdk.Resource{} +} diff --git a/website/docs/d/key_vault_encrypted_value.html.markdown b/website/docs/d/key_vault_encrypted_value.html.markdown new file mode 100644 index 0000000000000..a222feeed8bbe --- /dev/null +++ b/website/docs/d/key_vault_encrypted_value.html.markdown @@ -0,0 +1,63 @@ +--- +subcategory: "Key Vault" +layout: "azurerm" +page_title: "Azure Resource Manager: Data Source: azurerm_key_vault_encrypted_value" +description: |- + Encrypts or Decrypts a value using a Key Vault Key. +--- + +# Data Source: azurerm_key_vault_encrypted_value + +Encrypts or Decrypts a value using a Key Vault Key. + +## Example Usage + +```hcl +data "azurerm_key_vault" "example" { + name = "mykeyvault" + resource_group_name = "some-resource-group" +} + +data "azurerm_key_vault_key" "example" { + name = "some-key" + key_vault_id = data.azurerm_key_vault.example.id +} + +data "azurerm_key_vault_encrypted_value" "encrypted" { + key_vault_key_id = azurerm_key_vault_key.test.id + algorithm = "RSA1_5" + plain_text_value = "some-encrypted-value" +} + +output "id" { + value = data.azurerm_key_vault_encrypted_value.example.encrypted_data +} +``` + +## Arguments Reference + +The following arguments are supported: + +* `algorithm` - (Required) The Algorithm which should be used to Decrypt/Encrypt this Value. Possible values are `RSA1_5`, `RSA-OAEP` and `RSA-OAEP-256`. + +* `key_vault_key_id` - (Required) The ID of the Key Vault Key which should be used to Decrypt/Encrypt this Value. + +--- + +* `encrypted_data` - (Optional) The Base64 URL Encoded Encrypted Data which should be decrypted into `plain_text_value`. + +* `plain_text_value` - (Optional) The plain-text value which should be Encrypted into `encrypted_data`. + +-> **Note:** One of either `encrypted_data` or `plain_text_value` must be specified and is used to populate the encrypted/decrypted value for the other field. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of this Encrypted Value + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `read` - (Defaults to 5 minutes) Used when encrypting/decrypting this value.