From 4e4c8b0b96c5e4e7615a055a00493d135dd32cea Mon Sep 17 00:00:00 2001 From: Neil Ye Date: Wed, 21 Jul 2021 13:53:29 +0800 Subject: [PATCH] New Resource: azurerm_signalr_service_network_acl (#12434) Currently, azurerm_signalr_service doesn't support to set network access control for SignalR. After investigated, I think we cannot implement the networkACL property in azurerrm_signalr_service. Because service team confirmed that we must create private endpoint (Here private endpoint is created by azurerm_private_endpoint) which is referencing the existing SignalR resource (Here signalr resource is created by azurerm_signalr_service) first before updating the networkACL.privateEndpoint property of the existing SignalR resource. If we implement networkACL.privateEndpoint in azurerm_signalr_service, it would cause the issue of circular reference between azurerm_private_endpoint and azurerm_signalr_service. So we have to make this property as a separate resource. Overview: docs.microsoft.com/en-us/azure/azure-signalr/howto-network-access-control --- .../internal/services/signalr/registration.go | 3 +- .../signalr_service_network_acl_resource.go | 438 ++++++++++++++++++ ...gnalr_service_network_acl_resource_test.go | 300 ++++++++++++ .../signalr_service_network_acl.html.markdown | 140 ++++++ 4 files changed, 880 insertions(+), 1 deletion(-) create mode 100644 azurerm/internal/services/signalr/signalr_service_network_acl_resource.go create mode 100644 azurerm/internal/services/signalr/signalr_service_network_acl_resource_test.go create mode 100644 website/docs/r/signalr_service_network_acl.html.markdown diff --git a/azurerm/internal/services/signalr/registration.go b/azurerm/internal/services/signalr/registration.go index 721218b003ad..abba5c6df855 100644 --- a/azurerm/internal/services/signalr/registration.go +++ b/azurerm/internal/services/signalr/registration.go @@ -28,6 +28,7 @@ func (r Registration) SupportedDataSources() map[string]*pluginsdk.Resource { // SupportedResources returns the supported Resources supported by this Service func (r Registration) SupportedResources() map[string]*pluginsdk.Resource { return map[string]*pluginsdk.Resource{ - "azurerm_signalr_service": resourceArmSignalRService(), + "azurerm_signalr_service": resourceArmSignalRService(), + "azurerm_signalr_service_network_acl": resourceArmSignalRServiceNetworkACL(), } } diff --git a/azurerm/internal/services/signalr/signalr_service_network_acl_resource.go b/azurerm/internal/services/signalr/signalr_service_network_acl_resource.go new file mode 100644 index 000000000000..005e876cd5c1 --- /dev/null +++ b/azurerm/internal/services/signalr/signalr_service_network_acl_resource.go @@ -0,0 +1,438 @@ +package signalr + +import ( + "fmt" + "time" + + "github.com/Azure/azure-sdk-for-go/services/signalr/mgmt/2020-05-01/signalr" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/locks" + networkValidate "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/network/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/signalr/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/signalr/validate" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/validation" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/timeouts" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +func resourceArmSignalRServiceNetworkACL() *pluginsdk.Resource { + return &pluginsdk.Resource{ + Create: resourceSignalRServiceNetworkACLCreateUpdate, + Read: resourceSignalRServiceNetworkACLRead, + Update: resourceSignalRServiceNetworkACLCreateUpdate, + Delete: resourceSignalRServiceNetworkACLDelete, + + Timeouts: &pluginsdk.ResourceTimeout{ + Create: pluginsdk.DefaultTimeout(30 * time.Minute), + Read: pluginsdk.DefaultTimeout(5 * time.Minute), + Update: pluginsdk.DefaultTimeout(30 * time.Minute), + Delete: pluginsdk.DefaultTimeout(30 * time.Minute), + }, + + Importer: pluginsdk.DefaultImporter(), + + Schema: map[string]*pluginsdk.Schema{ + "signalr_service_id": { + Type: pluginsdk.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validate.ServiceID, + }, + + "default_action": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice([]string{ + string(signalr.Allow), + string(signalr.Deny), + }, false), + }, + + "public_network": { + Type: pluginsdk.TypeList, + Required: true, + MaxItems: 1, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + // API response includes the `Trace` type but it isn't in rest api client. + // https://github.com/Azure/azure-rest-api-specs/issues/14923 + "allowed_request_types": { + Type: pluginsdk.TypeSet, + Optional: true, + ConflictsWith: []string{"public_network.0.denied_request_types"}, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(signalr.ClientConnection), + string(signalr.RESTAPI), + string(signalr.ServerConnection), + "Trace", + }, false), + }, + }, + + "denied_request_types": { + Type: pluginsdk.TypeSet, + Optional: true, + ConflictsWith: []string{"public_network.0.allowed_request_types"}, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(signalr.ClientConnection), + string(signalr.RESTAPI), + string(signalr.ServerConnection), + "Trace", + }, false), + }, + }, + }, + }, + }, + + "private_endpoint": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Resource{ + Schema: map[string]*pluginsdk.Schema{ + "id": { + Type: pluginsdk.TypeString, + Required: true, + ValidateFunc: networkValidate.PrivateEndpointID, + }, + + // API response includes the `Trace` type but it isn't in rest api client. + // https://github.com/Azure/azure-rest-api-specs/issues/14923 + "allowed_request_types": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(signalr.ClientConnection), + string(signalr.RESTAPI), + string(signalr.ServerConnection), + "Trace", + }, false), + }, + }, + + "denied_request_types": { + Type: pluginsdk.TypeSet, + Optional: true, + Elem: &pluginsdk.Schema{ + Type: pluginsdk.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + string(signalr.ClientConnection), + string(signalr.RESTAPI), + string(signalr.ServerConnection), + "Trace", + }, false), + }, + }, + }, + }, + }, + }, + } +} + +func resourceSignalRServiceNetworkACLCreateUpdate(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).SignalR.Client + ctx, cancel := timeouts.ForCreateUpdate(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ServiceID(d.Get("signalr_service_id").(string)) + if err != nil { + return err + } + + locks.ByName(id.SignalRName, "azurerm_signalr_service") + defer locks.UnlockByName(id.SignalRName, "azurerm_signalr_service") + + resp, err := client.Get(ctx, id.ResourceGroup, id.SignalRName) + if err != nil { + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + parameters := resp + + if props := resp.Properties; props != nil { + networkACL := &signalr.NetworkACLs{ + DefaultAction: signalr.ACLAction(d.Get("default_action").(string)), + PublicNetwork: expandSignalRServicePublicNetwork(d.Get("public_network").([]interface{})), + } + + if v, ok := d.GetOk("private_endpoint"); ok { + networkACL.PrivateEndpoints = expandSignalRServicePrivateEndpoint(v.(*pluginsdk.Set).List(), props.PrivateEndpointConnections) + } + + if networkACL.DefaultAction == signalr.Allow && len(*networkACL.PublicNetwork.Allow) != 0 { + return fmt.Errorf("when `default_action` is `Allow` for `public_network`, `allowed_request_types` cannot be specified") + } else if networkACL.DefaultAction == signalr.Deny && len(*networkACL.PublicNetwork.Deny) != 0 { + return fmt.Errorf("when `default_action` is `Deny` for `public_network`, `denied_request_types` cannot be specified") + } + + if networkACL.PrivateEndpoints != nil { + for _, privateEndpoint := range *networkACL.PrivateEndpoints { + if len(*privateEndpoint.Allow) != 0 && len(*privateEndpoint.Deny) != 0 { + return fmt.Errorf("`allowed_request_types` and `denied_request_types` cannot be set together for `private_endpoint`") + } + + if networkACL.DefaultAction == signalr.Allow && len(*privateEndpoint.Allow) != 0 { + return fmt.Errorf("when `default_action` is `Allow` for `private_endpoint`, `allowed_request_types` cannot be specified") + } else if networkACL.DefaultAction == signalr.Deny && len(*privateEndpoint.Deny) != 0 { + return fmt.Errorf("when `default_action` is `Deny` for `private_endpoint`, `denied_request_types` cannot be specified") + } + } + } + + parameters.Properties.NetworkACLs = networkACL + } + + future, err := client.Update(ctx, id.ResourceGroup, id.SignalRName, ¶meters) + if err != nil { + return fmt.Errorf("creating/updating NetworkACL for %s: %v", id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for completion of creating/updating NetworkACL for %s: %v", id, err) + } + + d.SetId(id.ID()) + + return resourceSignalRServiceNetworkACLRead(d, meta) +} + +func resourceSignalRServiceNetworkACLRead(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).SignalR.Client + ctx, cancel := timeouts.ForRead(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ServiceID(d.Id()) + if err != nil { + return err + } + + resp, err := client.Get(ctx, id.ResourceGroup, id.SignalRName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + d.SetId("") + return nil + } + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + d.Set("signalr_service_id", id.ID()) + + if props := resp.Properties; props != nil { + d.Set("default_action", props.NetworkACLs.DefaultAction) + + if err := d.Set("public_network", flattenSignalRServicePublicNetwork(props.NetworkACLs.PublicNetwork)); err != nil { + return fmt.Errorf("setting `public_network`: %+v", err) + } + + if err := d.Set("private_endpoint", flattenSignalRServicePrivateEndpoint(props.NetworkACLs.PrivateEndpoints, props.PrivateEndpointConnections)); err != nil { + return fmt.Errorf("setting `private_endpoint`: %+v", err) + } + } + + return nil +} + +func resourceSignalRServiceNetworkACLDelete(d *pluginsdk.ResourceData, meta interface{}) error { + client := meta.(*clients.Client).SignalR.Client + ctx, cancel := timeouts.ForDelete(meta.(*clients.Client).StopContext, d) + defer cancel() + + id, err := parse.ServiceID(d.Id()) + if err != nil { + return err + } + + locks.ByName(id.SignalRName, "azurerm_signalr_service") + defer locks.UnlockByName(id.SignalRName, "azurerm_signalr_service") + + resp, err := client.Get(ctx, id.ResourceGroup, id.SignalRName) + if err != nil { + if utils.ResponseWasNotFound(resp.Response) { + return nil + } + return fmt.Errorf("retrieving %s: %+v", *id, err) + } + + // As this isn't a real object, so it has to update NetworkACL to default settings for the delete operation + parameters := resp + + requestTypes := signalr.PossibleRequestTypeValues() + requestTypes = append(requestTypes, "Trace") + + networkACL := &signalr.NetworkACLs{ + DefaultAction: signalr.Deny, + PublicNetwork: &signalr.NetworkACL{ + Allow: &requestTypes, + }, + } + + if resp.Properties != nil && resp.Properties.NetworkACLs != nil && resp.Properties.NetworkACLs.PrivateEndpoints != nil { + privateEndpoints := make([]signalr.PrivateEndpointACL, 0) + for _, item := range *resp.Properties.NetworkACLs.PrivateEndpoints { + if item.Name != nil { + privateEndpoints = append(privateEndpoints, signalr.PrivateEndpointACL{ + Allow: &requestTypes, + Name: item.Name, + }) + } + } + networkACL.PrivateEndpoints = &privateEndpoints + } + + if parameters.Properties != nil { + parameters.Properties.NetworkACLs = networkACL + } + + future, err := client.Update(ctx, id.ResourceGroup, id.SignalRName, ¶meters) + if err != nil { + return fmt.Errorf("reverting NetworkACL to default settings for %s: %v", id, err) + } + + if err = future.WaitForCompletionRef(ctx, client.Client); err != nil { + return fmt.Errorf("waiting for completion of reverting NetworkACL to default settings for %s: %v", id, err) + } + + return nil +} + +func expandSignalRServicePublicNetwork(input []interface{}) *signalr.NetworkACL { + allowedRTs := make([]signalr.RequestType, 0) + deniedRTs := make([]signalr.RequestType, 0) + + if len(input) != 0 && input[0] != nil { + v := input[0].(map[string]interface{}) + + for _, item := range *(utils.ExpandStringSlice(v["allowed_request_types"].(*pluginsdk.Set).List())) { + allowedRTs = append(allowedRTs, (signalr.RequestType)(item)) + } + + for _, item := range *(utils.ExpandStringSlice(v["denied_request_types"].(*pluginsdk.Set).List())) { + deniedRTs = append(deniedRTs, (signalr.RequestType)(item)) + } + } + + return &signalr.NetworkACL{ + Allow: &allowedRTs, + Deny: &deniedRTs, + } +} + +func expandSignalRServicePrivateEndpoint(input []interface{}, privateEndpointConnections *[]signalr.PrivateEndpointConnection) *[]signalr.PrivateEndpointACL { + results := make([]signalr.PrivateEndpointACL, 0) + + if privateEndpointConnections != nil { + for _, privateEndpointConnection := range *privateEndpointConnections { + result := signalr.PrivateEndpointACL{ + Allow: &[]signalr.RequestType{}, + Deny: &[]signalr.RequestType{}, + } + + if privateEndpointConnection.Name != nil { + result.Name = utils.String(*privateEndpointConnection.Name) + } + + for _, item := range input { + v := item.(map[string]interface{}) + privateEndpointId := v["id"].(string) + + if privateEndpointConnection.PrivateEndpointConnectionProperties != nil && privateEndpointConnection.PrivateEndpointConnectionProperties.PrivateEndpoint != nil && privateEndpointConnection.PrivateEndpointConnectionProperties.PrivateEndpoint.ID != nil && privateEndpointId == *privateEndpointConnection.PrivateEndpointConnectionProperties.PrivateEndpoint.ID { + allowedRTs := make([]signalr.RequestType, 0) + for _, item := range *(utils.ExpandStringSlice(v["allowed_request_types"].(*pluginsdk.Set).List())) { + allowedRTs = append(allowedRTs, (signalr.RequestType)(item)) + } + result.Allow = &allowedRTs + + deniedRTs := make([]signalr.RequestType, 0) + for _, item := range *(utils.ExpandStringSlice(v["denied_request_types"].(*pluginsdk.Set).List())) { + deniedRTs = append(deniedRTs, (signalr.RequestType)(item)) + } + result.Deny = &deniedRTs + + break + } + } + + results = append(results, result) + } + } + + return &results +} + +func flattenSignalRServicePublicNetwork(input *signalr.NetworkACL) []interface{} { + if input == nil { + return make([]interface{}, 0) + } + + allowRequestTypes := make([]string, 0) + if input.Allow != nil { + for _, item := range *input.Allow { + allowRequestTypes = append(allowRequestTypes, (string)(item)) + } + } + allow := utils.FlattenStringSlice(&allowRequestTypes) + + deniedRequestTypes := make([]string, 0) + if input.Deny != nil { + for _, item := range *input.Deny { + deniedRequestTypes = append(deniedRequestTypes, (string)(item)) + } + } + deny := utils.FlattenStringSlice(&deniedRequestTypes) + + return []interface{}{ + map[string]interface{}{ + "allowed_request_types": allow, + "denied_request_types": deny, + }, + } +} + +func flattenSignalRServicePrivateEndpoint(input *[]signalr.PrivateEndpointACL, privateEndpointConnections *[]signalr.PrivateEndpointConnection) []interface{} { + results := make([]interface{}, 0) + if input == nil { + return results + } + + for _, item := range *input { + if privateEndpointConnections != nil { + for _, privateEndpointConnection := range *privateEndpointConnections { + if item.Name != nil && privateEndpointConnection.Name != nil && *item.Name == *privateEndpointConnection.Name && privateEndpointConnection.PrivateEndpointConnectionProperties != nil && privateEndpointConnection.PrivateEndpointConnectionProperties.PrivateEndpoint != nil && privateEndpointConnection.PrivateEndpointConnectionProperties.PrivateEndpoint.ID != nil { + allowedRequestTypes := make([]string, 0) + if item.Allow != nil { + for _, item := range *item.Allow { + allowedRequestTypes = append(allowedRequestTypes, (string)(item)) + } + } + allow := utils.FlattenStringSlice(&allowedRequestTypes) + + deniedRequestTypes := make([]string, 0) + if item.Deny != nil { + for _, item := range *item.Deny { + deniedRequestTypes = append(deniedRequestTypes, (string)(item)) + } + } + deny := utils.FlattenStringSlice(&deniedRequestTypes) + + results = append(results, map[string]interface{}{ + "id": *privateEndpointConnection.PrivateEndpointConnectionProperties.PrivateEndpoint.ID, + "allowed_request_types": allow, + "denied_request_types": deny, + }) + + break + } + } + } + } + + return results +} diff --git a/azurerm/internal/services/signalr/signalr_service_network_acl_resource_test.go b/azurerm/internal/services/signalr/signalr_service_network_acl_resource_test.go new file mode 100644 index 000000000000..c08b9ddd8fbc --- /dev/null +++ b/azurerm/internal/services/signalr/signalr_service_network_acl_resource_test.go @@ -0,0 +1,300 @@ +package signalr_test + +import ( + "context" + "fmt" + "testing" + + "github.com/Azure/azure-sdk-for-go/services/signalr/mgmt/2020-05-01/signalr" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/acceptance/check" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/clients" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/services/signalr/parse" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/internal/tf/pluginsdk" + "github.com/terraform-providers/terraform-provider-azurerm/azurerm/utils" +) + +type SignalRServiceNetworkACLResource struct{} + +func TestAccSignalRServiceNetworkACL_basic(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_signalr_service_network_acl", "test") + r := SignalRServiceNetworkACLResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSignalRServiceNetworkACL_complete(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_signalr_service_network_acl", "test") + r := SignalRServiceNetworkACLResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSignalRServiceNetworkACL_update(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_signalr_service_network_acl", "test") + r := SignalRServiceNetworkACLResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.basic(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func TestAccSignalRServiceNetworkACL_updateMultiplePrivateEndpoints(t *testing.T) { + data := acceptance.BuildTestData(t, "azurerm_signalr_service_network_acl", "test") + r := SignalRServiceNetworkACLResource{} + + data.ResourceTest(t, r, []acceptance.TestStep{ + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.multiplePrivateEndpoints(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + { + Config: r.complete(data), + Check: acceptance.ComposeTestCheckFunc( + check.That(data.ResourceName).ExistsInAzure(r), + ), + }, + data.ImportStep(), + }) +} + +func (r SignalRServiceNetworkACLResource) Exists(ctx context.Context, clients *clients.Client, state *pluginsdk.InstanceState) (*bool, error) { + id, err := parse.ServiceID(state.ID) + if err != nil { + return nil, err + } + + resp, err := clients.SignalR.Client.Get(ctx, id.ResourceGroup, id.SignalRName) + if err != nil { + return nil, fmt.Errorf("retrieving %s: %v", id.String(), err) + } + + requestTypes := signalr.PossibleRequestTypeValues() + requestTypes = append(requestTypes, "Trace") + + if resp.Properties != nil && resp.Properties.NetworkACLs != nil && resp.Properties.NetworkACLs.DefaultAction == signalr.Deny && resp.Properties.NetworkACLs.PublicNetwork != nil && len(*resp.Properties.NetworkACLs.PublicNetwork.Allow) == len(requestTypes) { + return utils.Bool(false), nil + } + + return utils.Bool(true), nil +} + +func (r SignalRServiceNetworkACLResource) basic(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_signalr_service_network_acl" "test" { + signalr_service_id = azurerm_signalr_service.test.id + default_action = "Deny" + + public_network { + allowed_request_types = ["ClientConnection"] + } +} +`, r.template(data)) +} + +func (r SignalRServiceNetworkACLResource) complete(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_network" "test" { + name = "acctest-vnet-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + address_space = ["10.5.0.0/16"] +} + +resource "azurerm_subnet" "test" { + name = "acctest-subnet-%d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.5.2.0/24"] + + enforce_private_link_endpoint_network_policies = true +} + +resource "azurerm_private_endpoint" "test" { + name = "acctest-pe-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + subnet_id = azurerm_subnet.test.id + + private_service_connection { + name = "psc-sig-test" + is_manual_connection = false + private_connection_resource_id = azurerm_signalr_service.test.id + subresource_names = ["signalr"] + } +} + +resource "azurerm_signalr_service_network_acl" "test" { + signalr_service_id = azurerm_signalr_service.test.id + default_action = "Allow" + + public_network { + denied_request_types = ["ClientConnection"] + } + + private_endpoint { + id = azurerm_private_endpoint.test.id + denied_request_types = ["ClientConnection"] + } +} +`, r.template(data), data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + +func (r SignalRServiceNetworkACLResource) multiplePrivateEndpoints(data acceptance.TestData) string { + return fmt.Sprintf(` +%s + +resource "azurerm_virtual_network" "test" { + name = "acctest-vnet-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + address_space = ["10.5.0.0/16"] +} + +resource "azurerm_subnet" "test" { + name = "acctest-subnet-%d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test.name + address_prefixes = ["10.5.2.0/24"] + + enforce_private_link_endpoint_network_policies = true +} + +resource "azurerm_private_endpoint" "test" { + name = "acctest-pe-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + subnet_id = azurerm_subnet.test.id + + private_service_connection { + name = "psc-sig-test" + is_manual_connection = false + private_connection_resource_id = azurerm_signalr_service.test.id + subresource_names = ["signalr"] + } +} + +resource "azurerm_virtual_network" "test2" { + name = "acctest-vnet2-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + address_space = ["10.5.0.0/16"] +} + +resource "azurerm_subnet" "test2" { + name = "acctest-subnet2-%d" + resource_group_name = azurerm_resource_group.test.name + virtual_network_name = azurerm_virtual_network.test2.name + address_prefixes = ["10.5.2.0/24"] + + enforce_private_link_endpoint_network_policies = true +} + +resource "azurerm_private_endpoint" "test2" { + name = "acctest-pe2-%d" + resource_group_name = azurerm_resource_group.test.name + location = azurerm_resource_group.test.location + subnet_id = azurerm_subnet.test2.id + + private_service_connection { + name = "psc-sig-test2" + is_manual_connection = false + private_connection_resource_id = azurerm_signalr_service.test.id + subresource_names = ["signalr"] + } +} + +resource "azurerm_signalr_service_network_acl" "test" { + signalr_service_id = azurerm_signalr_service.test.id + default_action = "Allow" + + public_network { + denied_request_types = ["ClientConnection"] + } + + private_endpoint { + id = azurerm_private_endpoint.test.id + denied_request_types = ["ClientConnection"] + } + + private_endpoint { + id = azurerm_private_endpoint.test2.id + denied_request_types = ["ServerConnection"] + } +} +`, r.template(data), data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger, data.RandomInteger) +} + +func (r SignalRServiceNetworkACLResource) template(data acceptance.TestData) string { + return fmt.Sprintf(` +provider "azurerm" { + features {} +} + +resource "azurerm_resource_group" "test" { + name = "acctestRG-signalr-%d" + location = "%s" +} + +resource "azurerm_signalr_service" "test" { + name = "acctest-signalr-%d" + location = azurerm_resource_group.test.location + resource_group_name = azurerm_resource_group.test.name + + sku { + name = "Standard_S1" + capacity = 1 + } +} + `, data.RandomInteger, data.Locations.Primary, data.RandomInteger) +} diff --git a/website/docs/r/signalr_service_network_acl.html.markdown b/website/docs/r/signalr_service_network_acl.html.markdown new file mode 100644 index 000000000000..7e6e27ccaa63 --- /dev/null +++ b/website/docs/r/signalr_service_network_acl.html.markdown @@ -0,0 +1,140 @@ +--- +subcategory: "Messaging" +layout: "azurerm" +page_title: "Azure Resource Manager: azurerm_signalr_service_network_acl" +description: |- + Manages the Network ACL for a SignalR service. +--- + +# azurerm_signalr_service_network_acl + +Manages the Network ACL for a SignalR service. + +## Example Usage + +```hcl +resource "azurerm_resource_group" "example" { + name = "example-resources" + location = "West Europe" +} + +resource "azurerm_signalr_service" "example" { + name = "example-signalr" + location = azurerm_resource_group.example.location + resource_group_name = azurerm_resource_group.example.name + + sku { + name = "Standard_S1" + capacity = 1 + } +} + +resource "azurerm_virtual_network" "example" { + name = "example-vnet" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + address_space = ["10.5.0.0/16"] +} + +resource "azurerm_subnet" "example" { + name = "example-subnet" + resource_group_name = azurerm_resource_group.example.name + virtual_network_name = azurerm_virtual_network.example.name + address_prefixes = ["10.5.2.0/24"] + + enforce_private_link_endpoint_network_policies = true +} + +resource "azurerm_private_endpoint" "example" { + name = "example-privateendpoint" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + subnet_id = azurerm_subnet.example.id + + private_service_connection { + name = "psc-sig-test" + is_manual_connection = false + private_connection_resource_id = azurerm_signalr_service.example.id + subresource_names = ["signalr"] + } +} + +resource "azurerm_signalr_service_network_acl" "example" { + signalr_service_id = azurerm_signalr_service.example.id + default_action = "Deny" + + public_network { + allowed_request_types = ["ClientConnection"] + } + + private_endpoint { + id = azurerm_private_endpoint.example.id + allowed_request_types = ["ServerConnection"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `signalr_service_id` - (Required) The ID of the SignalR service. Changing this forces a new resource to be created. + +* `default_action` - (Required) The default action to control the network access when no other rule matches. Possible values are `Allow` and `Deny`. + +* `public_network` - (Required) A `public_network` block as defined below. + +* `private_endpoint` - (Optional) A `private_endpoint` block as defined below. + +--- + +A `public_network` block supports the following: + +* `allowed_request_types` - (Optional) The allowed request types for the public network. Possible values are `ClientConnection`, `ServerConnection`, `RESTAPI` and `Trace`. + +**Note:** When `default_action` is `Allow`, `allowed_request_types`cannot be set. + +* `denied_request_types` - (Optional) The denied request types for the public network. Possible values are `ClientConnection`, `ServerConnection`, `RESTAPI` and `Trace`. + +**Note:** When `default_action` is `Deny`, `denied_request_types`cannot be set. + +**Note:** `allowed_request_types` and `denied_request_types` cannot be set together. + +--- + +A `private_endpoint` block supports the following: + +* `id` - (Required) The ID of the Private Endpoint which is based on the SignalR service. + +* `allowed_request_types` - (Optional) The allowed request types for the Private Endpoint Connection. Possible values are `ClientConnection`, `ServerConnection`, `RESTAPI` and `Trace`. + +**Note:** When `default_action` is `Allow`, `allowed_request_types`cannot be set. + +* `denied_request_types` - (Optional) The denied request types for the Private Endpoint Connection. Possible values are `ClientConnection`, `ServerConnection`, `RESTAPI` and `Trace`. + +**Note:** When `default_action` is `Deny`, `denied_request_types`cannot be set. + +**Note:** `allowed_request_types` and `denied_request_types` cannot be set together. + +## Attributes Reference + +In addition to the Arguments listed above - the following Attributes are exported: + +* `id` - The ID of the SignalR service. + +## Timeouts + +The `timeouts` block allows you to specify [timeouts](https://www.terraform.io/docs/configuration/resources.html#timeouts) for certain actions: + +* `create` - (Defaults to 30 minutes) Used when creating the Network ACL of the SignalR service +* `read` - (Defaults to 5 minutes) Used when retrieving the Network ACL of the SignalR service +* `update` - (Defaults to 30 minutes) Used when updating the Network ACL of the SignalR service +* `delete` - (Defaults to 30 minutes) Used when deleting the Network ACL of the SignalR service + +## Import + +Network ACLs for a SignalR service can be imported using the `resource id`, e.g. + +```shell +terraform import azurerm_signalr_service_network_acl.example /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/group1/providers/Microsoft.SignalRService/SignalR/signalr1 +```