Skip to content

Commit

Permalink
Solved issue 45: Add optional private endpoints for AI Services and S…
Browse files Browse the repository at this point in the history
…torage Accounts (#55)

* Added new output variable `virtual_network_id` in module `vnet` to return the unique identifier of the created virtual network.

* Updated `st` module to create an optional private endpoints infrastructure for Blog Storage and File Shares.

* - Now the Azure API Management is deployed with (External) VNet support when the `use_private_endpoints` variable is set to `true`.

* Add `use_private_endpoints` variable to APIM module.

* - Added support to set private endpoints for AI Services and Storage Account services. This private endpoints are set when the variable `use_private_endpoints` is `true`. Also, the current IP address of the machine executing these templates is used as a allowed IP address to facilitate managing these services.

* - Increased the size of the subnet for Private Endpoints, since currently near 9 services requires them.
- Increased the size of the subnet for the APIM as recommended by Microsoft's documentation.
- Also, added delegation in the subnet for the APIM as specified by Microsoft's documentation.

* - Improved names for Private Endpoints and related resources in Storage Account module.

* Add support for private endpoints and related variables in Cognitive Services module.

* Add support for private endpoints in Document Intelligence (a.k.a. `Forms`) module.

* Add support for private endpoints in OpenAI module.

* Add support for private endpoints in Azure AI Search module.

* Add dependency on `azapi_resource.apim_backend_pool` for `policy` resource in APIM module.

* Add support for purging soft delete on destroy for API Management in `providers.tf`

* Add support for HTTP provider version `3.4.2` from HashiCorp.

* Removed `form_recognizer` module. It is now created in the `cog` module.
  • Loading branch information
rliberoff authored May 31, 2024
1 parent cccb0f2 commit 5171865
Show file tree
Hide file tree
Showing 20 changed files with 451 additions and 56 deletions.
19 changes: 19 additions & 0 deletions infra/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 41 additions & 23 deletions infra/main.tf
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
data "azurerm_subscription" "current" {}

data "http" "current_ip" {
url = "http://ipv4.icanhazip.com"
count = var.use_private_endpoints ? 1 : 0
}

resource "random_id" "random" {
byte_length = 8
}

locals {
allowed_ips = var.use_private_endpoints ? concat(var.allowed_ips, ["${chomp(data.http.current_ip[0].response_body)}"]) : var.allowed_ips
sufix = var.use_random_suffix ? substr(lower(random_id.random.hex), 1, 5) : ""
name_sufix = var.use_random_suffix ? "-${local.sufix}" : ""
resource_group_name = "${var.resource_group_name}${local.name_sufix}"
Expand Down Expand Up @@ -66,7 +72,8 @@ module "apim" {
openai_service_name = module.openai.openai_service_name
openai_service_endpoint = module.openai.openai_endpoint
tenant_id = data.azurerm_subscription.current.tenant_id

use_private_endpoints = var.use_private_endpoints

depends_on = [module.nsg]
}

Expand All @@ -84,18 +91,15 @@ resource "azurerm_role_assignment" "id_reader" {
}

module "search" {
source = "./modules/search"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
search_name = local.search_name
principal_id = module.mi.principal_id
}

module "form_recognizer" {
source = "./modules/form"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
form_recognizer_name = local.form_recognizer_name
source = "./modules/search"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
search_name = local.search_name
principal_id = module.mi.principal_id
allowed_ips = local.allowed_ips
vnet_id = module.vnet.virtual_network_id
private_endpoints_subnet_id = module.vnet.pe_subnet_id
use_private_endpoints = var.use_private_endpoints
}

module "log" {
Expand All @@ -114,19 +118,28 @@ module "appi" {
}

module "st" {
source = "./modules/st"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
storage_account_name = local.storage_account_name
principal_id = module.mi.principal_id
source = "./modules/st"
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
storage_account_name = local.storage_account_name
principal_id = module.mi.principal_id
vnet_id = module.vnet.virtual_network_id
private_endpoints_subnet_id = module.vnet.pe_subnet_id
use_private_endpoints = var.use_private_endpoints
allowed_ips = local.allowed_ips
}

module "openai" {
source = "./modules/openai"
location = var.location_azopenai
resource_group_name = azurerm_resource_group.rg.name
azopenai_name = local.azopenai_name
principal_id = module.mi.principal_id
source = "./modules/openai"
location = var.location_azopenai
resource_group_name = azurerm_resource_group.rg.name
azopenai_name = local.azopenai_name
principal_id = module.mi.principal_id
allowed_ips = local.allowed_ips
vnet_id = module.vnet.virtual_network_id
vnet_location = azurerm_resource_group.rg.location
private_endpoints_subnet_id = module.vnet.pe_subnet_id
use_private_endpoints = var.use_private_endpoints
}

module "cog" {
Expand All @@ -137,12 +150,17 @@ module "cog" {
bing_name = local.bing_name
cognitive_services_name = local.cognitive_services_name
content_safety_name = local.content_safety_name
form_recognizer_name = local.form_recognizer_name
speech_name = local.speech_name
vision_name = local.vision_name
vision_location = var.location_azopenai
content_safety_storage_resource_id = module.st.storage_account_id
deploy_bing = var.deploy_bing
content_safety_location = var.location_content_safety
allowed_ips = local.allowed_ips
vnet_id = module.vnet.virtual_network_id
private_endpoints_subnet_id = module.vnet.pe_subnet_id
use_private_endpoints = var.use_private_endpoints
}

module "cae" {
Expand Down
6 changes: 5 additions & 1 deletion infra/modules/apim/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ resource "azapi_resource" "apim" {
publisherName = var.publisher_name
apiVersionConstraint = {}
developerPortalStatus = "Disabled"
virtualNetworkType = var.use_private_endpoints ? "External" : "None"
virtualNetworkConfiguration = var.use_private_endpoints ? {
subnetResourceId = var.apim_subnet_id
} : null
}
})
response_export_values = [
Expand Down Expand Up @@ -150,7 +154,7 @@ resource "azurerm_api_management_api_policy" "policy" {
</on-error>
</policies>
XML
depends_on = [azurerm_api_management_backend.openai]
depends_on = [azurerm_api_management_backend.openai, azapi_resource.apim_backend_pool]
}

# https://github.com/aavetis/azure-openai-logger/blob/main/README.md
Expand Down
1 change: 1 addition & 0 deletions infra/modules/apim/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ variable "appi_instrumentation_key" {}
variable "openai_service_name" {}
variable "openai_service_endpoint" {}
variable "tenant_id" {}
variable "use_private_endpoints" {}
163 changes: 163 additions & 0 deletions infra/modules/cog/main.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
resource "azurerm_cognitive_account" "form" {
name = var.form_recognizer_name
location = var.location
resource_group_name = var.resource_group_name
kind = "FormRecognizer"
sku_name = "S0"
public_network_access_enabled = true
custom_subdomain_name = var.form_recognizer_name
dynamic "network_acls" { # Only set network rules if private endpoints are used, adding allowed IPs to access the service
for_each = var.use_private_endpoints ? [1] : []
content {
default_action = "Deny"
ip_rules = var.allowed_ips
}
}
}

resource "azurerm_cognitive_account" "content_safety" {
name = var.content_safety_name
kind = "ContentSafety"
Expand All @@ -9,6 +26,13 @@ resource "azurerm_cognitive_account" "content_safety" {
identity {
type = "SystemAssigned"
}
dynamic "network_acls" { # Only set network rules if private endpoints are used, adding allowed IPs to access the service
for_each = var.use_private_endpoints ? [1] : []
content {
default_action = "Deny"
ip_rules = var.allowed_ips
}
}
}

resource "azurerm_cognitive_account" "cognitive" {
Expand All @@ -19,6 +43,13 @@ resource "azurerm_cognitive_account" "cognitive" {
resource_group_name = var.resource_group_name
public_network_access_enabled = true
custom_subdomain_name = var.cognitive_services_name
dynamic "network_acls" { # Only set network rules if private endpoints are used, adding allowed IPs to access the service
for_each = var.use_private_endpoints ? [1] : []
content {
default_action = "Deny"
ip_rules = var.allowed_ips
}
}
}

resource "azurerm_cognitive_account" "speech" {
Expand All @@ -29,6 +60,13 @@ resource "azurerm_cognitive_account" "speech" {
resource_group_name = var.resource_group_name
public_network_access_enabled = true
custom_subdomain_name = var.speech_name
dynamic "network_acls" { # Only set network rules if private endpoints are used, adding allowed IPs to access the service
for_each = var.use_private_endpoints ? [1] : []
content {
default_action = "Deny"
ip_rules = var.allowed_ips
}
}
}

resource "azurerm_cognitive_account" "vision" {
Expand All @@ -39,6 +77,13 @@ resource "azurerm_cognitive_account" "vision" {
resource_group_name = var.resource_group_name
public_network_access_enabled = true
custom_subdomain_name = var.vision_name
dynamic "network_acls" { # Only set network rules if private endpoints are used, adding allowed IPs to access the service
for_each = var.use_private_endpoints ? [1] : []
content {
default_action = "Deny"
ip_rules = var.allowed_ips
}
}
}

resource "azurerm_resource_group_template_deployment" "main" {
Expand Down Expand Up @@ -105,8 +150,126 @@ resource "azurerm_resource_group_template_deployment" "main" {
TEMPLATE
}

# Assign Cognitive Services identity to reader role on the storage account

resource "azurerm_role_assignment" "reader" {
scope = var.content_safety_storage_resource_id
role_definition_name = "Storage Blob Data Reader"
principal_id = azurerm_cognitive_account.content_safety.identity[0].principal_id
}

## Private endpoints

resource "azurerm_private_dns_zone" "private_dns_zone_cognitive" {
count = var.use_private_endpoints ? 1 : 0
name = "privatelink.cognitiveservices.azure.com"
resource_group_name = var.resource_group_name
}

resource "azurerm_private_dns_zone_virtual_network_link" "private_dns_zone_link_cognitive" {
count = var.use_private_endpoints ? 1 : 0
name = var.content_safety_name
resource_group_name = var.resource_group_name
private_dns_zone_name = azurerm_private_dns_zone.private_dns_zone_cognitive[0].name
virtual_network_id = var.vnet_id
}

resource "azurerm_private_endpoint" "pep_content_safety" {
count = var.use_private_endpoints ? 1 : 0
name = "pep-${var.content_safety_name}"
location = var.location
resource_group_name = var.resource_group_name
subnet_id = var.private_endpoints_subnet_id

private_service_connection {
name = "${var.content_safety_name}-safety-privateserviceconnection"
private_connection_resource_id = azurerm_cognitive_account.content_safety.id
is_manual_connection = false
subresource_names = ["account"]
}

private_dns_zone_group {
name = "${var.content_safety_name}-privatelink"
private_dns_zone_ids = [azurerm_private_dns_zone.private_dns_zone_cognitive[0].id]
}
}

resource "azurerm_private_endpoint" "pep_cognitive_services" {
count = var.use_private_endpoints ? 1 : 0
name = "pep-${var.cognitive_services_name}"
location = var.location
resource_group_name = var.resource_group_name
subnet_id = var.private_endpoints_subnet_id

private_service_connection {
name = "${var.cognitive_services_name}-privateserviceconnection"
private_connection_resource_id = azurerm_cognitive_account.cognitive.id
is_manual_connection = false
subresource_names = ["account"]
}

private_dns_zone_group {
name = "${var.cognitive_services_name}-privatelink"
private_dns_zone_ids = [azurerm_private_dns_zone.private_dns_zone_cognitive[0].id]
}
}

resource "azurerm_private_endpoint" "pep_speech" {
count = var.use_private_endpoints ? 1 : 0
name = "pep-${var.speech_name}"
location = var.location
resource_group_name = var.resource_group_name
subnet_id = var.private_endpoints_subnet_id

private_service_connection {
name = "${var.speech_name}-privateserviceconnection"
private_connection_resource_id = azurerm_cognitive_account.speech.id
is_manual_connection = false
subresource_names = ["account"]
}

private_dns_zone_group {
name = "${var.speech_name}-privatelink"
private_dns_zone_ids = [azurerm_private_dns_zone.private_dns_zone_cognitive[0].id]
}
}

resource "azurerm_private_endpoint" "pep_vision" {
count = var.use_private_endpoints ? 1 : 0
name = "pep-${var.vision_name}"
location = var.location
resource_group_name = var.resource_group_name
subnet_id = var.private_endpoints_subnet_id

private_service_connection {
name = "${var.vision_name}-privateserviceconnection"
private_connection_resource_id = azurerm_cognitive_account.vision.id
is_manual_connection = false
subresource_names = ["account"]
}

private_dns_zone_group {
name = "${var.vision_name}-privatelink"
private_dns_zone_ids = [azurerm_private_dns_zone.private_dns_zone_cognitive[0].id]
}
}

resource "azurerm_private_endpoint" "pep_form" {
count = var.use_private_endpoints ? 1 : 0
name = "pep-${var.form_recognizer_name}"
location = var.location
resource_group_name = var.resource_group_name
subnet_id = var.private_endpoints_subnet_id

private_service_connection {
name = "${var.form_recognizer_name}-privateserviceconnection"
private_connection_resource_id = azurerm_cognitive_account.form.id
is_manual_connection = false
subresource_names = ["account"]
}

private_dns_zone_group {
name = "${var.form_recognizer_name}-privatelink"
private_dns_zone_ids = [azurerm_private_dns_zone.private_dns_zone_cognitive[0].id]
}
}
4 changes: 4 additions & 0 deletions infra/modules/cog/outputs.tf
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,7 @@ output "vision_endpoint" {
output "vision_key" {
value = azurerm_cognitive_account.vision.primary_access_key
}

output "form_recognizer_name" {
value = azurerm_cognitive_account.form.name
}
5 changes: 5 additions & 0 deletions infra/modules/cog/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,15 @@ variable "resource_group_id" {}
variable "location" {}
variable "content_safety_name" {}
variable "cognitive_services_name" {}
variable "form_recognizer_name" {}
variable "speech_name" {}
variable "vision_name" {}
variable "vision_location" {}
variable "bing_name" {}
variable "deploy_bing" {}
variable "content_safety_storage_resource_id" {}
variable "content_safety_location" {}
variable "allowed_ips" {}
variable "vnet_id" {}
variable "private_endpoints_subnet_id" {}
variable "use_private_endpoints" {}
Loading

0 comments on commit 5171865

Please sign in to comment.