From aa4a19d08a98eed8e38876f5cfce9a2483c7f88c Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Sun, 1 Sep 2019 06:06:28 +0200 Subject: [PATCH 1/2] app service: support for binding an SSL certificate to a web app --- ...arm_app_service_custom_hostname_binding.go | 48 ++++++ ...pp_service_custom_hostname_binding_test.go | 160 ++++++++++++++++++ ...vice_custom_hostname_binding.html.markdown | 8 + 3 files changed, 216 insertions(+) diff --git a/azurerm/resource_arm_app_service_custom_hostname_binding.go b/azurerm/resource_arm_app_service_custom_hostname_binding.go index 88a7ae437180..189d1fcdf624 100644 --- a/azurerm/resource_arm_app_service_custom_hostname_binding.go +++ b/azurerm/resource_arm_app_service_custom_hostname_binding.go @@ -6,8 +6,10 @@ import ( "github.com/Azure/azure-sdk-for-go/services/web/mgmt/2018-02-01/web" "github.com/hashicorp/terraform/helper/schema" + "github.com/hashicorp/terraform/helper/validation" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/azure" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/tf" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/helpers/validate" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/features" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" @@ -38,6 +40,28 @@ func resourceArmAppServiceCustomHostnameBinding() *schema.Resource { Required: true, ForceNew: true, }, + + "ssl_state": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + string(web.SslStateIPBasedEnabled), + string(web.SslStateSniEnabled), + }, false), + }, + + "thumbprint": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ValidateFunc: validate.NoEmptyStrings, + }, + + "virtual_ip": { + Type: schema.TypeString, + Computed: true, + }, }, } } @@ -51,6 +75,8 @@ func resourceArmAppServiceCustomHostnameBindingCreate(d *schema.ResourceData, me resourceGroup := d.Get("resource_group_name").(string) appServiceName := d.Get("app_service_name").(string) hostname := d.Get("hostname").(string) + sslState := d.Get("ssl_state").(string) + thumbprint := d.Get("thumbprint").(string) locks.ByName(appServiceName, appServiceCustomHostnameBindingResourceName) defer locks.UnlockByName(appServiceName, appServiceCustomHostnameBindingResourceName) @@ -74,6 +100,22 @@ func resourceArmAppServiceCustomHostnameBindingCreate(d *schema.ResourceData, me }, } + if sslState != "" { + if thumbprint == "" { + return fmt.Errorf("`thumbprint` must be specified when `ssl_state` is set") + } + + properties.HostNameBindingProperties.SslState = web.SslState(sslState) + } + + if thumbprint != "" { + if sslState == "" { + return fmt.Errorf("`ssl_state` must be specified when `thumbprint` is set") + } + + properties.HostNameBindingProperties.Thumbprint = utils.String(thumbprint) + } + if _, err := client.CreateOrUpdateHostNameBinding(ctx, resourceGroup, appServiceName, hostname, properties); err != nil { return err } @@ -118,6 +160,12 @@ func resourceArmAppServiceCustomHostnameBindingRead(d *schema.ResourceData, meta d.Set("app_service_name", appServiceName) d.Set("resource_group_name", resourceGroup) + if props := resp.HostNameBindingProperties; props != nil { + d.Set("ssl_state", props.SslState) + d.Set("thumbprint", props.Thumbprint) + d.Set("virtual_ip", props.VirtualIP) + } + return nil } diff --git a/azurerm/resource_arm_app_service_custom_hostname_binding_test.go b/azurerm/resource_arm_app_service_custom_hostname_binding_test.go index 23420d387f51..eb3fd93a7de7 100644 --- a/azurerm/resource_arm_app_service_custom_hostname_binding_test.go +++ b/azurerm/resource_arm_app_service_custom_hostname_binding_test.go @@ -34,6 +34,9 @@ func TestAccAzureRMAppServiceCustomHostnameBinding(t *testing.T) { "multiple": testAccAzureRMAppServiceCustomHostnameBinding_multiple, "requiresImport": testAccAzureRMAppServiceCustomHostnameBinding_requiresImport, }, + "ssl": { + "sniEnabled": testAccAzureRMAppServiceCustomHostnameBinding_sslSniEnabled, + }, } for group, m := range testCases { @@ -131,6 +134,58 @@ func testAccAzureRMAppServiceCustomHostnameBinding_multiple(t *testing.T, appSer }) } +func testAccAzureRMAppServiceCustomHostnameBinding_ssl(t *testing.T, appServiceEnv, domainEnv string) { + resourceName := "azurerm_app_service_custom_hostname_binding.test" + ri := tf.AccRandTimeInt() + location := testLocation() + config := testAccAzureRMAppServiceCustomHostnameBinding_sslConfig(ri, location, appServiceEnv, domainEnv) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceCustomHostnameBindingDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceCustomHostnameBindingExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccAzureRMAppServiceCustomHostnameBinding_sslSniEnabled(t *testing.T, appServiceEnv, domainEnv string) { + resourceName := "azurerm_app_service_custom_hostname_binding.test" + ri := tf.AccRandTimeInt() + location := testLocation() + config := testAccAzureRMAppServiceCustomHostnameBinding_sslSniEnabledConfig(ri, location, appServiceEnv, domainEnv) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testCheckAzureRMAppServiceCustomHostnameBindingDestroy, + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + testCheckAzureRMAppServiceCustomHostnameBindingExists(resourceName), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testCheckAzureRMAppServiceCustomHostnameBindingDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*ArmClient).web.AppServicesClient @@ -244,3 +299,108 @@ resource "azurerm_app_service_custom_hostname_binding" "test2" { } `, template, altDomain) } + +func testAccAzureRMAppServiceCustomHostnameBinding_sslConfig(rInt int, location, appServiceName, domain string) string { + return fmt.Sprintf(` +resource "azurerm_resource_group" "test" { + name = "acctestRG-%d" + location = "%s" +} + +resource "azurerm_app_service_plan" "test" { + name = "acctestASP-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + + sku { + tier = "Standard" + size = "S1" + } +} + +resource "azurerm_app_service" "test" { + name = "%s" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + app_service_plan_id = "${azurerm_app_service_plan.test.id}" +} + +data "azurerm_client_config" "test" {} + +resource "azurerm_key_vault" "test" { + name = "acct-%d" + location = "${azurerm_resource_group.test.location}" + resource_group_name = "${azurerm_resource_group.test.name}" + tenant_id = "${data.azurerm_client_config.test.tenant_id}" + sku_name = "standard" + + access_policy { + tenant_id = "${data.azurerm_client_config.test.tenant_id}" + object_id = "${data.azurerm_client_config.test.service_principal_object_id}" + secret_permissions = ["delete", "get", "set"] + certificate_permissions = ["create", "delete", "get", "import"] + } +} + +resource "azurerm_key_vault_certificate" "test" { + name = "acct-%d" + key_vault_id = "${azurerm_key_vault.test.id}" + + certificate_policy { + issuer_parameters { + name = "Self" + } + + key_properties { + exportable = true + key_size = 2048 + key_type = "RSA" + reuse_key = true + } + + secret_properties { + content_type = "application/x-pkcs12" + } + + x509_certificate_properties { + extended_key_usage = ["1.3.6.1.5.5.7.3.1"] + + key_usage = [ + "digitalSignature", + "keyEncipherment", + ] + + subject = "CN=%s" + validity_in_months = 12 + } + } +} + +data "azurerm_key_vault_secret" "test" { + name = "${azurerm_key_vault_certificate.test.name}" + key_vault_id = "${azurerm_key_vault.test.id}" +} + +resource "azurerm_app_service_certificate" "test" { + name = "acctestCert-%d" + resource_group_name = "${azurerm_resource_group.test.name}" + location = "${azurerm_resource_group.test.location}" + pfx_blob = "${data.azurerm_key_vault_secret.test.value}" +} +`, rInt, location, rInt, appServiceName, rInt, rInt, domain, rInt) +} + +func testAccAzureRMAppServiceCustomHostnameBinding_sslSniEnabledConfig(rInt int, location, appServiceName, domain string) string { + template := testAccAzureRMAppServiceCustomHostnameBinding_sslConfig(rInt, location, appServiceName, domain) + return fmt.Sprintf(` +%s + +resource "azurerm_app_service_custom_hostname_binding" "test" { + hostname = "%s" + app_service_name = "${azurerm_app_service.test.name}" + resource_group_name = "${azurerm_resource_group.test.name}" + ssl_state = "SniEnabled" + thumbprint = "${azurerm_app_service_certificate.test.thumbprint}" +} +`, template, domain) +} diff --git a/website/docs/r/app_service_custom_hostname_binding.html.markdown b/website/docs/r/app_service_custom_hostname_binding.html.markdown index 3f5b9b28ed61..19c2427a687a 100644 --- a/website/docs/r/app_service_custom_hostname_binding.html.markdown +++ b/website/docs/r/app_service_custom_hostname_binding.html.markdown @@ -64,12 +64,20 @@ The following arguments are supported: * `resource_group_name` - (Required) The name of the resource group in which the App Service exists. Changing this forces a new resource to be created. +* `ssl_state` - (Optional) The SSL type. Possible values are `IpBasedEnabled` and `SniEnabled`. Changing this forces a new resource to be created. + +* `thumbprint` - (Optional) The SSL certificate thumbprint. Changing this forces a new resource to be created. + +-> **NOTE:** `thumbprint` must be specified when `ssl_state` is set. + ## Attributes Reference The following attributes are exported: * `id` - The ID of the App Service Custom Hostname Binding +* `virtual_ip` - The virtual IP address assigned to the hostname if IP based SSL is enabled. + ## Import App Service Custom Hostname Bindings can be imported using the `resource id`, e.g. From 25c470af8621871733f1642c6af07ced6bcc04c9 Mon Sep 17 00:00:00 2001 From: Joakim Bakke Hellum Date: Sun, 1 Sep 2019 17:44:54 +0200 Subject: [PATCH 2/2] Fix test --- ...pp_service_custom_hostname_binding_test.go | 39 +------------------ 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/azurerm/resource_arm_app_service_custom_hostname_binding_test.go b/azurerm/resource_arm_app_service_custom_hostname_binding_test.go index eb3fd93a7de7..c2e9849f039e 100644 --- a/azurerm/resource_arm_app_service_custom_hostname_binding_test.go +++ b/azurerm/resource_arm_app_service_custom_hostname_binding_test.go @@ -33,9 +33,7 @@ func TestAccAzureRMAppServiceCustomHostnameBinding(t *testing.T) { "basic": testAccAzureRMAppServiceCustomHostnameBinding_basic, "multiple": testAccAzureRMAppServiceCustomHostnameBinding_multiple, "requiresImport": testAccAzureRMAppServiceCustomHostnameBinding_requiresImport, - }, - "ssl": { - "sniEnabled": testAccAzureRMAppServiceCustomHostnameBinding_sslSniEnabled, + "ssl": testAccAzureRMAppServiceCustomHostnameBinding_ssl, }, } @@ -160,32 +158,6 @@ func testAccAzureRMAppServiceCustomHostnameBinding_ssl(t *testing.T, appServiceE }) } -func testAccAzureRMAppServiceCustomHostnameBinding_sslSniEnabled(t *testing.T, appServiceEnv, domainEnv string) { - resourceName := "azurerm_app_service_custom_hostname_binding.test" - ri := tf.AccRandTimeInt() - location := testLocation() - config := testAccAzureRMAppServiceCustomHostnameBinding_sslSniEnabledConfig(ri, location, appServiceEnv, domainEnv) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testCheckAzureRMAppServiceCustomHostnameBindingDestroy, - Steps: []resource.TestStep{ - { - Config: config, - Check: resource.ComposeTestCheckFunc( - testCheckAzureRMAppServiceCustomHostnameBindingExists(resourceName), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - func testCheckAzureRMAppServiceCustomHostnameBindingDestroy(s *terraform.State) error { client := testAccProvider.Meta().(*ArmClient).web.AppServicesClient @@ -387,13 +359,6 @@ resource "azurerm_app_service_certificate" "test" { location = "${azurerm_resource_group.test.location}" pfx_blob = "${data.azurerm_key_vault_secret.test.value}" } -`, rInt, location, rInt, appServiceName, rInt, rInt, domain, rInt) -} - -func testAccAzureRMAppServiceCustomHostnameBinding_sslSniEnabledConfig(rInt int, location, appServiceName, domain string) string { - template := testAccAzureRMAppServiceCustomHostnameBinding_sslConfig(rInt, location, appServiceName, domain) - return fmt.Sprintf(` -%s resource "azurerm_app_service_custom_hostname_binding" "test" { hostname = "%s" @@ -402,5 +367,5 @@ resource "azurerm_app_service_custom_hostname_binding" "test" { ssl_state = "SniEnabled" thumbprint = "${azurerm_app_service_certificate.test.thumbprint}" } -`, template, domain) +`, rInt, location, rInt, appServiceName, rInt, rInt, domain, rInt, domain) }