diff --git a/azurerm/internal/services/keyvault/key_vault_certificate_data_source.go b/azurerm/internal/services/keyvault/key_vault_certificate_data_source.go new file mode 100644 index 000000000000..b27c31438aa1 --- /dev/null +++ b/azurerm/internal/services/keyvault/key_vault_certificate_data_source.go @@ -0,0 +1,382 @@ +package keyvault + +import ( + "encoding/base64" + "encoding/hex" + "fmt" + "log" + "strings" + "time" + + "github.com/Azure/azure-sdk-for-go/services/keyvault/2016-10-01/keyvault" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tags" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func dataSourceArmKeyVaultCertificate() *schema.Resource { + return &schema.Resource{ + Read: dataSourceArmKeyVaultCertificateRead, + + Timeouts: &schema.ResourceTimeout{ + Read: schema.DefaultTimeout(5 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateKeyVaultChildName, + }, + + "key_vault_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: azure.ValidateResourceID, + }, + + "version": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + // Computed + "certificate_policy": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "issuer_parameters": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "key_properties": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "exportable": { + Type: schema.TypeBool, + Computed: true, + }, + "key_size": { + Type: schema.TypeInt, + Computed: true, + }, + "key_type": { + Type: schema.TypeString, + Computed: true, + }, + "reuse_key": { + Type: schema.TypeBool, + Computed: true, + }, + }, + }, + }, + + "lifetime_action": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "action_type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "trigger": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "days_before_expiry": { + Type: schema.TypeInt, + Computed: true, + }, + "lifetime_percentage": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + + "secret_properties": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "content_type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + + "x509_certificate_properties": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "extended_key_usage": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "key_usage": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "subject": { + Type: schema.TypeString, + Computed: true, + }, + "subject_alternative_names": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "emails": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "dns_names": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "upns": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "validity_in_months": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + + "secret_id": { + Type: schema.TypeString, + Computed: true, + }, + + "certificate_data": { + Type: schema.TypeString, + Computed: true, + }, + + "thumbprint": { + Type: schema.TypeString, + Computed: true, + }, + + "tags": tags.SchemaDataSource(), + }, + } +} + +func dataSourceArmKeyVaultCertificateRead(d *schema.ResourceData, meta interface{}) error { + vaultClient := meta.(*clients.Client).KeyVault.VaultsClient + client := meta.(*clients.Client).KeyVault.ManagementClient + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + name := d.Get("name").(string) + keyVaultId := d.Get("key_vault_id").(string) + version := d.Get("version").(string) + + keyVaultBaseUri, err := azure.GetKeyVaultBaseUrlFromID(ctx, vaultClient, keyVaultId) + if err != nil { + return fmt.Errorf("Error looking up Key %q vault url from id %q: %+v", name, keyVaultId, err) + } + + cert, err := client.GetCertificate(ctx, keyVaultBaseUri, name, version) + if err != nil { + if utils.ResponseWasNotFound(cert.Response) { + log.Printf("[DEBUG] Certificate %q was not found in Key Vault at URI %q - removing from state", name, keyVaultBaseUri) + d.SetId("") + return nil + } + + return fmt.Errorf("Error reading Key Vault Certificate: %+v", err) + } + + if cert.ID == nil || *cert.ID == "" { + return fmt.Errorf("failure reading Key Vault Certificate ID for %q", name) + } + + d.SetId(*cert.ID) + + id, err := azure.ParseKeyVaultChildID(*cert.ID) + if err != nil { + return err + } + + d.Set("name", id.Name) + + certificatePolicy := flattenKeyVaultCertificatePolicyForDataSource(cert.Policy) + if err := d.Set("certificate_policy", certificatePolicy); err != nil { + return fmt.Errorf("Error setting Key Vault Certificate Policy: %+v", err) + } + + d.Set("version", id.Version) + d.Set("secret_id", cert.Sid) + + certificateData := "" + if contents := cert.Cer; contents != nil { + certificateData = strings.ToUpper(hex.EncodeToString(*contents)) + } + d.Set("certificate_data", certificateData) + + thumbprint := "" + if v := cert.X509Thumbprint; v != nil { + x509Thumbprint, err := base64.RawURLEncoding.DecodeString(*v) + if err != nil { + return err + } + + thumbprint = strings.ToUpper(hex.EncodeToString(x509Thumbprint)) + } + d.Set("thumbprint", thumbprint) + + return tags.FlattenAndSet(d, cert.Tags) +} + +func flattenKeyVaultCertificatePolicyForDataSource(input *keyvault.CertificatePolicy) []interface{} { + policy := make(map[string]interface{}) + + if params := input.IssuerParameters; params != nil { + issuerParams := make(map[string]interface{}) + issuerParams["name"] = *params.Name + policy["issuer_parameters"] = []interface{}{issuerParams} + } + + // key properties + if props := input.KeyProperties; props != nil { + keyProps := make(map[string]interface{}) + keyProps["exportable"] = *props.Exportable + keyProps["key_size"] = int(*props.KeySize) + keyProps["key_type"] = *props.KeyType + keyProps["reuse_key"] = *props.ReuseKey + + policy["key_properties"] = []interface{}{keyProps} + } + + // lifetime actions + lifetimeActions := make([]interface{}, 0) + if actions := input.LifetimeActions; actions != nil { + for _, action := range *actions { + lifetimeAction := make(map[string]interface{}) + + actionOutput := make(map[string]interface{}) + if act := action.Action; act != nil { + actionOutput["action_type"] = string(act.ActionType) + } + lifetimeAction["action"] = []interface{}{actionOutput} + + triggerOutput := make(map[string]interface{}) + if trigger := action.Trigger; trigger != nil { + if days := trigger.DaysBeforeExpiry; days != nil { + triggerOutput["days_before_expiry"] = int(*trigger.DaysBeforeExpiry) + } + + if days := trigger.LifetimePercentage; days != nil { + triggerOutput["lifetime_percentage"] = int(*trigger.LifetimePercentage) + } + } + lifetimeAction["trigger"] = []interface{}{triggerOutput} + lifetimeActions = append(lifetimeActions, lifetimeAction) + } + } + policy["lifetime_action"] = lifetimeActions + + // secret properties + if props := input.SecretProperties; props != nil { + keyProps := make(map[string]interface{}) + keyProps["content_type"] = *props.ContentType + + policy["secret_properties"] = []interface{}{keyProps} + } + + // x509 Certificate Properties + if props := input.X509CertificateProperties; props != nil { + certProps := make(map[string]interface{}) + + usages := make([]string, 0) + for _, usage := range *props.KeyUsage { + usages = append(usages, string(usage)) + } + + sanOutputs := make([]interface{}, 0) + if san := props.SubjectAlternativeNames; san != nil { + sanOutput := make(map[string]interface{}) + if emails := san.Emails; emails != nil { + sanOutput["emails"] = *emails + } + if dnsNames := san.DNSNames; dnsNames != nil { + sanOutput["dns_names"] = *dnsNames + } + if upns := san.Upns; upns != nil { + sanOutput["upns"] = *upns + } + + sanOutputs = append(sanOutputs, sanOutput) + } + + certProps["key_usage"] = usages + certProps["subject"] = *props.Subject + certProps["validity_in_months"] = int(*props.ValidityInMonths) + if props.Ekus != nil { + certProps["extended_key_usage"] = props.Ekus + } + certProps["subject_alternative_names"] = sanOutputs + policy["x509_certificate_properties"] = []interface{}{certProps} + } + + return []interface{}{policy} +} diff --git a/azurerm/internal/services/keyvault/registration.go b/azurerm/internal/services/keyvault/registration.go index d21e83053cac..3aa7c37d67d6 100644 --- a/azurerm/internal/services/keyvault/registration.go +++ b/azurerm/internal/services/keyvault/registration.go @@ -22,6 +22,7 @@ func (r Registration) WebsiteCategories() []string { func (r Registration) SupportedDataSources() map[string]*schema.Resource { return map[string]*schema.Resource{ "azurerm_key_vault_access_policy": dataSourceArmKeyVaultAccessPolicy(), + "azurerm_key_vault_certificate": dataSourceArmKeyVaultCertificate(), "azurerm_key_vault_key": dataSourceArmKeyVaultKey(), "azurerm_key_vault_secret": dataSourceArmKeyVaultSecret(), "azurerm_key_vault": dataSourceArmKeyVault(), diff --git a/azurerm/internal/services/keyvault/tests/key_vault_certificate_data_source_test.go b/azurerm/internal/services/keyvault/tests/key_vault_certificate_data_source_test.go new file mode 100644 index 000000000000..6a7fc7199fdb --- /dev/null +++ b/azurerm/internal/services/keyvault/tests/key_vault_certificate_data_source_test.go @@ -0,0 +1,79 @@ +package tests + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" +) + +func TestAccDataSourceAzureRMKeyVaultCertificate_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_key_vault_certificate", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAzureRMKeyVaultCertificate_basic(data), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(data.ResourceName, "certificate_data"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.key_properties.0.key_size", "2048"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.key_properties.0.key_type", "RSA"), + ), + }, + }, + }) +} + +func TestAccDataSourceAzureRMKeyVaultCertificate_generated(t *testing.T) { + data := acceptance.BuildTestData(t, "data.azurerm_key_vault_certificate", "test") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + Providers: acceptance.SupportedProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceAzureRMKeyVaultCertificate_generated(data), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(data.ResourceName, "certificate_data"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.issuer_parameters.0.name", "Self"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.key_properties.0.exportable", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.key_properties.0.key_size", "2048"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.key_properties.0.key_type", "RSA"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.key_properties.0.reuse_key", "true"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.lifetime_action.0.action.0.action_type", "AutoRenew"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.lifetime_action.0.trigger.0.days_before_expiry", "30"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.secret_properties.0.content_type", "application/x-pkcs12"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.x509_certificate_properties.0.subject", "CN=hello-world"), + resource.TestCheckResourceAttr(data.ResourceName, "certificate_policy.0.x509_certificate_properties.0.validity_in_months", "12"), + ), + }, + }, + }) +} + +func testAccDataSourceAzureRMKeyVaultCertificate_basic(data acceptance.TestData) string { + template := testAccAzureRMKeyVaultCertificate_basicImportPFX(data) + 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 +} +`, template) +} + +func testAccDataSourceAzureRMKeyVaultCertificate_generated(data acceptance.TestData) string { + template := testAccAzureRMKeyVaultCertificate_basicGenerate(data) + 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 +} +`, template) +} diff --git a/website/azurerm.erb b/website/azurerm.erb index 9930795058e4..b9c1199dc4e9 100644 --- a/website/azurerm.erb +++ b/website/azurerm.erb @@ -293,6 +293,10 @@ azurerm_key_vault_access_policy +
  • + azurerm_key_vault_certificate +
  • +
  • azurerm_key_vault_key
  • diff --git a/website/docs/d/key_vault_certificate.html.markdown b/website/docs/d/key_vault_certificate.html.markdown new file mode 100644 index 000000000000..f929051349ba --- /dev/null +++ b/website/docs/d/key_vault_certificate.html.markdown @@ -0,0 +1,127 @@ +--- +subcategory: "Key Vault" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_key_vault_certificate" +description: |- + Gets information about an existing Key Vault Certificate. +--- + +# Data Source: azurerm_key_vault_secret + +Use this data source to access information about an existing Key Vault Certificate. + +~> **Note:** All arguments including the secret value will be stored in the raw state as plain-text. +[Read more about sensitive data in state](/docs/state/sensitive-data.html). + +## Example Usage + +```hcl +data "azurerm_key_vault" "example" { + name = "examplekv" + resource_group_name = "some-resource-group" +} + +data "azurerm_key_vault_certificate" "example" { + name = "secret-sauce" + key_vault_id = data.azurerm_key_vault.example.id +} + +output "certificate_thumbprint" { + value = data.azurerm_key_vault_certificate.example.thumbprint +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - Specifies the name of the Key Vault Secret. + +* `key_vault_id` - Specifies the ID of the Key Vault instance where the Secret resides, available on the `azurerm_key_vault` Data Source / Resource. + +* `version` - (Optional) Specifies the version of the certificate to look up. (Defaults to latest) + +**NOTE:** The vault must be in the same subscription as the provider. If the vault is in another subscription, you must create an aliased provider for that subscription. + +## Attributes Reference + +The following attributes are exported: + +* `certificate_policy` - A `certificate_policy` block as defined below. + +* `tags` - A mapping of tags to assign to the resource. + +--- + +`certificate_policy` exports the following: + +* `issuer_parameters` - A `issuer_parameters` block as defined below. +* `key_properties` - A `key_properties` block as defined below. +* `lifetime_action` - A `lifetime_action` block as defined below. +* `secret_properties` - A `secret_properties` block as defined below. +* `x509_certificate_properties` - An `x509_certificate_properties` block as defined below. + +--- + +`issuer_parameters` exports the following: + +* `name` - The name of the Certificate Issuer. + +--- + +`key_properties` exports the following: + +* `exportable` - Is this Certificate Exportable? +* `key_size` - The size of the Key used in the Certificate. +* `key_type` - Specifies the Type of Key, for example `RSA`. +* `reuse_key` - Is the key reusable? + +--- + +`lifetime_action` exports the following: + +* `action` - A `action` block as defined below. +* `trigger` - A `trigger` block as defined below. + +--- + +`action` exports the following: + +* `action_type` - The Type of action to be performed when the lifetime trigger is triggerec. + +--- + +`trigger` exports the following: + +* `days_before_expiry` - The number of days before the Certificate expires that the action associated with this Trigger should run. +* `lifetime_percentage` - The percentage at which during the Certificates Lifetime the action associated with this Trigger should run. + +--- + +`secret_properties` exports the following: + +* `content_type` - The Content-Type of the Certificate, for example `application/x-pkcs12` for a PFX or `application/x-pem-file` for a PEM. + +--- + +`x509_certificate_properties` exports the following: + +* `extended_key_usage` - A list of Extended/Enhanced Key Usages. +* `key_usage` - A list of uses associated with this Key. +* `subject` - The Certificate's Subject. +* `subject_alternative_names` - A `subject_alternative_names` block as defined below. +* `validity_in_months` - The Certificates Validity Period in Months. + +--- + +`subject_alternative_names` exports the following: + +* `dns_names` - A list of alternative DNS names (FQDNs) identified by the Certificate. +* `emails` - A list of email addresses identified by this Certificate. +* `upns` - A list of User Principal Names identified by the Certificate. + +## 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 retrieving the Key Vault Certificate.