Skip to content
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

azurerm_key_vault_key: add support for EC curve based keys #1814

Merged
merged 8 commits into from
Jun 18, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 73 additions & 6 deletions azurerm/resource_arm_key_vault_key.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package azurerm

import (
"encoding/base64"
"fmt"
"log"

Expand Down Expand Up @@ -54,22 +55,23 @@ func resourceArmKeyVaultKey() *schema.Resource {
"key_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
katbyte marked this conversation as resolved.
Show resolved Hide resolved
// turns out Azure's *really* sensitive about the casing of these
// issue: https://github.com/Azure/azure-rest-api-specs/issues/1739
ValidateFunc: validation.StringInSlice([]string{
// 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,
DiffSuppressFunc: ignoreKeySizeChangesForEC,
ConflictsWith: []string{"curve"},
},

"key_opts": {
Expand All @@ -90,6 +92,22 @@ func resourceArmKeyVaultKey() *schema.Resource {
},
},

"curve": {
Type: schema.TypeString,
Optional: 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
DiffSuppressFunc: ignoreEmptyCurveChanges,
katbyte marked this conversation as resolved.
Show resolved Hide resolved
ConflictsWith: []string{"key_size"},
},

// Computed
"version": {
Type: schema.TypeString,
Expand All @@ -106,11 +124,33 @@ func resourceArmKeyVaultKey() *schema.Resource {
Computed: true,
},

"x": {
Type: schema.TypeString,
Computed: true,
},

"y": {
Type: schema.TypeString,
Computed: true,
},

"tags": tagsSchema(),
},
}
}

func ignoreKeySizeChangesForEC(k, old, new string, d *schema.ResourceData) bool {
keyType := keyvault.JSONWebKeyType(d.Get("key_type").(string))
if keyType == keyvault.EC || keyType == keyvault.ECHSM {
return true
}
return old == new
}

func ignoreEmptyCurveChanges(k, old, new string, d *schema.ResourceData) bool {
return new == "" || old == new
}

func resourceArmKeyVaultKeyCreate(d *schema.ResourceData, meta interface{}) error {
vaultClient := meta.(*ArmClient).keyVaultClient
client := meta.(*ArmClient).keyVaultManagementClient
Expand Down Expand Up @@ -165,10 +205,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)
}
Expand Down Expand Up @@ -212,6 +265,9 @@ func resourceArmKeyVaultKeyUpdate(d *schema.ResourceData, meta interface{}) erro
return nil
}

if d.HasChange("key_size") || d.HasChange("key_type") || d.HasChange("curve") {
return resourceArmKeyVaultKeyCreate(d, meta)
katbyte marked this conversation as resolved.
Show resolved Hide resolved
}
keyOptions := expandKeyVaultKeyOptions(d)
tags := d.Get("tags").(map[string]interface{})

Expand Down Expand Up @@ -283,6 +339,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
Expand Down
200 changes: 200 additions & 0 deletions azurerm/resource_arm_key_vault_key_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,46 @@ 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.Test(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.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testCheckAzureRMKeyVaultKeyDestroy,
Steps: []resource.TestStep{
{
Config: config,
Check: resource.ComposeTestCheckFunc(
testCheckAzureRMKeyVaultKeyExists(resourceName),
),
},
},
})
}

func TestAccAzureRMKeyVaultKey_basicRSA(t *testing.T) {
resourceName := "azurerm_key_vault_key.test"
rs := acctest.RandString(6)
Expand Down Expand Up @@ -248,6 +288,56 @@ func TestAccAzureRMKeyVaultKey_disappearsWhenParentKeyVaultDeleted(t *testing.T)
})
}

func TestAccAzureRMKeyVaultKey_importRSA(t *testing.T) {
katbyte marked this conversation as resolved.
Show resolved Hide resolved
resourceName := "azurerm_key_vault_key.test"
rs := acctest.RandString(6)
config := testAccAzureRMKeyVaultKey_basicRSA(rs, testLocation())

resource.Test(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_importEC(t *testing.T) {
katbyte marked this conversation as resolved.
Show resolved Hide resolved
resourceName := "azurerm_key_vault_key.test"
rs := acctest.RandString(6)
config := testAccAzureRMKeyVaultKey_curveEC(rs, testLocation())

resource.Test(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 testCheckAzureRMKeyVaultKeyDestroy(s *terraform.State) error {
client := testAccProvider.Meta().(*ArmClient).keyVaultManagementClient
ctx := testAccProvider.Meta().(*ArmClient).StopContext
Expand Down Expand Up @@ -727,3 +817,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)
}
8 changes: 6 additions & 2 deletions website/docs/r/key_vault_key.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ The following arguments are supported:

* `key_vault_id` - (Required) The ID of the Key Vault where the Key should 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`.

* `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`.

* `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 relese if `key_type` is `EC` or `EC-HSM`.

* `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.

Expand All @@ -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
Expand Down