Creating a web app - Terraform
The preferred structure for a Web App on Azure is for the configuration to be held in an Azure App Configuration instance, with secrets in an Azure Key Vault. Visibility of performance and errors in the app is provided by App Insights. Application logs should be saved to blob storage.
Depends upon
For the standard configuration of a web app, you will also need the following resources:
- App Service Plan
- App Service Certificate (if using a Cloudflare Origin cert) - tbc - what happens if using a Microsoft issued certificates
- App Insights
- App Configuration
- Key Vault
- Blob Storage (if saving logs to storage)
Configuration for custom domain
Two main options affect the setup of a custom domain:
- A Cloudflare Origin certificate or a third-party certificate has been uploaded into key vault (preferred)
- Use of Microsoft issued TLS certificates
Terraform/Azure resources to be configured
- Azure Windows Web App (or Linux Web App)
- Site configuration:
- Application stack
- Identity
- SystemAssigned
- Connection String - assuming site uses DB
- Logs
- Site configuration:
- Azure app service connection
- App Configuration
- Azure SQL
- Blob storage (if the app needs access to blob storage)
- Key vault
- Azure app service custom hostname binding
- Azure app service certificate binding
Service connections
The service connections on the web app ensures that appropriate permissions and firewall connections are created for accessing the downstream resources. These setup access to use the System Managed Identities which is Microsoft’s preferred (& most secure) way of connecting to the resources.
Please note: for Framework v4.x apps you need to ensure your app is using Microsoft.Data.SqlClient - older apps will have been using System.Data.SqlClient. The older libraries don’t have support for modern authentication methods.
Terraform script
# Admin API
resource "azurerm_windows_web_app" "app_adminapi" {
name = var.app_api_name
location = azurerm_resource_group.rg.location
resource_group_name = azurerm_resource_group.rg.name
service_plan_id = azurerm_service_plan.appserviceplan.id
site_config {
application_stack {
current_stack = "dotnet"
dotnet_version = "v8.0"
}
always_on = true
}
identity {
type = "SystemAssigned"
}
app_settings = {
"WEBSITE_ENABLE_SYNC_UPDATE_SITE" : "true"
"AZURE_APPCONFIGURATION_ENDPOINT" = azurerm_app_configuration.appconfig.endpoint
"APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.appinsights.instrumentation_key
"APPLICATIONINSIGHTS_CONNECTION_STRING" = azurerm_application_insights.appinsights.connection_string
"ApplicationInsightsAgent_EXTENSION_VERSION" = "~2"
}
depends_on = [ azurerm_application_insights.appinsights ]
connection_string {
name = "iscquare"
type = "SQLAzure"
value = "Server=tcp:${var.sql_server_name}.database.windows.net,1433;Initial Catalog=${azurerm_mssql_database.sqldb_main.name};Authentication=Active Directory Managed Identity;Encrypt=True;TrustServerCertificate=False;Connection Timeout=30;"
}
logs {
detailed_error_messages = false
failed_request_tracing = false
application_logs {
file_system_level = "Error"
}
http_logs {
file_system {
retention_in_days = 7
retention_in_mb = 35
}
}
}
}
# Create service connector to app config
resource "azurerm_app_service_connection" "api_appconfig_connection" {
name = "api_appconfig_connection"
app_service_id = azurerm_windows_web_app.app_adminapi.id
target_resource_id = azurerm_app_configuration.appconfig.id
authentication {
type = "systemAssignedIdentity"
}
}
# Create service connector to Azure SQL Database
resource "azurerm_app_service_connection" "api_sqldb_connection" {
name = "api_sqldb_connection"
app_service_id = azurerm_windows_web_app.app_adminapi.id
target_resource_id = azurerm_mssql_database.sqldb_main.id
authentication {
type = "systemAssignedIdentity"
}
}
# Create service connector to Azure Blob Storage
resource "azurerm_app_service_connection" "api_storage_connection" {
name = "api_storage_connection"
app_service_id = azurerm_windows_web_app.app_adminapi.id
target_resource_id = azurerm_storage_account.storage.id
authentication {
type = "systemAssignedIdentity"
}
}
# Create service connector to Key Vault
resource "azurerm_app_service_connection" "api_keyvault_connection" {
name = "api_keyvault_connection"
app_service_id = azurerm_windows_web_app.app_adminapi.id
target_resource_id = azurerm_key_vault.keyvault.id
authentication {
type = "systemAssignedIdentity"
}
}
# Assign api-uat custom domain to the admin API
resource "azurerm_app_service_custom_hostname_binding" "adminapi_custom_domain" {
hostname = var.app_api_hostname
app_service_name = azurerm_windows_web_app.app_adminapi.name
resource_group_name = azurerm_resource_group.rg.name
}
# bind certificate to admin api
resource "azurerm_app_service_certificate_binding" "adminapi_cert_binding" {
hostname_binding_id = azurerm_app_service_custom_hostname_binding.adminapi_custom_domain.id
certificate_id = azurerm_app_service_certificate.iscquare_net_cert.id
ssl_state = "SniEnabled"
}