Menu

Virtual Geek

Tales from real IT system administrators world and non-production environment

Terraform module Azure function app with private endpoint and storage account

terraform microsoft azure function app service plan storage account private endpoint virtual network vnet resource group virtual private link private dns zone configuration deployment.png

In this article I posting terraform configuration script to deploy and configure complete function app with private endpoint and related resources in Microsoft Azure. This is complete configuration script for function app with private endpoint. With this configuration code it creates all below list of dependency resources automatically.

  • Linux Function App
  • Private Endpoint
  • Subnet
  • App Service Virtual Network Swift Connection
  • Private DNS Zone
  • Private DNS Zone Virtual Network Link
  • Resource Group
  • Role Assignment
  • App Service Plan
  • Storage Account
  • User Assigned Identity
  • Virtual Network

Following is the parent module structure code of entire Function App in Azure. All the resources will be created from scratch / ground up inside new Resource Group. In the file provide the all the information.

# Parent module information provided for FUNCTION APP and related Resources

provider "azurerm" {
  features {}
}

module "function_app" {
  source = "./module"
  resource_group = {
    name     = "dev1"
    location = "eastus"
  }
  virtual_network = {
    name          = "vcloud-lab-dev1"
    address_space = ["192.168.0.0/16"]
  }
  subnet = [
    {
      name               = "dev-subnet1"
      address_prefixes   = [] #["192.168.1.0/24"]
      service_delegation = "Microsoft.Web/serverFarms"
      delegation_name    = "web-serverfarms"
    },
    {
      name             = "dev-subnet2"
      address_prefixes = [] #["192.168.1.0/24"]
    }
  ]
  private_dns_zone        = "privatelink.azurewebsites.net"
  azurerm_role_assignment = ["Storage Account Contributor", "Storage Blob Data Contributor", "Storage File Data SMB Share Contributor"]
  storage_account = {
    name                     = "vcloudlabsadev1"
    account_tier             = "Standard"
    account_replication_type = "LRS"
  }
  function_app = {
    name                      = "vcloudlabfa"
    app_service_plan_name     = "vcloudlabasp"
    app_service_plan_os       = "Linux"
    app_service_plan_sku_name = "S1"
    app_settings = {
      "FUNCTIONS_WORKER_RUNTIME2" = "python"
      "FUNCTIONS_WORKER_RUNTIME3" = "python3"
    }
    user_assigned_identity_name = "vcloudlabuser"
  }
  python_version = "3.8"
}

Below is the actual child module code, which is doing all the deadlifts behind the scene.

# Parent module information provided for FUNCTION APP and related Resources

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
#Define Azure provider azurerm information for Terraform

provider "azurerm" {
  features {}
}

######################################

#All Variable sections

#Resource Group information
variable "resource_group" {
  type = object({
    name     = string
    location = string
  })
  default = {
    name     = "dev1"
    location = "eastus"
  }
}

#Virtual Network (vnet) information
variable "virtual_network" {
  type = object({
    name          = string
    address_space = list(string)

  })
  default = {
    name          = "vcloud-lab-dev1"
    address_space = ["192.168.0.0/16"]
  }
}

#Subnet inside Virtual Network information
variable "subnet" {
  type = list(object({
    name               = string
    address_prefixes   = list(string)
    delegation_name    = optional(string)
    service_delegation = optional(string)
  }))
  default = [
    {
      name               = "dev-subnet1"
      address_prefixes   = [] #["192.168.1.0/24"]
      service_delegation = "Microsoft.Web/serverFarms"
      delegation_name    = "web-serverfarms"
    },
    {
      name             = "dev-subnet2"
      address_prefixes = [] #["192.168.1.0/24"]
    }
  ]
}

#Private DNS Zone for Private Endpoint
variable "private_dns_zone" {
  default = "privatelink.azurewebsites.net"
}

#Storage Account details for Function App
variable "storage_account" {
  type = object({
    name                     = string
    account_tier             = string
    account_replication_type = string
  })
  default = {
    name                     = "vcloudlabsadev"
    account_tier             = "Standard"
    account_replication_type = "LRS"
  }
}

#App service plan and Function App details
variable "function_app" {
  type = object({
    name                        = string
    app_service_plan_name       = string
    app_service_plan_os         = string
    app_service_plan_sku_name   = string
    app_settings                = map(string) #object(string)
    user_assigned_identity_name = string
  })
  default = {
    name                      = "vcloudlabfa"
    app_service_plan_name     = "vcloudlabasp"
    app_service_plan_os       = "Linux" #expected os_type to be one of ["Linux" "Windows" "WindowsContainer"]
    app_service_plan_sku_name = "S1"
    app_settings = {
      "FUNCTIONS_WORKER_RUNTIME2" = "python"
      "FUNCTIONS_WORKER_RUNTIME3" = "python3"
    }
    user_assigned_identity_name = "vcloudlabuser"
  }

  # validation {
  #   condition = (
  #     # Validate 'name' is non-empty
  #     length(var.function_app.name) > 0 &&
  #     # Validate 'app_service_plan_name' is non-empty
  #     length(var.function_app.app_service_plan_name) > 0 &&
  #     # Validate 'function_app_os' is either 'linux' or 'windows'
  #     contains(["linux", "windows"], var.function_app.function_app_os) &&
  #     # Validate 'tier' is one of the acceptable values
  #     contains(["basic", "standard", "premium"], var.function_app.tier)
  #   )
  #   error_message = "Invalid configuration for function app. Ensure that 'name', 'app_service_plan_name', and 'tier' are non-empty, 'function_app_os' is either 'linux' or 'windows', and 'tier' is one of 'basic', 'standard', or 'premium'."
  # }

  #  validation {
  #     condition = (
  #       # Ensure that 'name' and 'app_service_plan_name' are non-empty
  #       length(var.function_app.name) > 0 &&
  #       length(var.function_app.app_service_plan_name) > 0 &&
  #       # Validate 'app_service_os' is either 'linux' or 'windows'
  #       contains(["linux", "windows"], var.function_app.app_service_os) &&
  #       # Ensure that 'app_settings' contains valid key-value pairs if needed
  #       alltrue([for k, v in var.function_app.app_settings : contains(["python", "python3"], v)])
  #     )
  #     error_message = "Invalid configuration. Ensure that 'name', 'app_service_plan_name' are non-empty, 'app_service_os' is either 'linux' or 'windows', and 'app_settings' values are valid."
  #   }
}

#Azurerm role assignment to User assigned identity over storage account
variable "azurerm_role_assignment" {
  type    = list(string)
  default = ["Storage Account Contributor", "Storage Blob Data Contributor", "Storage File Data SMB Share Contributor"]
}

############## Function App App Settings details for application stack ###################
variable "dotnet_version" {
  description = "The .NET version for the 'function app'"
  type        = string
  default     = null

  validation {
    condition = var.dotnet_version == null || (
      var.function_app.app_service_plan_os == "Linux" && can(regex("^(3\\.1|6\\.0|7\\.0|8\\.0)$", var.dotnet_version))
      ) || (
      var.function_app.app_service_plan_os == "Windows" && can(regex("^(3\\.1|5\\.0|7\\.0|8\\.0)$", var.dotnet_version))
    )
    error_message = "Valid options for Linux are 3.1, 6.0, 7.0, 8.0. Valid options for Windows are 3.1, 5.0, 6.0, 7.0, 8.0."
  }
}

variable "use_dotnet_isolated_runtime" {
  default = false
}

variable "java_version" {
  default = null
}

variable "node_version" {
  default = null
}

variable "python_version" {
  description = "The .NET version for the 'function app'"
  type        = string
  default     = "3.9"

  validation {
    condition = var.python_version == null || (
      lower(var.function_app.app_service_plan_os) == "linux" && can(regex("^(3\\.7|3\\.8|3\\.9|3\\.10)$", var.python_version))
      ) || (
      lower(var.function_app.app_service_plan_os) == "windows" && can(regex("^(3\\.7|3\\.8|3\\.9|3\\.10)$", var.python_version))
    )
    error_message = "Valid options for Linux are 3.7, 3.8, 3.9, 3.10. Valid options for Windows are 3.7, 3.8, 3.9, 3.10."
  }
}

variable "powershell_core_version" {
  default = null
}

variable "use_custom_runtime" {
  default = false
}

############## Function App App Settings details for application stack ###################

# variable "runtime_configuration" {
#   description = "Configuration for the function app runtime versions"
#   type = object({
#     dotnet_version            = string
#     use_dotnet_isolated_runtime = bool
#     java_version              = string
#     node_version              = string
#     python_version            = string
#     powershell_core_version   = string
#     use_custom_runtime        = bool
#   })
#   default = {
#     dotnet_version            = null
#     use_dotnet_isolated_runtime = false
#     java_version              = null
#     node_version              = null
#     python_version            = null
#     powershell_core_version   = null
#     use_custom_runtime        = false
#   }

#   validation {
#     condition = (
#       (
#         length([for v in [
#           var.runtime_configuration.dotnet_version,
#           var.runtime_configuration.java_version,
#           var.runtime_configuration.node_version,
#           var.runtime_configuration.python_version,
#           var.runtime_configuration.powershell_core_version
#         ] : v if v != null]) <= 1
#       ) && (
#         length([for b in [
#           var.runtime_configuration.use_dotnet_isolated_runtime,
#           var.runtime_configuration.use_custom_runtime
#         ] : b if b == true]) <= 1
#       )
#     )
#     error_message = "Only one runtime version or isolated runtime option can be set at a time."
#   }
# }

######################################


locals {
  calculated_subnets = {
    for i, subnet in var.subnet : subnet.name => { #change format from %02d-%s to %s-%02d to reflect correct name #format("%02d-%s", i, subnet.delegation_name)
      index            = i
      name             = subnet.name
      address_prefixes = [cidrsubnet(var.virtual_network.address_space[0], 8, i)]
      delegation_name  = subnet.delegation_name != null ? format("web-serverfarm-%02d", i + 1) : null # Adding 1 to make the index start from 1  #format("%02d-%s", i, subnet.delegation_name)
    }
  }
}

#######################################

resource "azurerm_resource_group" "rg" {
  name     = var.resource_group.name
  location = var.resource_group.location
}

resource "azurerm_virtual_network" "vnet" {
  name                = var.virtual_network.name
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  address_space       = var.virtual_network.address_space
}

resource "azurerm_subnet" "subnet" {
  for_each             = local.calculated_subnets
  name                 = each.value.name
  virtual_network_name = resource.azurerm_virtual_network.vnet.name
  resource_group_name  = azurerm_resource_group.rg.name
  address_prefixes     = each.value.address_prefixes

  # delegation {
  #   name = each.value.delegation_name

  #   service_delegation {
  #     name = "Microsoft.Web/serverFarms"
  #     #actions = ["Microsoft.Network/virtualNetworks/subnets/join/action", "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action"]
  #   }
  # }

  dynamic "delegation" {
    for_each = each.value.delegation_name != null ? [each.value.delegation_name] : []

    content {
      name = each.value.delegation_name

      service_delegation {
        name = "Microsoft.Web/serverFarms"
        # actions = ["Microsoft.Network/virtualNetworks/subnets/join/action", "Microsoft.Network/virtualNetworks/subnets/prepareNetworkPolicies/action"]
      }
    }
  }
}

resource "azurerm_private_dns_zone" "pdz" {
  name                = var.private_dns_zone
  resource_group_name = resource.azurerm_resource_group.rg.name
}

resource "azurerm_user_assigned_identity" "uai" {
  name                = var.function_app.user_assigned_identity_name
  resource_group_name = resource.azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
}

resource "azurerm_storage_account" "sa" {
  name                     = var.storage_account.name
  resource_group_name      = resource.azurerm_resource_group.rg.name
  location                 = azurerm_resource_group.rg.location
  account_tier             = var.storage_account.account_tier
  account_replication_type = var.storage_account.account_replication_type
  depends_on = [
    azurerm_subnet.subnet
  ]
}

# User mananged Privileges role assignments to storage account
resource "azurerm_role_assignment" "assignement" {
  for_each             = toset(var.azurerm_role_assignment)
  scope                = azurerm_storage_account.sa.id
  principal_id         = resource.azurerm_user_assigned_identity.uai.principal_id
  role_definition_name = each.value
}

resource "azurerm_service_plan" "asp" {
  name                = var.function_app.app_service_plan_name
  location            = resource.azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  os_type             = var.function_app.app_service_plan_os
  sku_name            = var.function_app.app_service_plan_sku_name
}

resource "azurerm_linux_function_app" "lfa" {
  name                 = var.function_app.name
  location             = resource.azurerm_resource_group.rg.location
  resource_group_name  = azurerm_resource_group.rg.name
  service_plan_id      = azurerm_service_plan.asp.id
  storage_account_name = azurerm_storage_account.sa.name
  #storage_account_access_key    = azurerm_storage_account.sa.primary_access_key   #When using system managed identity
  storage_uses_managed_identity = true #When using user managed identity
  public_network_access_enabled = false

  #tags = var.tags
  app_settings = merge(
    var.function_app.app_settings,
    {
      "FUNCTIONS_WORKER_RUNTIME" = "python"
    }
  )

  site_config {
    application_stack {
      dotnet_version = var.dotnet_version
      #use_dotnet_isolated_runtime = var.use_dotnet_isolated_runtime
      java_version            = var.java_version
      node_version            = var.node_version
      python_version          = var.python_version
      powershell_core_version = var.powershell_core_version
      #use_custom_runtime          = var.use_custom_runtime
    }
  }

  identity {
    type         = "UserAssigned" #When using user managed identity to access storage account / or use systemassigned
    identity_ids = [azurerm_user_assigned_identity.uai.id]
  }

  depends_on = [
    azurerm_resource_group.rg,
    resource.azurerm_storage_account.sa,
    azurerm_subnet.subnet,
    resource.azurerm_user_assigned_identity.uai
  ]
}

resource "azurerm_private_dns_zone_virtual_network_link" "pdzvnl" {
  name                  = "${var.function_app.name}-private-link"
  resource_group_name   = resource.azurerm_resource_group.rg.name
  private_dns_zone_name = resource.azurerm_private_dns_zone.pdz.name
  virtual_network_id    = azurerm_virtual_network.vnet.id
  registration_enabled  = true
  depends_on            = [resource.azurerm_linux_function_app.lfa, azurerm_private_dns_zone.pdz]
}

resource "azurerm_private_endpoint" "pe" {
  name                = "${var.function_app.name}-pe"
  resource_group_name = azurerm_resource_group.rg.name
  location            = resource.azurerm_resource_group.rg.location
  subnet_id           = azurerm_subnet.subnet[keys(azurerm_subnet.subnet)[1]].id

  private_service_connection {
    name                           = "${var.function_app.name}-pe-connection"
    private_connection_resource_id = resource.azurerm_linux_function_app.lfa.id
    subresource_names              = ["sites"]
    is_manual_connection           = false
  }

  private_dns_zone_group {
    name                 = "${resource.azurerm_linux_function_app.lfa.name}-dnz-zone-group"
    private_dns_zone_ids = [resource.azurerm_private_dns_zone.pdz.id]
  }

  depends_on = [resource.azurerm_linux_function_app.lfa, azurerm_subnet.subnet, azurerm_private_dns_zone.pdz]
}

##############

#For outbound calls to other services, subnet should have delegation access
resource "azurerm_app_service_virtual_network_swift_connection" "apsvnsc" {
  app_service_id = azurerm_linux_function_app.lfa.id
  subnet_id      = resource.azurerm_subnet.subnet[keys(azurerm_subnet.subnet)[0]].id
  depends_on     = [azurerm_linux_function_app.lfa, resource.azurerm_subnet.subnet]
}

##############

output "application_stack" {
  value     = resource.azurerm_linux_function_app.lfa.site_config[0].application_stack[0] #.node_version
  sensitive = false
}

output "app_settings" {
  value     = resource.azurerm_linux_function_app.lfa.app_settings
  sensitive = false
}

Once all the resources are deployed below is the screenshot of Overview page of Resource Group and output of terraform state list. All resources are created without hiccups.

Microsoft Azure terraform private dns zone, swift connection linux function app private dns zone virtual network link private endpoint role assignment sotrage account subnet virtual network user assigned identity vnet.png

Download this Terraform_Module_Function_App_Private_Endpoint.zip code here or it is also available on github.com.

Below is the output after deployment of the code, total 15 resources will be deployed.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
terraform apply --auto-approve

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:

  # module.function_app.azurerm_app_service_virtual_network_swift_connection.apsvnsc will be created
  + resource "azurerm_app_service_virtual_network_swift_connection" "apsvnsc" {
      + app_service_id = (known after apply)
      + id             = (known after apply)
      + subnet_id      = (known after apply)
    }

  # module.function_app.azurerm_linux_function_app.lfa will be created
  + resource "azurerm_linux_function_app" "lfa" {
      + app_settings                                   = {
          + "FUNCTIONS_WORKER_RUNTIME"  = "python"
          + "FUNCTIONS_WORKER_RUNTIME2" = "python"
          + "FUNCTIONS_WORKER_RUNTIME3" = "python3"
        }
      + builtin_logging_enabled                        = true
      + client_certificate_enabled                     = false
      + client_certificate_mode                        = "Optional"
      + content_share_force_disabled                   = false
      + custom_domain_verification_id                  = (sensitive value)
      + daily_memory_time_quota                        = 0
      + default_hostname                               = (known after apply)
      + enabled                                        = true
      + ftp_publish_basic_authentication_enabled       = true
      + functions_extension_version                    = "~4"
      + hosting_environment_id                         = (known after apply)
      + https_only                                     = false
      + id                                             = (known after apply)
      + key_vault_reference_identity_id                = (known after apply)
      + kind                                           = (known after apply)
      + location                                       = "eastus"
      + name                                           = "vcloudlabfa"
      + outbound_ip_address_list                       = (known after apply)
      + outbound_ip_addresses                          = (known after apply)
      + possible_outbound_ip_address_list              = (known after apply)
      + possible_outbound_ip_addresses                 = (known after apply)
      + public_network_access_enabled                  = false
      + resource_group_name                            = "dev1"
      + service_plan_id                                = (known after apply)
      + site_credential                                = (sensitive value)
      + storage_account_name                           = "vcloudlabsadev1"
      + storage_uses_managed_identity                  = true
      + webdeploy_publish_basic_authentication_enabled = true
      + zip_deploy_file                                = (known after apply)

      + identity {
          + identity_ids = (known after apply)
          + principal_id = (known after apply)
          + tenant_id    = (known after apply)
          + type         = "UserAssigned"
        }

      + site_config {
          + always_on                               = (known after apply)
          + app_scale_limit                         = (known after apply)
          + container_registry_use_managed_identity = false
          + default_documents                       = (known after apply)
          + detailed_error_logging_enabled          = (known after apply)
          + elastic_instance_minimum                = (known after apply)
          + ftps_state                              = "Disabled"
          + health_check_eviction_time_in_min       = (known after apply)
          + http2_enabled                           = false
          + ip_restriction_default_action           = "Allow"
          + linux_fx_version                        = (known after apply)
          + load_balancing_mode                     = "LeastRequests"
          + managed_pipeline_mode                   = "Integrated"
          + minimum_tls_version                     = "1.2"
          + pre_warmed_instance_count               = (known after apply)
          + remote_debugging_enabled                = false
          + remote_debugging_version                = (known after apply)
          + scm_ip_restriction_default_action       = "Allow"
          + scm_minimum_tls_version                 = "1.2"
          + scm_type                                = (known after apply)
          + scm_use_main_ip_restriction             = false
          + use_32_bit_worker                       = false
          + vnet_route_all_enabled                  = false
          + websockets_enabled                      = false
          + worker_count                            = (known after apply)

          + application_stack {
              + python_version              = "3.8"
              + use_dotnet_isolated_runtime = false
            }
        }
    }

  # module.function_app.azurerm_private_dns_zone.pdz will be created
  + resource "azurerm_private_dns_zone" "pdz" {
      + id                                                    = (known after apply)
      + max_number_of_record_sets                             = (known after apply)
      + max_number_of_virtual_network_links                   = (known after apply)
      + max_number_of_virtual_network_links_with_registration = (known after apply)
      + name                                                  = "privatelink.azurewebsites.net"
      + number_of_record_sets                                 = (known after apply)
      + resource_group_name                                   = "dev1"

      + soa_record (known after apply)
    }

  # module.function_app.azurerm_private_dns_zone_virtual_network_link.pdzvnl will be created
  + resource "azurerm_private_dns_zone_virtual_network_link" "pdzvnl" {
      + id                    = (known after apply)
      + name                  = "vcloudlabfa-private-link"
      + private_dns_zone_name = "privatelink.azurewebsites.net"
      + registration_enabled  = true
      + resource_group_name   = "dev1"
      + virtual_network_id    = (known after apply)
    }

  # module.function_app.azurerm_private_endpoint.pe will be created
  + resource "azurerm_private_endpoint" "pe" {
      + custom_dns_configs       = (known after apply)
      + id                       = (known after apply)
      + location                 = "eastus"
      + name                     = "vcloudlabfa-pe"
      + network_interface        = (known after apply)
      + private_dns_zone_configs = (known after apply)
      + resource_group_name      = "dev1"
      + subnet_id                = (known after apply)

      + private_dns_zone_group {
          + id                   = (known after apply)
          + name                 = "vcloudlabfa-dnz-zone-group"
          + private_dns_zone_ids = (known after apply)
        }

      + private_service_connection {
          + is_manual_connection           = false
          + name                           = "vcloudlabfa-pe-connection"
          + private_connection_resource_id = (known after apply)
          + private_ip_address             = (known after apply)
          + subresource_names              = [
              + "sites",
            ]
        }
    }

  # module.function_app.azurerm_resource_group.rg will be created
  + resource "azurerm_resource_group" "rg" {
      + id       = (known after apply)
      + location = "eastus"
      + name     = "dev1"
    }

  # module.function_app.azurerm_role_assignment.assignement["Storage Account Contributor"] will be created
  + resource "azurerm_role_assignment" "assignement" {
      + id                               = (known after apply)
      + name                             = (known after apply)
      + principal_id                     = (known after apply)
      + principal_type                   = (known after apply)
      + role_definition_id               = (known after apply)
      + role_definition_name             = "Storage Account Contributor"
      + scope                            = (known after apply)
      + skip_service_principal_aad_check = (known after apply)
    }

  # module.function_app.azurerm_role_assignment.assignement["Storage Blob Data Contributor"] will be created
  + resource "azurerm_role_assignment" "assignement" {
      + id                               = (known after apply)
      + name                             = (known after apply)
      + principal_id                     = (known after apply)
      + principal_type                   = (known after apply)
      + role_definition_id               = (known after apply)
      + role_definition_name             = "Storage Blob Data Contributor"
      + scope                            = (known after apply)
      + skip_service_principal_aad_check = (known after apply)
    }

  # module.function_app.azurerm_role_assignment.assignement["Storage File Data SMB Share Contributor"] will be created
  + resource "azurerm_role_assignment" "assignement" {
      + id                               = (known after apply)
      + name                             = (known after apply)
      + principal_id                     = (known after apply)
      + principal_type                   = (known after apply)
      + role_definition_id               = (known after apply)
      + role_definition_name             = "Storage File Data SMB Share Contributor"
      + scope                            = (known after apply)
      + skip_service_principal_aad_check = (known after apply)
    }

  # module.function_app.azurerm_service_plan.asp will be created
  + resource "azurerm_service_plan" "asp" {
      + id                           = (known after apply)
      + kind                         = (known after apply)
      + location                     = "eastus"
      + maximum_elastic_worker_count = (known after apply)
      + name                         = "vcloudlabasp"
      + os_type                      = "Linux"
      + per_site_scaling_enabled     = false
      + reserved                     = (known after apply)
      + resource_group_name          = "dev1"
      + sku_name                     = "S1"
      + worker_count                 = (known after apply)
    }

  # module.function_app.azurerm_storage_account.sa will be created
  + resource "azurerm_storage_account" "sa" {
      + 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
      + dns_endpoint_type                  = "Standard"
      + enable_https_traffic_only          = (known after apply)
      + https_traffic_only_enabled         = (known after apply)
      + id                                 = (known after apply)
      + infrastructure_encryption_enabled  = false
      + is_hns_enabled                     = false
      + large_file_share_enabled           = (known after apply)
      + local_user_enabled                 = true
      + location                           = "eastus"
      + min_tls_version                    = "TLS1_2"
      + name                               = "vcloudlabsadev1"
      + 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_blob_internet_endpoint     = (known after apply)
      + primary_blob_internet_host         = (known after apply)
      + primary_blob_microsoft_endpoint    = (known after apply)
      + primary_blob_microsoft_host        = (known after apply)
      + primary_connection_string          = (sensitive value)
      + primary_dfs_endpoint               = (known after apply)
      + primary_dfs_host                   = (known after apply)
      + primary_dfs_internet_endpoint      = (known after apply)
      + primary_dfs_internet_host          = (known after apply)
      + primary_dfs_microsoft_endpoint     = (known after apply)
      + primary_dfs_microsoft_host         = (known after apply)
      + primary_file_endpoint              = (known after apply)
      + primary_file_host                  = (known after apply)
      + primary_file_internet_endpoint     = (known after apply)
      + primary_file_internet_host         = (known after apply)
      + primary_file_microsoft_endpoint    = (known after apply)
      + primary_file_microsoft_host        = (known after apply)
      + primary_location                   = (known after apply)
      + primary_queue_endpoint             = (known after apply)
      + primary_queue_host                 = (known after apply)
      + primary_queue_microsoft_endpoint   = (known after apply)
      + primary_queue_microsoft_host       = (known after apply)
      + primary_table_endpoint             = (known after apply)
      + primary_table_host                 = (known after apply)
      + primary_table_microsoft_endpoint   = (known after apply)
      + primary_table_microsoft_host       = (known after apply)
      + primary_web_endpoint               = (known after apply)
      + primary_web_host                   = (known after apply)
      + primary_web_internet_endpoint      = (known after apply)
      + primary_web_internet_host          = (known after apply)
      + primary_web_microsoft_endpoint     = (known after apply)
      + primary_web_microsoft_host         = (known after apply)
      + public_network_access_enabled      = true
      + queue_encryption_key_type          = "Service"
      + resource_group_name                = "dev1"
      + secondary_access_key               = (sensitive value)
      + secondary_blob_connection_string   = (sensitive value)
      + secondary_blob_endpoint            = (known after apply)
      + secondary_blob_host                = (known after apply)
      + secondary_blob_internet_endpoint   = (known after apply)
      + secondary_blob_internet_host       = (known after apply)
      + secondary_blob_microsoft_endpoint  = (known after apply)
      + secondary_blob_microsoft_host      = (known after apply)
      + secondary_connection_string        = (sensitive value)
      + secondary_dfs_endpoint             = (known after apply)
      + secondary_dfs_host                 = (known after apply)
      + secondary_dfs_internet_endpoint    = (known after apply)
      + secondary_dfs_internet_host        = (known after apply)
      + secondary_dfs_microsoft_endpoint   = (known after apply)
      + secondary_dfs_microsoft_host       = (known after apply)
      + secondary_file_endpoint            = (known after apply)
      + secondary_file_host                = (known after apply)
      + secondary_file_internet_endpoint   = (known after apply)
      + secondary_file_internet_host       = (known after apply)
      + secondary_file_microsoft_endpoint  = (known after apply)
      + secondary_file_microsoft_host      = (known after apply)
      + secondary_location                 = (known after apply)
      + secondary_queue_endpoint           = (known after apply)
      + secondary_queue_host               = (known after apply)
      + secondary_queue_microsoft_endpoint = (known after apply)
      + secondary_queue_microsoft_host     = (known after apply)
      + secondary_table_endpoint           = (known after apply)
      + secondary_table_host               = (known after apply)
      + secondary_table_microsoft_endpoint = (known after apply)
      + secondary_table_microsoft_host     = (known after apply)
      + secondary_web_endpoint             = (known after apply)
      + secondary_web_host                 = (known after apply)
      + secondary_web_internet_endpoint    = (known after apply)
      + secondary_web_internet_host        = (known after apply)
      + secondary_web_microsoft_endpoint   = (known after apply)
      + secondary_web_microsoft_host       = (known after apply)
      + sftp_enabled                       = false
      + shared_access_key_enabled          = true
      + table_encryption_key_type          = "Service"

      + blob_properties (known after apply)

      + network_rules (known after apply)

      + queue_properties (known after apply)

      + routing (known after apply)

      + share_properties (known after apply)
    }

  # module.function_app.azurerm_subnet.subnet["dev-subnet1"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "192.168.0.0/24",
        ]
      + default_outbound_access_enabled                = true
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "dev-subnet1"
      + private_endpoint_network_policies              = (known after apply)
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "dev1"
      + virtual_network_name                           = "vcloud-lab-dev1"

      + delegation {
          + name = "web-serverfarm-01"

          + service_delegation {
              + name = "Microsoft.Web/serverFarms"
            }
        }
    }

  # module.function_app.azurerm_subnet.subnet["dev-subnet2"] will be created
  + resource "azurerm_subnet" "subnet" {
      + address_prefixes                               = [
          + "192.168.1.0/24",
        ]
      + default_outbound_access_enabled                = true
      + enforce_private_link_endpoint_network_policies = (known after apply)
      + enforce_private_link_service_network_policies  = (known after apply)
      + id                                             = (known after apply)
      + name                                           = "dev-subnet2"
      + private_endpoint_network_policies              = (known after apply)
      + private_endpoint_network_policies_enabled      = (known after apply)
      + private_link_service_network_policies_enabled  = (known after apply)
      + resource_group_name                            = "dev1"
      + virtual_network_name                           = "vcloud-lab-dev1"
    }

  # module.function_app.azurerm_user_assigned_identity.uai will be created
  + resource "azurerm_user_assigned_identity" "uai" {
      + client_id           = (known after apply)
      + id                  = (known after apply)
      + location            = "eastus"
      + name                = "vcloudlabuser"
      + principal_id        = (known after apply)
      + resource_group_name = "dev1"
      + tenant_id           = (known after apply)
    }

  # module.function_app.azurerm_virtual_network.vnet will be created
  + resource "azurerm_virtual_network" "vnet" {
      + address_space       = [
          + "192.168.0.0/16",
        ]
      + dns_servers         = (known after apply)
      + guid                = (known after apply)
      + id                  = (known after apply)
      + location            = "eastus"
      + name                = "vcloud-lab-dev1"
      + resource_group_name = "dev1"
      + subnet              = (known after apply)
    }

Plan: 15 to add, 0 to change, 0 to destroy.
module.function_app.azurerm_resource_group.rg: Creating...
module.function_app.azurerm_resource_group.rg: Still creating... [10s elapsed]
module.function_app.azurerm_resource_group.rg: Creation complete after 13s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1]
module.function_app.azurerm_user_assigned_identity.uai: Creating...
module.function_app.azurerm_private_dns_zone.pdz: Creating...
module.function_app.azurerm_virtual_network.vnet: Creating...
module.function_app.azurerm_service_plan.asp: Creating...
module.function_app.azurerm_user_assigned_identity.uai: Creation complete after 5s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.ManagedIdentity/userAssignedIdentities/vcloudlabuser]
module.function_app.azurerm_virtual_network.vnet: Creation complete after 8s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Network/virtualNetworks/vcloud-lab-dev1]
module.function_app.azurerm_subnet.subnet["dev-subnet2"]: Creating...
module.function_app.azurerm_subnet.subnet["dev-subnet1"]: Creating...
module.function_app.azurerm_service_plan.asp: Still creating... [10s elapsed]
module.function_app.azurerm_private_dns_zone.pdz: Still creating... [10s elapsed]
module.function_app.azurerm_subnet.subnet["dev-subnet2"]: Creation complete after 7s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Network/virtualNetworks/vcloud-lab-dev1/subnets/dev-subnet2]
module.function_app.azurerm_subnet.subnet["dev-subnet1"]: Still creating... [10s elapsed]
module.function_app.azurerm_private_dns_zone.pdz: Still creating... [20s elapsed]
module.function_app.azurerm_service_plan.asp: Still creating... [20s elapsed]
module.function_app.azurerm_subnet.subnet["dev-subnet1"]: Creation complete after 13s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Network/virtualNetworks/vcloud-lab-dev1/subnets/dev-subnet1]
module.function_app.azurerm_storage_account.sa: Creating...
module.function_app.azurerm_service_plan.asp: Creation complete after 24s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Web/serverFarms/vcloudlabasp]
module.function_app.azurerm_private_dns_zone.pdz: Still creating... [30s elapsed]
module.function_app.azurerm_storage_account.sa: Still creating... [10s elapsed]
module.function_app.azurerm_private_dns_zone.pdz: Creation complete after 36s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Network/privateDnsZones/privatelink.azurewebsites.net]
module.function_app.azurerm_storage_account.sa: Still creating... [20s elapsed]
module.function_app.azurerm_storage_account.sa: Still creating... [30s elapsed]
module.function_app.azurerm_storage_account.sa: Still creating... [40s elapsed]
module.function_app.azurerm_storage_account.sa: Still creating... [50s elapsed]
module.function_app.azurerm_storage_account.sa: Still creating... [1m0s elapsed]
module.function_app.azurerm_storage_account.sa: Still creating... [1m10s elapsed]
module.function_app.azurerm_storage_account.sa: Creation complete after 1m13s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Storage/storageAccounts/vcloudlabsadev1]
module.function_app.azurerm_role_assignment.assignement["Storage Account Contributor"]: Creating...
module.function_app.azurerm_role_assignment.assignement["Storage File Data SMB Share Contributor"]: Creating...
module.function_app.azurerm_role_assignment.assignement["Storage Blob Data Contributor"]: Creating...
module.function_app.azurerm_linux_function_app.lfa: Creating...
module.function_app.azurerm_role_assignment.assignement["Storage Account Contributor"]: Still creating... [10s elapsed]
module.function_app.azurerm_role_assignment.assignement["Storage Blob Data Contributor"]: Still creating... [10s elapsed]
module.function_app.azurerm_role_assignment.assignement["Storage File Data SMB Share Contributor"]: Still creating... [10s elapsed]
module.function_app.azurerm_linux_function_app.lfa: Still creating... [10s elapsed]
module.function_app.azurerm_role_assignment.assignement["Storage File Data SMB Share Contributor"]: Still creating... [20s elapsed]
module.function_app.azurerm_role_assignment.assignement["Storage Account Contributor"]: Still creating... [20s elapsed]
module.function_app.azurerm_role_assignment.assignement["Storage Blob Data Contributor"]: Still creating... [20s elapsed]
module.function_app.azurerm_linux_function_app.lfa: Still creating... [20s elapsed]
module.function_app.azurerm_role_assignment.assignement["Storage File Data SMB Share Contributor"]: Creation complete after 27s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Storage/storageAccounts/vcloudlabsadev1/providers/Microsoft.Authorization/roleAssignments/c0168068-4a9c-1ac2-18af-083cd51a2fc6]
module.function_app.azurerm_role_assignment.assignement["Storage Blob Data Contributor"]: Creation complete after 27s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Storage/storageAccounts/vcloudlabsadev1/providers/Microsoft.Authorization/roleAssignments/6e71cc4c-13dd-dca1-5596-df7d8a94eb1f]
module.function_app.azurerm_role_assignment.assignement["Storage Account Contributor"]: Creation complete after 29s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Storage/storageAccounts/vcloudlabsadev1/providers/Microsoft.Authorization/roleAssignments/5f2910e0-acf5-1ac5-29c2-d99115775a9a]
module.function_app.azurerm_linux_function_app.lfa: Still creating... [30s elapsed]
module.function_app.azurerm_linux_function_app.lfa: Still creating... [40s elapsed]
module.function_app.azurerm_linux_function_app.lfa: Still creating... [50s elapsed]
module.function_app.azurerm_linux_function_app.lfa: Still creating... [1m0s elapsed]
module.function_app.azurerm_linux_function_app.lfa: Still creating... [1m10s elapsed]
module.function_app.azurerm_linux_function_app.lfa: Creation complete after 1m18s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Web/sites/vcloudlabfa]
module.function_app.azurerm_private_dns_zone_virtual_network_link.pdzvnl: Creating...
module.function_app.azurerm_app_service_virtual_network_swift_connection.apsvnsc: Creating...
module.function_app.azurerm_private_endpoint.pe: Creating...
module.function_app.azurerm_private_dns_zone_virtual_network_link.pdzvnl: Still creating... [10s elapsed]
module.function_app.azurerm_app_service_virtual_network_swift_connection.apsvnsc: Still creating... [10s elapsed]
module.function_app.azurerm_private_endpoint.pe: Still creating... [10s elapsed]
module.function_app.azurerm_app_service_virtual_network_swift_connection.apsvnsc: Still creating... [20s elapsed]
module.function_app.azurerm_private_dns_zone_virtual_network_link.pdzvnl: Still creating... [20s elapsed]
module.function_app.azurerm_private_endpoint.pe: Still creating... [20s elapsed]
module.function_app.azurerm_private_dns_zone_virtual_network_link.pdzvnl: Still creating... [30s elapsed]
module.function_app.azurerm_app_service_virtual_network_swift_connection.apsvnsc: Still creating... [30s elapsed]
module.function_app.azurerm_private_endpoint.pe: Still creating... [30s elapsed]
module.function_app.azurerm_app_service_virtual_network_swift_connection.apsvnsc: Creation complete after 39s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Web/sites/vcloudlabfa/config/virtualNetwork]
module.function_app.azurerm_private_dns_zone_virtual_network_link.pdzvnl: Still creating... [40s elapsed]
module.function_app.azurerm_private_endpoint.pe: Still creating... [40s elapsed]
module.function_app.azurerm_private_dns_zone_virtual_network_link.pdzvnl: Still creating... [50s elapsed]
module.function_app.azurerm_private_endpoint.pe: Still creating... [50s elapsed]
module.function_app.azurerm_private_dns_zone_virtual_network_link.pdzvnl: Still creating... [1m0s elapsed]
module.function_app.azurerm_private_endpoint.pe: Still creating... [1m0s elapsed]
module.function_app.azurerm_private_dns_zone_virtual_network_link.pdzvnl: Creation complete after 1m6s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Network/privateDnsZones/privatelink.azurewebsites.net/virtualNetworkLinks/vcloudlabfa-private-link]
module.function_app.azurerm_private_endpoint.pe: Still creating... [1m10s elapsed]
module.function_app.azurerm_private_endpoint.pe: Still creating... [1m20s elapsed]
module.function_app.azurerm_private_endpoint.pe: Still creating... [1m30s elapsed]
module.function_app.azurerm_private_endpoint.pe: Still creating... [1m40s elapsed]
module.function_app.azurerm_private_endpoint.pe: Creation complete after 1m40s [id=/subscriptions/9e22xxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/dev1/providers/Microsoft.Network/privateEndpoints/vcloudlabfa-pe]

Apply complete! Resources: 15 added, 0 changed, 0 destroyed.

To check further I drill down and checked Function App networking section Inbound and Outbound traffic configuration is set correctly with public network access is disabled.

Microsoft Azure Terraform hashicorp function app inbound and outbound traffic connection public private endpoint virtual network integration DNS certificates networking authentication app service plan linux automation devops.png

Further more I checked Private Endpoint DNS configuration for Function App. All the settings related to zone group and FQDN looks good.

Terraform Microsoft Azure private dns integration customer visible fqdn network interface ip address privatelink.azurewebsites.net subscription private dns zone group.png

Useful Articles
Terraform Azure function app with private endpoint and storage account
Hashicorp Terraform map and object inside module and variable example
Terraform one module deploy null or multiple resources based on input
Terraform A reference to a resource type must be followed by at least one attribute access, specifying the resource name
Terraform fore_each for loop filter with if condition example
Terraform remote-exec provisioner with ssh connection in null_resource
Terraform count vs for_each for examples with map of objects
Terraform one module deploy null or multiple resources based on input (nested for loop) Example of Terraform functions flatten() and coalesce()
Terraform Azure Create Private Endpoint to existing Storage Account with Custom Private DNS zone record link
Creating a Private Endpoint for Azure Storage Account with required sub services using Terraform Example Terraform functions of lookup() and lower()
Using element function with count meta argument example Terraform Azure subnets Example Terraform functions of element()count() and sum()
Terraform create Azure Virtual Network subnets from map of object and show name in the header Header name change in the output
Creating a Private Endpoint for Azure Storage Account with Terraform example 2 Example of for_each with toset() function
Creating a Private Endpoint for Azure Storage Account with Terraform example 3

Go Back

Comment

Blog Search

Page Views

12086151

Follow me on Blogarama