From 5eccd9228635d2290a28c58f6142e0cd2f0b2c18 Mon Sep 17 00:00:00 2001 From: Payam Hekmat Date: Tue, 18 Jun 2019 18:41:34 -0500 Subject: [PATCH] azurerm_key_vault_key: add support for EC curve based keys (#1814) --- azurerm/resource_arm_key_vault_key.go | 64 ++++++++- azurerm/resource_arm_key_vault_key_test.go | 155 +++++++++++++++++++++ website/docs/r/key_vault_key.html.markdown | 10 +- 3 files changed, 221 insertions(+), 8 deletions(-) diff --git a/azurerm/resource_arm_key_vault_key.go b/azurerm/resource_arm_key_vault_key.go index df3701fee596..3c2a8e4e4d86 100644 --- a/azurerm/resource_arm_key_vault_key.go +++ b/azurerm/resource_arm_key_vault_key.go @@ -1,6 +1,7 @@ package azurerm import ( + "encoding/base64" "fmt" "log" @@ -61,15 +62,17 @@ func resourceArmKeyVaultKey() *schema.Resource { // TODO: add `oct` back in once this is fixed // https://github.com/Azure/azure-rest-api-specs/issues/1739#issuecomment-332236257 string(keyvault.EC), + string(keyvault.ECHSM), string(keyvault.RSA), string(keyvault.RSAHSM), }, false), }, "key_size": { - Type: schema.TypeInt, - Required: true, - ForceNew: true, + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"curve"}, }, "key_opts": { @@ -90,6 +93,23 @@ func resourceArmKeyVaultKey() *schema.Resource { }, }, + "curve": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(keyvault.P256), + string(keyvault.P384), + string(keyvault.P521), + string(keyvault.SECP256K1), + }, false), + // TODO: the curve name should probably be mandatory for EC in the future, + // but handle the diff so that we don't break existing configurations and + // imported EC keys + ConflictsWith: []string{"key_size"}, + }, + // Computed "version": { Type: schema.TypeString, @@ -106,6 +126,16 @@ func resourceArmKeyVaultKey() *schema.Resource { Computed: true, }, + "x": { + Type: schema.TypeString, + Computed: true, + }, + + "y": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tagsSchema(), }, } @@ -165,10 +195,23 @@ func resourceArmKeyVaultKeyCreate(d *schema.ResourceData, meta interface{}) erro KeyAttributes: &keyvault.KeyAttributes{ Enabled: utils.Bool(true), }, - KeySize: utils.Int32(int32(d.Get("key_size").(int))), - Tags: expandTags(tags), + + Tags: expandTags(tags), } + if parameters.Kty == keyvault.EC || parameters.Kty == keyvault.ECHSM { + curveName := d.Get("curve").(string) + parameters.Curve = keyvault.JSONWebKeyCurveName(curveName) + } else if parameters.Kty == keyvault.RSA || parameters.Kty == keyvault.RSAHSM { + keySize, ok := d.GetOk("key_size") + if !ok { + return fmt.Errorf("Key size is required when creating an RSA key") + } + parameters.KeySize = utils.Int32(int32(keySize.(int))) + } + // TODO: support `oct` once this is fixed + // https://github.com/Azure/azure-rest-api-specs/issues/1739#issuecomment-332236257 + if _, err := client.CreateKey(ctx, keyVaultBaseUri, name, parameters); err != nil { return fmt.Errorf("Error Creating Key: %+v", err) } @@ -283,6 +326,17 @@ func resourceArmKeyVaultKeyRead(d *schema.ResourceData, meta interface{}) error d.Set("n", key.N) d.Set("e", key.E) + d.Set("x", key.X) + d.Set("y", key.Y) + if key.N != nil { + nBytes, err := base64.RawURLEncoding.DecodeString(*key.N) + if err != nil { + return fmt.Errorf("Could not decode N: %+v", err) + } + d.Set("key_size", len(nBytes)*8) + } + + d.Set("curve", key.Crv) } // Computed diff --git a/azurerm/resource_arm_key_vault_key_test.go b/azurerm/resource_arm_key_vault_key_test.go index b9c37fd56431..dc0d12237ad3 100644 --- a/azurerm/resource_arm_key_vault_key_test.go +++ b/azurerm/resource_arm_key_vault_key_test.go @@ -94,6 +94,51 @@ func TestAccAzureRMKeyVaultKey_requiresImport(t *testing.T) { }) } +func TestAccAzureRMKeyVaultKey_basicECHSM(t *testing.T) { + resourceName := "azurerm_key_vault_key.test" + rs := acctest.RandString(6) + config := testAccAzureRMKeyVaultKey_basicECHSM(rs, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultKeyDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultKeyExists(resourceName), + ), + }, + }, + }) +} + +func TestAccAzureRMKeyVaultKey_curveEC(t *testing.T) { + resourceName := "azurerm_key_vault_key.test" + rs := acctest.RandString(6) + config := testAccAzureRMKeyVaultKey_curveEC(rs, testLocation()) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMKeyVaultKeyDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMKeyVaultKeyExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccAzureRMKeyVaultKey_basicRSA(t *testing.T) { resourceName := "azurerm_key_vault_key.test" rs := acctest.RandString(6) @@ -727,3 +772,113 @@ resource "azurerm_key_vault_key" "test" { } `, rString, location, rString, rString) } + +func testAccAzureRMKeyVaultKey_curveEC(rString string, location string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%s" + location = "%s" +} + +resource "azurerm_key_vault" "test" { + name = "acctestkv-%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" + } + + access_policy { + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.service_principal_object_id}" + + key_permissions = [ + "create", + "delete", + "get", + ] + + secret_permissions = [ + "get", + "delete", + "set", + ] + } + + tags = { + environment = "Production" + } +} + +resource "azurerm_key_vault_key" "test" { + name = "key-%s" + vault_uri = "${azurerm_key_vault.test.vault_uri}" + key_type = "EC" + curve = "P-521" + + key_opts = [ + "sign", + "verify", + ] +} +`, rString, location, rString, rString) +} + +func testAccAzureRMKeyVaultKey_basicECHSM(rString string, location string) string { + return fmt.Sprintf(` +data "azurerm_client_config" "current" {} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-%s" + location = "%s" +} + +resource "azurerm_key_vault" "test" { + name = "acctestkv-%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" + } + + access_policy { + tenant_id = "${data.azurerm_client_config.current.tenant_id}" + object_id = "${data.azurerm_client_config.current.service_principal_object_id}" + + key_permissions = [ + "create", + "delete", + "get", + ] + + secret_permissions = [ + "get", + "delete", + "set", + ] + } + + tags = { + environment = "Production" + } +} + +resource "azurerm_key_vault_key" "test" { + name = "key-%s" + vault_uri = "${azurerm_key_vault.test.vault_uri}" + key_type = "EC-HSM" + curve = "P-521" + + key_opts = [ + "sign", + "verify", + ] +} +`, rString, location, rString, rString) +} diff --git a/website/docs/r/key_vault_key.html.markdown b/website/docs/r/key_vault_key.html.markdown index 022ece97dcde..8b8f64944781 100644 --- a/website/docs/r/key_vault_key.html.markdown +++ b/website/docs/r/key_vault_key.html.markdown @@ -81,11 +81,13 @@ The following arguments are supported: * `name` - (Required) Specifies the name of the Key Vault Key. Changing this forces a new resource to be created. -* `key_vault_id` - (Required) The ID of the Key Vault where the Key should be created. +* `key_vault_id` - (Required) The ID of the Key Vault where the Key should be created. Changing this forces a new resource to be created. -* `key_type` - (Required) Specifies the Key Type to use for this Key Vault Key. Possible values are `EC` (Elliptic Curve), `Oct` (Octet), `RSA` and `RSA-HSM`. Changing this forces a new resource to be created. +* `key_type` - (Required) Specifies the Key Type to use for this Key Vault Key. Possible values are `EC` (Elliptic Curve), `EC-HSM`, `Oct` (Octet), `RSA` and `RSA-HSM`. Changing this forces a new resource to be created. -* `key_size` - (Required) Specifies the Size of the Key to create in bytes. For example, 1024 or 2048. Changing this forces a new resource to be created. +* `key_size` - (Optional) Specifies the Size of the RSA key to create in bytes. For example, 1024 or 2048. *Note*: This field is required if `key_type` is `RSA` or `RSA-HSM`. Changing this forces a new resource to be created. + +* `curve` - (Optional) Specifies the curve to use when creating an `EC` key. Possible values are `P-256`, `P-384`, `P-521`, and `SECP256K1`. This field will be required in a future release if `key_type` is `EC` or `EC-HSM`. The API will default to `P-256` if nothing is specified. Changing this forces a new resource to be created. * `key_opts` - (Required) A list of JSON web key operations. Possible values include: `decrypt`, `encrypt`, `sign`, `unwrapKey`, `verify` and `wrapKey`. Please note these values are case sensitive. @@ -99,6 +101,8 @@ The following attributes are exported: * `version` - The current version of the Key Vault Key. * `n` - The RSA modulus of this Key Vault Key. * `e` - The RSA public exponent of this Key Vault Key. +* `x` - The EC X component of this Key Vault Key. +* `y` - The EC Y component of this Key Vault Key. ## Import