From b7d0d3fd2cd9e68efc975a71a23a25a5bed9624c Mon Sep 17 00:00:00 2001 From: John Downs Date: Fri, 25 Nov 2022 19:28:32 +1300 Subject: [PATCH 1/3] Add quickstart --- .../front-door.tf | 73 ++++ .../images/diagram.png | Bin 0 -> 52692 bytes .../outputs.tf | 3 + .../providers.tf | 20 ++ .../readme.md | 330 ++++++++++++++++++ .../resource-group.tf | 12 + .../storage-account.tf | 24 ++ .../variables.tf | 29 ++ 8 files changed, 491 insertions(+) create mode 100644 quickstart/101-front-door-premium-storage-blobs-private-link/front-door.tf create mode 100644 quickstart/101-front-door-premium-storage-blobs-private-link/images/diagram.png create mode 100644 quickstart/101-front-door-premium-storage-blobs-private-link/outputs.tf create mode 100644 quickstart/101-front-door-premium-storage-blobs-private-link/providers.tf create mode 100644 quickstart/101-front-door-premium-storage-blobs-private-link/readme.md create mode 100644 quickstart/101-front-door-premium-storage-blobs-private-link/resource-group.tf create mode 100644 quickstart/101-front-door-premium-storage-blobs-private-link/storage-account.tf create mode 100644 quickstart/101-front-door-premium-storage-blobs-private-link/variables.tf diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/front-door.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/front-door.tf new file mode 100644 index 00000000..1b475946 --- /dev/null +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/front-door.tf @@ -0,0 +1,73 @@ +locals { + front_door_profile_name = "MyFrontDoor" + front_door_sku_name = "Premium_AzureFrontDoor" // Must be premium for Private Link support. + front_door_endpoint_name = "afd-${lower(random_id.front_door_endpoint_name.hex)}" + front_door_origin_group_name = "MyOriginGroup" + front_door_origin_name = "MyBlobContainerOrigin" + front_door_route_name = "MyRoute" + front_door_origin_path = "/${var.storage_account_blob_container_name}" // The path to the blob container. +} + +resource "azurerm_cdn_frontdoor_profile" "my_front_door" { + name = local.front_door_profile_name + resource_group_name = azurerm_resource_group.my_resource_group.name + sku_name = local.front_door_sku_name +} + +resource "azurerm_cdn_frontdoor_endpoint" "my_endpoint" { + name = local.front_door_endpoint_name + cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.my_front_door.id +} + +resource "azurerm_cdn_frontdoor_origin_group" "my_origin_group" { + name = local.front_door_origin_group_name + cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.my_front_door.id + session_affinity_enabled = true + + load_balancing { + sample_size = 4 + successful_samples_required = 3 + } + + health_probe { + path = "/" + request_type = "HEAD" + protocol = "Https" + interval_in_seconds = 100 + } +} + +resource "azurerm_cdn_frontdoor_origin" "my_blob_container_origin" { + name = local.front_door_origin_name + cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.my_origin_group.id + + enabled = true + host_name = azurerm_storage_account.my_storage_account.primary_blob_host + http_port = 80 + https_port = 443 + origin_host_header = azurerm_storage_account.my_storage_account.primary_blob_host + priority = 1 + weight = 1000 + certificate_name_check_enabled = true + + private_link { + private_link_target_id = azurerm_storage_account.my_storage_account.id + target_type = "blob" + request_message = "Request access for Azure Front Door Private Link origin" + location = var.front_door_private_link_location + } +} + +resource "azurerm_cdn_frontdoor_route" "my_route" { + name = local.front_door_route_name + cdn_frontdoor_endpoint_id = azurerm_cdn_frontdoor_endpoint.my_endpoint.id + cdn_frontdoor_origin_group_id = azurerm_cdn_frontdoor_origin_group.my_origin_group.id + cdn_frontdoor_origin_ids = [azurerm_cdn_frontdoor_origin.my_blob_container_origin.id] + + supported_protocols = ["Http", "Https"] + patterns_to_match = ["/*"] + forwarding_protocol = "HttpsOnly" + link_to_default_domain = true + https_redirect_enabled = true + cdn_frontdoor_origin_path = local.front_door_origin_path +} diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/images/diagram.png b/quickstart/101-front-door-premium-storage-blobs-private-link/images/diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..79c505e44cccd8a36225284cde1292fdeb6a9655 GIT binary patch literal 52692 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îsWR*D@(} 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 w1A~f~8QO9m@Wt1$opaI>h(I;Vst01(}UssI20 literal 0 HcmV?d00001 diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/outputs.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/outputs.tf new file mode 100644 index 00000000..3642fe18 --- /dev/null +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/outputs.tf @@ -0,0 +1,3 @@ +output "frontDoorEndpointHostName" { + value = azurerm_cdn_frontdoor_endpoint.my_endpoint.host_name +} diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/providers.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/providers.tf new file mode 100644 index 00000000..c8990b6e --- /dev/null +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/providers.tf @@ -0,0 +1,20 @@ +# Configure the Azure provider +terraform { + required_providers { + azurerm = { + source = "hashicorp/azurerm" + version = "~> 3.27.0" + } + + random = { + source = "hashicorp/random" + version = "~> 3.4.3" + } + } + + required_version = ">= 1.1.0" +} + +provider "azurerm" { + features {} +} diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md b/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md new file mode 100644 index 00000000..78bfc359 --- /dev/null +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md @@ -0,0 +1,330 @@ +# Azure Front Door Premium with blob origin and Private Link + +This template deploys an [Azure Front Door Premium profile](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/cdn_frontdoor_profile) with an Azure Storage blob container origin, using a private endpoint to access the storage account. + +## Architecture + +![Architecture diagram showing traffic flowing through Front Door to the storage account.](images/diagram.png) + +The data flows through the solution are: + +1. The client establishes a connection to Azure Front Door by using a custom domain name. The client's connection terminates at a nearby Front Door point of presence (PoP). +1. The Front Door web application firewall (WAF) scans the request. If the WAF determines the request's risk level is too high, it blocks the request and Front Door returns an HTTP 403 error response. +1. If the Front Door PoP's cache contains a valid response for this request, Front Door returns the response immediately. +1. Otherwise, the PoP sends the request to the origin storage account, wherever it is in the world, by using Microsoft's backbone network. The PoP connects to the storage account by using a separate, long-lived, TCP connection. In this scenario, Private Link is used to securely connect to the storage account. +1. The storage account sends a response to the Front Door PoP. +1. When the PoP receives the response, it stores it in its cache for subsequent requests. +1. The PoP returns the response to the client. +1. Any requests directly to the storage account through the internet are blocked by the Azure Storage firewall. + +## Resources + +| Terraform Resource Type | Description | +| - | - | +| `azurerm_resource_group` | The resource group for all the deployed resources.| +| `azurerm_cdn_frontdoor_profile` | The Front Door profile. | +| `azurerm_cdn_frontdoor_endpoint` | The Front Door endpoint. | +| `azurerm_cdn_frontdoor_origin_group` | The Front Door origin group. | +| `azurerm_cdn_frontdoor_origin` | The Front Door origin, which refers to the storage account. | +| `azurerm_cdn_frontdoor_route` | The Front Door route. | +| `azurerm_storage_account` | The Azure Storage account. | +| `azurerm_storage_container` | The blob container within the Azure Storage account. | +| `random_id` | Two random identifier generators to generate a unique Front Door endpoint resource name and storage account name. | + +## Variables + +| Name | Description | Default Value | +|-|-|-| +| `location` | The location for all the deployed resources. | `westus3` | +| `front_door_private_link_location` | The location that the Private Link connection will terminate in when connecting to the origin. This must be one of the [locations in which Private Link origins are available for Front Door](https://learn.microsoft.com/azure/frontdoor/private-link#region-availability). | `westus3` | +| `resource_group_name` | The name of the resource group. | `FrontDoor` | +| `storage_account_tier` | The tier of the storage account. | `Standard` | +| `storage_account_replication_type` | The level of replication to be configured for the storage account. | `LRS` | +| `storage_account_blob_container_name` | The name of the blob contianer. | `mycontainer` | + +## Example + +```bash +$ terraform plan -out main.tfplan + +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # azurerm_cdn_frontdoor_endpoint.my_endpoint will be created + + resource "azurerm_cdn_frontdoor_endpoint" "my_endpoint" { + + cdn_frontdoor_profile_id = (known after apply) + + enabled = true + + host_name = (known after apply) + + id = (known after apply) + + name = (known after apply) + } + + # azurerm_cdn_frontdoor_origin.my_blob_container_origin will be created + + resource "azurerm_cdn_frontdoor_origin" "my_blob_container_origin" { + + cdn_frontdoor_origin_group_id = (known after apply) + + certificate_name_check_enabled = true + + enabled = true + + health_probes_enabled = (known after apply) + + host_name = (known after apply) + + http_port = 80 + + https_port = 443 + + id = (known after apply) + + name = "MyBlobContainerOrigin" + + origin_host_header = (known after apply) + + priority = 1 + + weight = 1000 + + + private_link { + + location = "westus3" + + private_link_target_id = (known after apply) + + request_message = "Request access for Azure Front Door Private Link origin" + + target_type = "blob" + } + } + + # azurerm_cdn_frontdoor_origin_group.my_origin_group will be created + + resource "azurerm_cdn_frontdoor_origin_group" "my_origin_group" { + + cdn_frontdoor_profile_id = (known after apply) + + id = (known after apply) + + name = "MyOriginGroup" + + restore_traffic_time_to_healed_or_new_endpoint_in_minutes = 10 + + session_affinity_enabled = true + + + health_probe { + + interval_in_seconds = 100 + + path = "/" + + protocol = "Https" + + request_type = "HEAD" + } + + + load_balancing { + + additional_latency_in_milliseconds = 50 + + sample_size = 4 + + successful_samples_required = 3 + } + } + + # azurerm_cdn_frontdoor_profile.my_front_door will be created + + resource "azurerm_cdn_frontdoor_profile" "my_front_door" { + + id = (known after apply) + + name = "MyFrontDoor" + + resource_group_name = "FrontDoor" + + resource_guid = (known after apply) + + response_timeout_seconds = 120 + + sku_name = "Premium_AzureFrontDoor" + } + + # azurerm_cdn_frontdoor_route.my_route will be created + + resource "azurerm_cdn_frontdoor_route" "my_route" { + + cdn_frontdoor_endpoint_id = (known after apply) + + cdn_frontdoor_origin_group_id = (known after apply) + + cdn_frontdoor_origin_ids = (known after apply) + + enabled = true + + forwarding_protocol = "HttpsOnly" + + https_redirect_enabled = true + + id = (known after apply) + + link_to_default_domain = true + + name = "MyRoute" + + patterns_to_match = [ + + "/*", + ] + + supported_protocols = [ + + "Http", + + "Https", + ] + } + + # azurerm_resource_group.my_resource_group will be created + + resource "azurerm_resource_group" "my_resource_group" { + + id = (known after apply) + + location = "westus3" + + name = "FrontDoor" + } + + # azurerm_storage_account.my_storage_account will be created + + resource "azurerm_storage_account" "my_storage_account" { + + access_tier = (known after apply) + + account_kind = "StorageV2" + + account_replication_type = "LRS" + + account_tier = "Standard" + + allow_nested_items_to_be_public = true + + cross_tenant_replication_enabled = true + + default_to_oauth_authentication = false + + enable_https_traffic_only = true + + id = (known after apply) + + infrastructure_encryption_enabled = false + + is_hns_enabled = false + + large_file_share_enabled = (known after apply) + + location = "westus3" + + min_tls_version = "TLS1_2" + + name = (known after apply) + + nfsv3_enabled = false + + primary_access_key = (sensitive value) + + primary_blob_connection_string = (sensitive value) + + primary_blob_endpoint = (known after apply) + + primary_blob_host = (known after apply) + + primary_connection_string = (sensitive value) + + primary_dfs_endpoint = (known after apply) + + primary_dfs_host = (known after apply) + + primary_file_endpoint = (known after apply) + + primary_file_host = (known after apply) + + primary_location = (known after apply) + + primary_queue_endpoint = (known after apply) + + primary_queue_host = (known after apply) + + primary_table_endpoint = (known after apply) + + primary_table_host = (known after apply) + + primary_web_endpoint = (known after apply) + + primary_web_host = (known after apply) + + public_network_access_enabled = false + + queue_encryption_key_type = "Service" + + resource_group_name = "FrontDoor" + + secondary_access_key = (sensitive value) + + secondary_blob_connection_string = (sensitive value) + + secondary_blob_endpoint = (known after apply) + + secondary_blob_host = (known after apply) + + secondary_connection_string = (sensitive value) + + secondary_dfs_endpoint = (known after apply) + + secondary_dfs_host = (known after apply) + + secondary_file_endpoint = (known after apply) + + secondary_file_host = (known after apply) + + secondary_location = (known after apply) + + secondary_queue_endpoint = (known after apply) + + secondary_queue_host = (known after apply) + + secondary_table_endpoint = (known after apply) + + secondary_table_host = (known after apply) + + secondary_web_endpoint = (known after apply) + + secondary_web_host = (known after apply) + + shared_access_key_enabled = true + + table_encryption_key_type = "Service" + + + blob_properties { + + change_feed_enabled = (known after apply) + + change_feed_retention_in_days = (known after apply) + + default_service_version = (known after apply) + + last_access_time_enabled = (known after apply) + + versioning_enabled = (known after apply) + + + container_delete_retention_policy { + + days = (known after apply) + } + + + cors_rule { + + allowed_headers = (known after apply) + + allowed_methods = (known after apply) + + allowed_origins = (known after apply) + + exposed_headers = (known after apply) + + max_age_in_seconds = (known after apply) + } + + + delete_retention_policy { + + days = (known after apply) + } + } + + + network_rules { + + bypass = (known after apply) + + default_action = "Deny" + + ip_rules = (known after apply) + + virtual_network_subnet_ids = (known after apply) + } + + + queue_properties { + + cors_rule { + + allowed_headers = (known after apply) + + allowed_methods = (known after apply) + + allowed_origins = (known after apply) + + exposed_headers = (known after apply) + + max_age_in_seconds = (known after apply) + } + + + hour_metrics { + + enabled = (known after apply) + + include_apis = (known after apply) + + retention_policy_days = (known after apply) + + version = (known after apply) + } + + + logging { + + delete = (known after apply) + + read = (known after apply) + + retention_policy_days = (known after apply) + + version = (known after apply) + + write = (known after apply) + } + + + minute_metrics { + + enabled = (known after apply) + + include_apis = (known after apply) + + retention_policy_days = (known after apply) + + version = (known after apply) + } + } + + + routing { + + choice = (known after apply) + + publish_internet_endpoints = (known after apply) + + publish_microsoft_endpoints = (known after apply) + } + + + share_properties { + + cors_rule { + + allowed_headers = (known after apply) + + allowed_methods = (known after apply) + + allowed_origins = (known after apply) + + exposed_headers = (known after apply) + + max_age_in_seconds = (known after apply) + } + + + retention_policy { + + days = (known after apply) + } + + + smb { + + authentication_types = (known after apply) + + channel_encryption_type = (known after apply) + + kerberos_ticket_encryption_type = (known after apply) + + multichannel_enabled = (known after apply) + + versions = (known after apply) + } + } + } + + # azurerm_storage_container.my_storage_container will be created + + resource "azurerm_storage_container" "my_storage_container" { + + container_access_type = "blob" + + has_immutability_policy = (known after apply) + + has_legal_hold = (known after apply) + + id = (known after apply) + + metadata = (known after apply) + + name = "mycontainer" + + resource_manager_id = (known after apply) + + storage_account_name = (known after apply) + } + + # random_id.front_door_endpoint_name will be created + + resource "random_id" "front_door_endpoint_name" { + + b64_std = (known after apply) + + b64_url = (known after apply) + + byte_length = 8 + + dec = (known after apply) + + hex = (known after apply) + + id = (known after apply) + } + + # random_id.storage_account_name will be created + + resource "random_id" "storage_account_name" { + + b64_std = (known after apply) + + b64_url = (known after apply) + + byte_length = 8 + + dec = (known after apply) + + hex = (known after apply) + + id = (known after apply) + } + +Plan: 10 to add, 0 to change, 0 to destroy. + +Changes to Outputs: + + frontDoorEndpointHostName = (known after apply) +``` diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/resource-group.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/resource-group.tf new file mode 100644 index 00000000..0a52e7a6 --- /dev/null +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/resource-group.tf @@ -0,0 +1,12 @@ +resource "azurerm_resource_group" "my_resource_group" { + name = var.resource_group_name + location = var.location +} + +resource "random_id" "storage_account_name" { + byte_length = 8 +} + +resource "random_id" "front_door_endpoint_name" { + byte_length = 8 +} diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/storage-account.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/storage-account.tf new file mode 100644 index 00000000..ac9d87fb --- /dev/null +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/storage-account.tf @@ -0,0 +1,24 @@ +locals { + storage_account_name = "stor${lower(random_id.storage_account_name.hex)}" +} +resource "azurerm_storage_account" "my_storage_account" { + name = local.storage_account_name + resource_group_name = azurerm_resource_group.my_resource_group.name + location = var.location + account_kind = "StorageV2" + account_tier = var.storage_account_tier + account_replication_type = var.storage_account_replication_type + enable_https_traffic_only = true + public_network_access_enabled = false + min_tls_version = "TLS1_2" + + network_rules { + default_action = "Deny" + } +} + +resource "azurerm_storage_container" "my_storage_container" { + name = var.storage_account_blob_container_name + storage_account_name = azurerm_storage_account.my_storage_account.name + container_access_type = "blob" +} diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/variables.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/variables.tf new file mode 100644 index 00000000..60677e6c --- /dev/null +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/variables.tf @@ -0,0 +1,29 @@ +variable "location" { + type = string + default = "westus3" +} + +variable "front_door_private_link_location" { + type = string + default = "westus3" +} + +variable "resource_group_name" { + type = string + default = "FrontDoor" +} + +variable "storage_account_tier" { + type = string + default = "Standard" +} + +variable "storage_account_replication_type" { + type = string + default = "LRS" +} + +variable "storage_account_blob_container_name" { + type = string + default = "mycontainer" +} \ No newline at end of file From 0bc2f2b309667b0fffb9ccd93d9d4504972ff3e7 Mon Sep 17 00:00:00 2001 From: John Downs Date: Fri, 25 Nov 2022 20:45:11 +1300 Subject: [PATCH 2/3] Updates --- .../front-door.tf | 71 +++++++++++++++++-- .../providers.tf | 5 ++ .../readme.md | 57 +++++++++++++-- .../storage-account.tf | 25 ++++--- .../variables.tf | 11 ++- 5 files changed, 148 insertions(+), 21 deletions(-) diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/front-door.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/front-door.tf index 1b475946..a5d74571 100644 --- a/quickstart/101-front-door-premium-storage-blobs-private-link/front-door.tf +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/front-door.tf @@ -1,11 +1,14 @@ locals { - front_door_profile_name = "MyFrontDoor" - front_door_sku_name = "Premium_AzureFrontDoor" // Must be premium for Private Link support. - front_door_endpoint_name = "afd-${lower(random_id.front_door_endpoint_name.hex)}" - front_door_origin_group_name = "MyOriginGroup" - front_door_origin_name = "MyBlobContainerOrigin" - front_door_route_name = "MyRoute" - front_door_origin_path = "/${var.storage_account_blob_container_name}" // The path to the blob container. + front_door_profile_name = "MyFrontDoor" + front_door_sku_name = "Premium_AzureFrontDoor" // Must be premium for Private Link support. + front_door_endpoint_name = "afd-${lower(random_id.front_door_endpoint_name.hex)}" + front_door_origin_group_name = "MyOriginGroup" + front_door_origin_name = "MyBlobContainerOrigin" + front_door_route_name = "MyRoute" + front_door_origin_path = "/${var.storage_account_blob_container_name}" // The path to the blob container. + front_door_custom_domain_name = "MyCustomDomain" + front_door_firewall_policy_name = "MyWAFPolicy" + front_door_security_policy_name = "MySecurityPolicy" } resource "azurerm_cdn_frontdoor_profile" "my_front_door" { @@ -70,4 +73,58 @@ resource "azurerm_cdn_frontdoor_route" "my_route" { link_to_default_domain = true https_redirect_enabled = true cdn_frontdoor_origin_path = local.front_door_origin_path + + cdn_frontdoor_custom_domain_ids = [ + azurerm_cdn_frontdoor_custom_domain.my_custom_domain.id + ] +} + +resource "azurerm_cdn_frontdoor_custom_domain" "my_custom_domain" { + name = local.front_door_custom_domain_name + cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.my_front_door.id + host_name = var.custom_domain_name + + tls { + certificate_type = "ManagedCertificate" + minimum_tls_version = "TLS12" + } +} + +resource "azurerm_cdn_frontdoor_firewall_policy" "my_waf_policy" { + name = local.front_door_firewall_policy_name + resource_group_name = azurerm_resource_group.my_resource_group.name + sku_name = local.front_door_sku_name + enabled = true + mode = var.waf_mode + + managed_rule { + type = "Microsoft_DefaultRuleSet" + version = "2.1" + action = "Block" + } + + managed_rule { + type = "Microsoft_BotManagerRuleSet" + version = "1.0" + action = "Block" + } +} + +resource "azurerm_cdn_frontdoor_security_policy" "my_security_policy" { + name = local.front_door_security_policy_name + cdn_frontdoor_profile_id = azurerm_cdn_frontdoor_profile.my_front_door.id + + security_policies { + firewall { + cdn_frontdoor_firewall_policy_id = azurerm_cdn_frontdoor_firewall_policy.my_waf_policy.id + + association { + patterns_to_match = ["/*"] + + domain { + cdn_frontdoor_domain_id = azurerm_cdn_frontdoor_custom_domain.my_custom_domain.id + } + } + } + } } diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/providers.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/providers.tf index c8990b6e..bc4d47d2 100644 --- a/quickstart/101-front-door-premium-storage-blobs-private-link/providers.tf +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/providers.tf @@ -6,6 +6,11 @@ terraform { version = "~> 3.27.0" } + azapi = { + source = "Azure/azapi" + version = "~> 1.1.0" + } + random = { source = "hashicorp/random" version = "~> 3.4.3" diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md b/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md index 78bfc359..86119091 100644 --- a/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md @@ -1,6 +1,6 @@ # Azure Front Door Premium with blob origin and Private Link -This template deploys an [Azure Front Door Premium profile](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/cdn_frontdoor_profile) with an Azure Storage blob container origin, using a private endpoint to access the storage account. +This quickstart deploys an [Azure Front Door Premium profile](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/cdn_frontdoor_profile) with an Azure Storage blob container origin, using a private endpoint to access the storage account. ## Architecture @@ -17,6 +17,32 @@ The data flows through the solution are: 1. The PoP returns the response to the client. 1. Any requests directly to the storage account through the internet are blocked by the Azure Storage firewall. +## Usage + +### Approve custom domain + +After you deploy the Terraform file, you need to validate your ownership of the custom domain by updating your DNS server. You must create a TXT record with the name specified in the `customDomainValidationDnsTxtRecordName` deployment output, and use the value specified in the `customDomainValidationDnsTxtRecordValue` deployment output. You must the validation before the time specified in the `customDomainValidationExpiry` deployment output. + +Front Door validates your domin ownership and updates the status automatically. You can monitor the validation process, or trigger an immediate validation, in the domain configuration in the Azure portal. + +Next, you should configure your DNS server with a CNAME record to direct the traffic to Front Door. You must create a CNAME record at the host name you specified in the `customDomainName` deployment parameter, and use the value specified in the `frontDoorEndpointHostName` deployment output. + +### Approve private endpoint connection + +You need to approve the private endpoint connection to your storage account. This step is necessary because the private endpoint created by Front Door is deployed into a Microsoft-owned Azure subscription, and cross-subscription private endpoint connections require explicit approval. To approve the private endpoint: + +1. Open the Azure portal and navigate to the storage account. +2. Click the **Networking** tab, and then click the **Private endpoint connections** tab. +3. Select the private endpoint that is awaiting approval, and click the **Approve** button. This can take a couple of minutes to complete. + +After approving the private endpoint, wait a few minutes before you attempt to access your Front Door endpoint to allow time for Front Door to propagate the settings throughout its network. + +### Access the storage account + +You can then access the Front Door endpoint. The hostname is emitted as an output from the deployment - the output is named `frontDoorEndpointHostName`. You should see a page saying _The specified resource does not exist_. This is returned by Azure Storage because no files have been uploaded to the blob container and therefore there is no content to show yet. If you see a different error page, wait a few minutes and try again. + +You can also attempt to access the Azure Storage blob hostname directly. The hostname is also emitted as an output from the deployment - the output is named `blobEndpointHostName`. You should see an error saying _This request is not authorized to perform this operation_ error, since your storage account is configured to not accepts requests that come from the internet. + ## Resources | Terraform Resource Type | Description | @@ -27,8 +53,11 @@ The data flows through the solution are: | `azurerm_cdn_frontdoor_origin_group` | The Front Door origin group. | | `azurerm_cdn_frontdoor_origin` | The Front Door origin, which refers to the storage account. | | `azurerm_cdn_frontdoor_route` | The Front Door route. | +| `azurerm_cdn_frontdoor_custom_domain` | The Front Door custom domain. See above for details on custom domain provisioning. | +| `azurerm_cdn_frontdoor_firewall_policy` | The WAF policy. | +| `azurerm_cdn_frontdoor_security_policy` | The Front Door security policy, which associates the WAF policy with the custom domain. | | `azurerm_storage_account` | The Azure Storage account. | -| `azurerm_storage_container` | The blob container within the Azure Storage account. | +| `azapi_resource` | The blob container within the Azure Storage account. | | `random_id` | Two random identifier generators to generate a unique Front Door endpoint resource name and storage account name. | ## Variables @@ -40,7 +69,9 @@ The data flows through the solution are: | `resource_group_name` | The name of the resource group. | `FrontDoor` | | `storage_account_tier` | The tier of the storage account. | `Standard` | | `storage_account_replication_type` | The level of replication to be configured for the storage account. | `LRS` | -| `storage_account_blob_container_name` | The name of the blob contianer. | `mycontainer` | +| `storage_account_blob_container_name` | The name of the blob container. | `mycontainer` | +| `waf_mode` | The mode that the WAF should be deployed using. In 'Prevention' mode, the WAF will block requests it detects as malicious. In 'Detection' mode, the WAF will not block requests and will simply log the request.' | `Prevention` | +| `custom_domain_name` | The custom domain name to associate with your Front Door endpoint. | | ## Example @@ -121,6 +152,7 @@ Terraform will perform the following actions: + cdn_frontdoor_endpoint_id = (known after apply) + cdn_frontdoor_origin_group_id = (known after apply) + cdn_frontdoor_origin_ids = (known after apply) + + cdn_frontdoor_origin_path = "/mycontainer" + enabled = true + forwarding_protocol = "HttpsOnly" + https_redirect_enabled = true @@ -225,9 +257,14 @@ Terraform will perform the following actions: + network_rules { + bypass = (known after apply) - + default_action = "Deny" + + default_action = (known after apply) + ip_rules = (known after apply) + virtual_network_subnet_ids = (known after apply) + + + private_link_access { + + endpoint_resource_id = (known after apply) + + endpoint_tenant_id = (known after apply) + } } + queue_properties { @@ -291,6 +328,16 @@ Terraform will perform the following actions: } } + # azurerm_storage_account_network_rules.my_network_rules will be created + + resource "azurerm_storage_account_network_rules" "my_network_rules" { + + bypass = (known after apply) + + default_action = "Deny" + + id = (known after apply) + + ip_rules = (known after apply) + + storage_account_id = (known after apply) + + virtual_network_subnet_ids = (known after apply) + } + # azurerm_storage_container.my_storage_container will be created + resource "azurerm_storage_container" "my_storage_container" { + container_access_type = "blob" @@ -323,7 +370,7 @@ Terraform will perform the following actions: + id = (known after apply) } -Plan: 10 to add, 0 to change, 0 to destroy. +Plan: 11 to add, 0 to change, 0 to destroy. Changes to Outputs: + frontDoorEndpointHostName = (known after apply) diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/storage-account.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/storage-account.tf index ac9d87fb..e9f0b4ef 100644 --- a/quickstart/101-front-door-premium-storage-blobs-private-link/storage-account.tf +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/storage-account.tf @@ -1,6 +1,7 @@ locals { storage_account_name = "stor${lower(random_id.storage_account_name.hex)}" } + resource "azurerm_storage_account" "my_storage_account" { name = local.storage_account_name resource_group_name = azurerm_resource_group.my_resource_group.name @@ -11,14 +12,22 @@ resource "azurerm_storage_account" "my_storage_account" { enable_https_traffic_only = true public_network_access_enabled = false min_tls_version = "TLS1_2" - - network_rules { - default_action = "Deny" - } } -resource "azurerm_storage_container" "my_storage_container" { - name = var.storage_account_blob_container_name - storage_account_name = azurerm_storage_account.my_storage_account.name - container_access_type = "blob" +resource "azurerm_storage_account_network_rules" "my_network_rules" { + storage_account_id = azurerm_storage_account.my_storage_account.id + + default_action = "Deny" +} + +// This resource uses the AzApi provider because of the issue described here: https://github.com/hashicorp/terraform-provider-azurerm/issues/2977 +resource "azapi_resource" "my_storage_container" { + name = var.storage_account_blob_container_name + parent_id = "${azurerm_storage_account.my_storage_account.id}/blobServices/default" + type = "Microsoft.Storage/storageAccounts/blobServices/containers@2021-04-01" + body = jsonencode({ + properties = { + publicAccess = "Blob" + } + }) } diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/variables.tf b/quickstart/101-front-door-premium-storage-blobs-private-link/variables.tf index 60677e6c..ed5a5a93 100644 --- a/quickstart/101-front-door-premium-storage-blobs-private-link/variables.tf +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/variables.tf @@ -26,4 +26,13 @@ variable "storage_account_replication_type" { variable "storage_account_blob_container_name" { type = string default = "mycontainer" -} \ No newline at end of file +} + +variable "waf_mode" { + type = string + default = "Prevention" +} + +variable "custom_domain_name" { + type = string +} From 06955e5e546e17f1ad7700787bdaabce288ed0ce Mon Sep 17 00:00:00 2001 From: John Downs Date: Fri, 25 Nov 2022 20:52:33 +1300 Subject: [PATCH 3/3] Update readme --- .../readme.md | 129 ++++++++++++++---- 1 file changed, 104 insertions(+), 25 deletions(-) diff --git a/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md b/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md index 86119091..245f9ece 100644 --- a/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md +++ b/quickstart/101-front-door-premium-storage-blobs-private-link/readme.md @@ -83,6 +83,50 @@ Terraform used the selected providers to generate the following execution plan. Terraform will perform the following actions: + # azapi_resource.my_storage_container will be created + + resource "azapi_resource" "my_storage_container" { + + body = jsonencode( + { + + properties = { + + publicAccess = "Blob" + } + } + ) + + id = (known after apply) + + ignore_casing = false + + ignore_missing_property = true + + location = (known after apply) + + name = "mycontainer" + + output = (known after apply) + + parent_id = (known after apply) + + schema_validation_enabled = true + + tags = (known after apply) + + type = "Microsoft.Storage/storageAccounts/blobServices/containers@2021-04-01" + + + identity { + + identity_ids = (known after apply) + + principal_id = (known after apply) + + tenant_id = (known after apply) + + type = (known after apply) + } + } + + # azurerm_cdn_frontdoor_custom_domain.my_custom_domain will be created + + resource "azurerm_cdn_frontdoor_custom_domain" "my_custom_domain" { + + cdn_frontdoor_profile_id = (known after apply) + + expiration_date = (known after apply) + + host_name = "my-custom-domain-name.com" + + id = (known after apply) + + name = "MyCustomDomain" + + validation_token = (known after apply) + + + tls { + + cdn_frontdoor_secret_id = (known after apply) + + certificate_type = "ManagedCertificate" + + minimum_tls_version = "TLS12" + } + } + # azurerm_cdn_frontdoor_endpoint.my_endpoint will be created + resource "azurerm_cdn_frontdoor_endpoint" "my_endpoint" { + cdn_frontdoor_profile_id = (known after apply) @@ -92,6 +136,28 @@ Terraform will perform the following actions: + name = (known after apply) } + # azurerm_cdn_frontdoor_firewall_policy.my_waf_policy will be created + + resource "azurerm_cdn_frontdoor_firewall_policy" "my_waf_policy" { + + enabled = true + + frontend_endpoint_ids = (known after apply) + + id = (known after apply) + + mode = "Prevention" + + name = "MyWAFPolicy" + + resource_group_name = "FrontDoor" + + sku_name = "Premium_AzureFrontDoor" + + + managed_rule { + + action = "Block" + + type = "Microsoft_DefaultRuleSet" + + version = "2.1" + } + + managed_rule { + + action = "Block" + + type = "Microsoft_BotManagerRuleSet" + + version = "1.0" + } + } + # azurerm_cdn_frontdoor_origin.my_blob_container_origin will be created + resource "azurerm_cdn_frontdoor_origin" "my_blob_container_origin" { + cdn_frontdoor_origin_group_id = (known after apply) @@ -149,25 +215,50 @@ Terraform will perform the following actions: # azurerm_cdn_frontdoor_route.my_route will be created + resource "azurerm_cdn_frontdoor_route" "my_route" { - + cdn_frontdoor_endpoint_id = (known after apply) - + cdn_frontdoor_origin_group_id = (known after apply) - + cdn_frontdoor_origin_ids = (known after apply) - + cdn_frontdoor_origin_path = "/mycontainer" - + enabled = true - + forwarding_protocol = "HttpsOnly" - + https_redirect_enabled = true - + id = (known after apply) - + link_to_default_domain = true - + name = "MyRoute" - + patterns_to_match = [ + + cdn_frontdoor_custom_domain_ids = (known after apply) + + cdn_frontdoor_endpoint_id = (known after apply) + + cdn_frontdoor_origin_group_id = (known after apply) + + cdn_frontdoor_origin_ids = (known after apply) + + cdn_frontdoor_origin_path = "/mycontainer" + + enabled = true + + forwarding_protocol = "HttpsOnly" + + https_redirect_enabled = true + + id = (known after apply) + + link_to_default_domain = true + + name = "MyRoute" + + patterns_to_match = [ + "/*", ] - + supported_protocols = [ + + supported_protocols = [ + "Http", + "Https", ] } + # azurerm_cdn_frontdoor_security_policy.my_security_policy will be created + + resource "azurerm_cdn_frontdoor_security_policy" "my_security_policy" { + + cdn_frontdoor_profile_id = (known after apply) + + id = (known after apply) + + name = "MySecurityPolicy" + + + security_policies { + + firewall { + + cdn_frontdoor_firewall_policy_id = (known after apply) + + + association { + + patterns_to_match = [ + + "/*", + ] + + + domain { + + active = (known after apply) + + cdn_frontdoor_domain_id = (known after apply) + } + } + } + } + } + # azurerm_resource_group.my_resource_group will be created + resource "azurerm_resource_group" "my_resource_group" { + id = (known after apply) @@ -338,18 +429,6 @@ Terraform will perform the following actions: + virtual_network_subnet_ids = (known after apply) } - # azurerm_storage_container.my_storage_container will be created - + resource "azurerm_storage_container" "my_storage_container" { - + container_access_type = "blob" - + has_immutability_policy = (known after apply) - + has_legal_hold = (known after apply) - + id = (known after apply) - + metadata = (known after apply) - + name = "mycontainer" - + resource_manager_id = (known after apply) - + storage_account_name = (known after apply) - } - # random_id.front_door_endpoint_name will be created + resource "random_id" "front_door_endpoint_name" { + b64_std = (known after apply) @@ -370,7 +449,7 @@ Terraform will perform the following actions: + id = (known after apply) } -Plan: 11 to add, 0 to change, 0 to destroy. +Plan: 14 to add, 0 to change, 0 to destroy. Changes to Outputs: + frontDoorEndpointHostName = (known after apply)