Skip to content

Commit

Permalink
Merge pull request #10867 from simonbrady/keyvault-cert-add-curve-name
Browse files Browse the repository at this point in the history
azurerm_key_vault_certificate: Support curve property for EC keys
  • Loading branch information
manicminer authored Apr 29, 2021
2 parents 23863d4 + 2f3b26c commit b860001
Show file tree
Hide file tree
Showing 8 changed files with 299 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func dataSourceKeyVaultCertificate() *schema.Resource {
Computed: true,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"curve": {
Type: schema.TypeString,
Computed: true,
},
"exportable": {
Type: schema.TypeBool,
Computed: true,
Expand Down Expand Up @@ -323,9 +327,10 @@ func flattenKeyVaultCertificatePolicyForDataSource(input *keyvault.CertificatePo

// key properties
if props := input.KeyProperties; props != nil {
var curve, keyType string
var exportable, reuseKey bool
var keySize int
var keyType string
curve = string(props.Curve)
if props.Exportable != nil {
exportable = *props.Exportable
}
Expand All @@ -341,6 +346,7 @@ func flattenKeyVaultCertificatePolicyForDataSource(input *keyvault.CertificatePo

policy["key_properties"] = []interface{}{
map[string]interface{}{
"curve": curve,
"exportable": exportable,
"key_size": keySize,
"key_type": keyType,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,32 @@ func TestAccDataSourceKeyVaultCertificate_generated(t *testing.T) {
})
}

func TestAccDataSourceKeyVaultCertificate_generatedEllipticCurve(t *testing.T) {
data := acceptance.BuildTestData(t, "data.azurerm_key_vault_certificate", "test")
r := KeyVaultCertificateDataSource{}

data.DataSourceTest(t, []resource.TestStep{
{
Config: r.generatedEllipticCurve(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).Key("certificate_data").Exists(),
check.That(data.ResourceName).Key("certificate_data_base64").Exists(),
check.That(data.ResourceName).Key("certificate_policy.0.issuer_parameters.0.name").HasValue("Self"),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.curve").HasValue("P-256K"),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.exportable").HasValue("true"),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.key_size").HasValue("256"),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.key_type").HasValue("EC"),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.reuse_key").HasValue("true"),
check.That(data.ResourceName).Key("certificate_policy.0.lifetime_action.0.action.0.action_type").HasValue("AutoRenew"),
check.That(data.ResourceName).Key("certificate_policy.0.lifetime_action.0.trigger.0.days_before_expiry").HasValue("30"),
check.That(data.ResourceName).Key("certificate_policy.0.secret_properties.0.content_type").HasValue("application/x-pkcs12"),
check.That(data.ResourceName).Key("certificate_policy.0.x509_certificate_properties.0.subject").HasValue("CN=hello-world"),
check.That(data.ResourceName).Key("certificate_policy.0.x509_certificate_properties.0.validity_in_months").HasValue("12"),
),
},
})
}

func (KeyVaultCertificateDataSource) basic(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
Expand All @@ -75,3 +101,14 @@ data "azurerm_key_vault_certificate" "test" {
}
`, KeyVaultCertificateResource{}.basicGenerate(data))
}

func (KeyVaultCertificateDataSource) generatedEllipticCurve(data acceptance.TestData) string {
return fmt.Sprintf(`
%s
data "azurerm_key_vault_certificate" "test" {
name = azurerm_key_vault_certificate.test.name
key_vault_id = azurerm_key_vault.test.id
}
`, KeyVaultCertificateResource{}.basicGenerateEllipticCurve(data))
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/helper/validation"

"github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients"
"github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/keyvault/parse"
Expand Down Expand Up @@ -108,16 +109,32 @@ func resourceKeyVaultCertificate() *schema.Resource {
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"curve": {
Type: schema.TypeString,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
string(keyvault.P256),
string(keyvault.P256K),
string(keyvault.P384),
string(keyvault.P521),
}, false),
},
"exportable": {
Type: schema.TypeBool,
Required: true,
ForceNew: true,
},
"key_size": {
Type: schema.TypeInt,
Required: true,
Optional: true,
Computed: true,
ForceNew: true,
ValidateFunc: validation.IntInSlice([]int{
256,
384,
521,
2048,
3072,
4096,
Expand All @@ -133,7 +150,7 @@ func resourceKeyVaultCertificate() *schema.Resource {
string(keyvault.RSA),
string(keyvault.RSAHSM),
string(keyvault.Oct),
}, false),
}, true),
},
"reuse_key": {
Type: schema.TypeBool,
Expand Down Expand Up @@ -405,15 +422,18 @@ func resourceKeyVaultCertificateCreate(d *schema.ResourceData, meta interface{})
}

t := d.Get("tags").(map[string]interface{})
policy := expandKeyVaultCertificatePolicy(d)
policy, err := expandKeyVaultCertificatePolicy(d)
if err != nil {
return fmt.Errorf("expanding certificate policy: %s", err)
}

if v, ok := d.GetOk("certificate"); ok {
// Import
certificate := expandKeyVaultCertificate(v)
importParameters := keyvault.CertificateImportParameters{
Base64EncodedCertificate: utils.String(certificate.CertificateData),
Password: utils.String(certificate.CertificatePassword),
CertificatePolicy: &policy,
CertificatePolicy: policy,
Tags: tags.Expand(t),
}
if _, err := client.ImportCertificate(ctx, *keyVaultBaseUrl, name, importParameters); err != nil {
Expand All @@ -422,7 +442,7 @@ func resourceKeyVaultCertificateCreate(d *schema.ResourceData, meta interface{})
} else {
// Generate new
parameters := keyvault.CertificateCreateParameters{
CertificatePolicy: &policy,
CertificatePolicy: policy,
Tags: tags.Expand(t),
}
if resp, err := client.CreateCertificate(ctx, *keyVaultBaseUrl, name, parameters); err != nil {
Expand Down Expand Up @@ -464,7 +484,7 @@ func resourceKeyVaultCertificateCreate(d *schema.ResourceData, meta interface{})
// It has been observed that at least one certificate issuer responds to a request with manual processing by issuer staff. SLA's may differ among issuers.
// The total create timeout duration is divided by a modified poll interval of 30s to calculate the number of times to allow not found instead of the default 20.
// Using math.Floor, the calculation will err on the lower side of the creation timeout, so as to return before the overall create timeout occurs.
if policy.IssuerParameters != nil && policy.IssuerParameters.Name != nil && *policy.IssuerParameters.Name != "Self" {
if policy != nil && policy.IssuerParameters != nil && policy.IssuerParameters.Name != nil && *policy.IssuerParameters.Name != "Self" {
stateConf.PollInterval = 30 * time.Second
stateConf.NotFoundChecks = int(math.Floor(float64(stateConf.Timeout) / float64(stateConf.PollInterval)))
}
Expand Down Expand Up @@ -671,7 +691,7 @@ func (d deleteAndPurgeCertificate) NestedItemHasBeenPurged(ctx context.Context)
return resp.Response, err
}

func expandKeyVaultCertificatePolicy(d *schema.ResourceData) keyvault.CertificatePolicy {
func expandKeyVaultCertificatePolicy(d *schema.ResourceData) (*keyvault.CertificatePolicy, error) {
policies := d.Get("certificate_policy").([]interface{})
policyRaw := policies[0].(map[string]interface{})
policy := keyvault.CertificatePolicy{}
Expand All @@ -684,10 +704,37 @@ func expandKeyVaultCertificatePolicy(d *schema.ResourceData) keyvault.Certificat

properties := policyRaw["key_properties"].([]interface{})
props := properties[0].(map[string]interface{})

curve := props["curve"].(string)
keyType := props["key_type"].(string)
keySize := props["key_size"].(int)

if keyType == string(keyvault.EC) || keyType == string(keyvault.ECHSM) {
if curve == "" {
return nil, fmt.Errorf("`curve` is required when creating an EC key")
}
// determine key_size if not specified
if keySize == 0 {
switch curve {
case string(keyvault.P256), string(keyvault.P256K):
keySize = 256
case string(keyvault.P384):
keySize = 384
case string(keyvault.P521):
keySize = 521
}
}
} else if keyType == string(keyvault.RSA) || keyType == string(keyvault.RSAHSM) {
if keySize == 0 {
return nil, fmt.Errorf("`key_size` is required when creating an RSA key")
}
}

policy.KeyProperties = &keyvault.KeyProperties{
Curve: keyvault.JSONWebKeyCurveName(curve),
Exportable: utils.Bool(props["exportable"].(bool)),
KeySize: utils.Int32(int32(props["key_size"].(int))),
KeyType: keyvault.JSONWebKeyType(props["key_type"].(string)),
KeySize: utils.Int32(int32(keySize)),
KeyType: keyvault.JSONWebKeyType(keyType),
ReuseKey: utils.Bool(props["reuse_key"].(bool)),
}

Expand Down Expand Up @@ -779,7 +826,7 @@ func expandKeyVaultCertificatePolicy(d *schema.ResourceData) keyvault.Certificat
}
}

return policy
return &policy, nil
}

func flattenKeyVaultCertificatePolicy(input *keyvault.CertificatePolicy, certData *[]byte) []interface{} {
Expand All @@ -798,6 +845,7 @@ func flattenKeyVaultCertificatePolicy(input *keyvault.CertificatePolicy, certDat
// key properties
if props := input.KeyProperties; props != nil {
keyProps := make(map[string]interface{})
keyProps["curve"] = string(props.Curve)
keyProps["exportable"] = *props.Exportable
keyProps["key_size"] = int(*props.KeySize)
keyProps["key_type"] = string(props.KeyType)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,52 @@ func TestAccKeyVaultCertificate_basicGenerateTags(t *testing.T) {
})
}

func TestAccKeyVaultCertificate_basicGenerateEllipticCurve(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_key_vault_certificate", "test")
r := KeyVaultCertificateResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basicGenerateEllipticCurve(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("secret_id").Exists(),
check.That(data.ResourceName).Key("certificate_data").Exists(),
check.That(data.ResourceName).Key("certificate_data_base64").Exists(),
check.That(data.ResourceName).Key("thumbprint").Exists(),
check.That(data.ResourceName).Key("certificate_attribute.0.created").Exists(),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.curve").HasValue("P-256K"),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.key_type").HasValue("EC"),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.key_size").HasValue("256"),
),
},
data.ImportStep(),
})
}

func TestAccKeyVaultCertificate_basicGenerateEllipticCurveAutoKeySize(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_key_vault_certificate", "test")
r := KeyVaultCertificateResource{}

data.ResourceTest(t, r, []resource.TestStep{
{
Config: r.basicGenerateEllipticCurveAutoKeySize(data),
Check: resource.ComposeTestCheckFunc(
check.That(data.ResourceName).ExistsInAzure(r),
check.That(data.ResourceName).Key("secret_id").Exists(),
check.That(data.ResourceName).Key("certificate_data").Exists(),
check.That(data.ResourceName).Key("certificate_data_base64").Exists(),
check.That(data.ResourceName).Key("thumbprint").Exists(),
check.That(data.ResourceName).Key("certificate_attribute.0.created").Exists(),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.curve").HasValue("P-521"),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.key_type").HasValue("EC"),
check.That(data.ResourceName).Key("certificate_policy.0.key_properties.0.key_size").HasValue("521"),
),
},
data.ImportStep(),
})
}

func TestAccKeyVaultCertificate_basicExtendedKeyUsage(t *testing.T) {
data := acceptance.BuildTestData(t, "azurerm_key_vault_certificate", "test")
r := KeyVaultCertificateResource{}
Expand Down Expand Up @@ -633,6 +679,109 @@ resource "azurerm_key_vault_certificate" "test" {
`, r.template(data), data.RandomString)
}

func (r KeyVaultCertificateResource) basicGenerateEllipticCurve(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
%s
resource "azurerm_key_vault_certificate" "test" {
name = "acctestcert%s"
key_vault_id = azurerm_key_vault.test.id
certificate_policy {
issuer_parameters {
name = "Self"
}
key_properties {
curve = "P-256K"
exportable = true
key_size = 256
key_type = "EC"
reuse_key = true
}
lifetime_action {
action {
action_type = "AutoRenew"
}
trigger {
days_before_expiry = 30
}
}
secret_properties {
content_type = "application/x-pkcs12"
}
x509_certificate_properties {
key_usage = [
"digitalSignature",
]
subject = "CN=hello-world"
validity_in_months = 12
}
}
}
`, r.template(data), data.RandomString)
}

func (r KeyVaultCertificateResource) basicGenerateEllipticCurveAutoKeySize(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
features {}
}
%s
resource "azurerm_key_vault_certificate" "test" {
name = "acctestcert%s"
key_vault_id = azurerm_key_vault.test.id
certificate_policy {
issuer_parameters {
name = "Self"
}
key_properties {
curve = "P-521"
exportable = true
key_type = "EC"
reuse_key = true
}
lifetime_action {
action {
action_type = "AutoRenew"
}
trigger {
days_before_expiry = 30
}
}
secret_properties {
content_type = "application/x-pkcs12"
}
x509_certificate_properties {
key_usage = [
"digitalSignature",
]
subject = "CN=hello-world"
validity_in_months = 12
}
}
}
`, r.template(data), data.RandomString)
}

func (r KeyVaultCertificateResource) basicExtendedKeyUsage(data acceptance.TestData) string {
return fmt.Sprintf(`
provider "azurerm" {
Expand Down
Loading

0 comments on commit b860001

Please sign in to comment.