Compute Security Baselines
Compute services are the workhorses of any Azure architecture. Each has a distinct threat surface — App Service is PaaS with limited OS control, VMs give you full OS responsibility, and Functions abstract away everything but your code. The security patterns differ for each.
Compute Service Security Architecture
Azure App Service
PaaS web hosting with built-in autoscale, deployment slots, and managed certificates. Microsoft manages the underlying OS — you secure the application layer, network access, and identity configuration.
| Property | Value |
|---|---|
| Defender Plan | Defender for App Service |
| Azure Policy Built-in | Yes — App Service initiative (MCSB-aligned) |
| Diagnostic Logs | AppServiceHTTPLogs, AppServiceConsoleLogs, AppServiceAuditLogs |
| Recommended SKU | Premium v3 (VNet integration, private endpoints, zone redundancy) |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | VNet Integration | ✓ | Manual | Customer |
| NS | Private Endpoint | ✓ | Manual | Customer |
| NS | TLS 1.2 Minimum | ✓ | Default | Microsoft |
| IM | Managed Identity | ✓ | Manual | Customer |
| IM | Disable Basic Auth | ✓ | Manual | Customer |
| DP | HTTPS Only | ✓ | Default | Microsoft |
| DP | Encryption at Rest | ✓ | Default | Microsoft |
| NS | WAF Protection | ✓ | Manual | Customer |
| LT | Defender for App Service | ✓ | Manual | Customer |
| LT | Diagnostic Settings | ✓ | Manual | Customer |
VNet Integration
Private Endpoint
TLS 1.2 Minimum
Managed Identity
Disable Basic Auth
HTTPS Only
Encryption at Rest
WAF Protection
Defender for App Service
Diagnostic Settings
Terraform: Hardened App Service
resource "azurerm_service_plan" "main" {
name = "asp-prod-001"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
os_type = "Linux"
sku_name = "P1v3" # Premium v3 for VNet integration
zone_balancing_enabled = true # Zone redundancy
}
resource "azurerm_linux_web_app" "main" {
name = "app-web-prod"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
service_plan_id = azurerm_service_plan.main.id
# IM-3: Managed Identity
identity {
type = "SystemAssigned"
}
site_config {
minimum_tls_version = "1.2" # NS-8: Disable insecure protocols
ftps_state = "Disabled" # IM: Disable basic auth channel
http2_enabled = true
always_on = true
vnet_route_all_enabled = true # NS-1: Route all traffic through VNet
remote_debugging_enabled = false # PV: Reduce attack surface
}
# DP-3: HTTPS only
https_only = true
# IM: Disable basic auth for SCM and FTP
# (Set via azapi_update_resource or site_config in newer provider versions)
app_settings = {
"WEBSITE_RUN_FROM_PACKAGE" = "1" # DS: Immutable deployment
}
}
# NS-2: Private Endpoint (disable public access)
resource "azurerm_private_endpoint" "app" {
name = "pe-app-web-prod"
location = azurerm_resource_group.app.location
resource_group_name = azurerm_resource_group.app.name
subnet_id = azurerm_subnet.private_endpoints.id
private_service_connection {
name = "psc-app-web"
private_connection_resource_id = azurerm_linux_web_app.main.id
subresource_names = ["sites"]
is_manual_connection = false
}
private_dns_zone_group {
name = "pdz-app-web"
private_dns_zone_ids = [azurerm_private_dns_zone.app_service.id]
}
}
# NS-1: VNet Integration (outbound)
resource "azurerm_app_service_virtual_network_swift_connection" "main" {
app_service_id = azurerm_linux_web_app.main.id
subnet_id = azurerm_subnet.app_integration.id
}
# LT-3: Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "app" {
name = "diag-app-web-prod"
target_resource_id = azurerm_linux_web_app.main.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
enabled_log { category = "AppServiceHTTPLogs" }
enabled_log { category = "AppServiceConsoleLogs" }
enabled_log { category = "AppServiceAuditLogs" }
enabled_log { category = "AppServicePlatformLogs" }
metric { category = "AllMetrics" }
}resource "azurerm_service_plan" "main" {
name = "asp-prod-001"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
os_type = "Linux"
sku_name = "P1v3" # Premium v3 for VNet integration
zone_balancing_enabled = true # Zone redundancy
}
resource "azurerm_linux_web_app" "main" {
name = "app-web-prod"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
service_plan_id = azurerm_service_plan.main.id
# IM-3: Managed Identity
identity {
type = "SystemAssigned"
}
site_config {
minimum_tls_version = "1.2" # NS-8: Disable insecure protocols
ftps_state = "Disabled" # IM: Disable basic auth channel
http2_enabled = true
always_on = true
vnet_route_all_enabled = true # NS-1: Route all traffic through VNet
remote_debugging_enabled = false # PV: Reduce attack surface
}
# DP-3: HTTPS only
https_only = true
# IM: Disable basic auth for SCM and FTP
# (Set via azapi_update_resource or site_config in newer provider versions)
app_settings = {
"WEBSITE_RUN_FROM_PACKAGE" = "1" # DS: Immutable deployment
}
}
# NS-2: Private Endpoint (disable public access)
resource "azurerm_private_endpoint" "app" {
name = "pe-app-web-prod"
location = azurerm_resource_group.app.location
resource_group_name = azurerm_resource_group.app.name
subnet_id = azurerm_subnet.private_endpoints.id
private_service_connection {
name = "psc-app-web"
private_connection_resource_id = azurerm_linux_web_app.main.id
subresource_names = ["sites"]
is_manual_connection = false
}
private_dns_zone_group {
name = "pdz-app-web"
private_dns_zone_ids = [azurerm_private_dns_zone.app_service.id]
}
}
# NS-1: VNet Integration (outbound)
resource "azurerm_app_service_virtual_network_swift_connection" "main" {
app_service_id = azurerm_linux_web_app.main.id
subnet_id = azurerm_subnet.app_integration.id
}
# LT-3: Diagnostic Settings
resource "azurerm_monitor_diagnostic_setting" "app" {
name = "diag-app-web-prod"
target_resource_id = azurerm_linux_web_app.main.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
enabled_log { category = "AppServiceHTTPLogs" }
enabled_log { category = "AppServiceConsoleLogs" }
enabled_log { category = "AppServiceAuditLogs" }
enabled_log { category = "AppServicePlatformLogs" }
metric { category = "AllMetrics" }
}Azure Portal Instructions
Step-by-step instructions for configuring each control through the Azure Portal.
NS-1 — Virtual Network Integration
- Navigate to your App Service resource in the Azure Portal
- In the left menu under Settings, select Networking
- Under Outbound traffic configuration, click VNet Integration
- Click Add VNet Integration and select your target VNet and subnet
- Enable Route All to send all outbound traffic through the VNet
- Verify connectivity by checking the Networking status panel
NS-2 — Azure Private Link
- Navigate to your App Service resource in the Azure Portal
- In the left menu under Settings, select Networking
- Under Inbound traffic configuration, click Private endpoints
- Click + Add to create a new private endpoint
- Select the target VNet, subnet, and configure the private DNS zone
- After creation, go to Networking > Access restriction and set "Public network access" to Disabled
NS-6 — Web Application Firewall
- Create or navigate to your Azure Front Door or Application Gateway resource
- Under Settings, select Web Application Firewall (WAF) policies
- Click Create or assign a WAF policy with OWASP 3.2+ managed ruleset
- Set the WAF mode to Prevention
- Navigate back to the App Service > Networking > Access Restrictions
- Add a rule to allow traffic only from the Front Door or Application Gateway service tag
- Set the default action to Deny to block direct access
IM-1 — Entra ID Authentication
- Navigate to your App Service resource in the Azure Portal
- In the left menu under Settings, select Authentication
- Click Add identity provider
- Select Microsoft as the identity provider
- Configure the App Registration settings (use Express for automatic setup)
- Set "Restrict access" to Require authentication
- Under Unauthenticated requests, select "HTTP 401 Unauthorized"
IM-3 — Managed Identities
- Navigate to your App Service resource in the Azure Portal
- In the left menu under Settings, select Identity
- On the System assigned tab, set Status to On and click Save
- Copy the Object (principal) ID for role assignment
- Navigate to the target resource (e.g., Key Vault, Storage Account)
- Go to Access control (IAM) > Add role assignment
- Assign the appropriate role (e.g., Key Vault Secrets User) to the managed identity
DP-3 — Data in Transit Encryption
- Navigate to your App Service resource in the Azure Portal
- In the left menu under Settings, select Configuration
- Select the General settings tab
- Set Minimum TLS version to 1.2 (or 1.3 if supported)
- Set HTTPS Only to On to redirect all HTTP traffic
- Click Save to apply the changes
DP-4 — Data at Rest Encryption (PMK)
This feature is managed by Microsoft and enabled by default. No portal configuration is required — all app content in Azure Storage is automatically encrypted with Microsoft-managed keys.
DP-5 — Data at Rest Encryption (CMK)
- Create or navigate to your Azure Key Vault and create an encryption key
- Navigate to your App Service resource in the Azure Portal
- In the left menu under Settings, select Configuration
- Under Application settings, use Key Vault references (
@Microsoft.KeyVault(SecretUri=...)) for all sensitive settings - For full CMK encryption of the underlying storage, this requires ARM/CLI deployment — the portal does not yet expose this setting directly
- Verify the managed identity has Get and Unwrap Key permissions on the Key Vault
LT-1 — Microsoft Defender for App Service
- Navigate to Microsoft Defender for Cloud in the Azure Portal
- In the left menu, select Environment settings
- Select your subscription
- Under Cloud Workload Protection (CWP), find the App Service row
- Toggle the plan to On
- Click Save to enable Defender for App Service on all App Services in the subscription
BR-1 — Azure Backup
- Navigate to your App Service resource in the Azure Portal
- In the left menu under Settings, select Backups
- Click Configure and select a Storage Account and container for backup storage
- Set the backup schedule (e.g., daily) and retention period
- Optionally check Include database to back up linked databases
- Click Save to enable scheduled backups
Critical: Disable Basic Auth
basicPublishingCredentialsPolicies to false for both FTP and SCM
via the Azure API or portal.
Azure Virtual Machines
IaaS with full OS responsibility. You manage patching, endpoint protection, firewall configuration, and hardening. VMs have the largest attack surface of any compute service — and the most security controls to configure.
| Property | Value |
|---|---|
| Defender Plan | Defender for Servers (Plan 2) |
| Azure Policy Built-in | Yes — VM initiative + Guest Configuration |
| Diagnostic Logs | Syslog, Security Events, Performance Counters via AMA |
| Key Feature | Trusted Launch with vTPM and Secure Boot |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | NSG on NIC/Subnet | ✓ | Manual | Customer |
| NS | No Public IP | ✓ | Manual | Customer |
| IM | Entra ID Login | ✓ | Manual | Customer |
| IM | Managed Identity | ✓ | Manual | Customer |
| DP | Encryption at Host | ✓ | Manual | Customer |
| DP | Azure Disk Encryption | ✓ | Manual | Customer |
| ES | MDE (Endpoint Detection) | ✓ | Manual | Customer |
| PV | Trusted Launch | ✓ | Default | Microsoft |
| PV | Azure Update Manager | ✓ | Manual | Customer |
| BR | Azure Backup | ✓ | Manual | Customer |
NSG on NIC/Subnet
No Public IP
Entra ID Login
Managed Identity
Encryption at Host
Azure Disk Encryption
MDE (Endpoint Detection)
Trusted Launch
Azure Update Manager
Azure Backup
Terraform: Hardened Virtual Machine
resource "azurerm_linux_virtual_machine" "main" {
name = "vm-app-prod-001"
resource_group_name = azurerm_resource_group.compute.name
location = azurerm_resource_group.compute.location
size = "Standard_D4s_v5"
admin_username = "azureadmin"
zone = "1"
# IM-3: Managed Identity
identity {
type = "SystemAssigned"
}
# IM-1: Entra ID SSH login (no password auth)
admin_ssh_key {
username = "azureadmin"
public_key = file("~/.ssh/id_rsa.pub")
}
disable_password_authentication = true
# NS: No Public IP — use Azure Bastion for access
network_interface_ids = [azurerm_network_interface.vm_nic.id]
os_disk {
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
# DP: Disk encryption set with CMK
disk_encryption_set_id = azurerm_disk_encryption_set.main.id
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
# PV: Trusted Launch
secure_boot_enabled = true
vtpm_enabled = true
# DP: Encryption at host (all temp disks and caches encrypted)
encryption_at_host_enabled = true
}
# NS-1: NIC in private subnet, no public IP
resource "azurerm_network_interface" "vm_nic" {
name = "nic-vm-app-prod-001"
location = azurerm_resource_group.compute.location
resource_group_name = azurerm_resource_group.compute.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.compute.id
private_ip_address_allocation = "Dynamic"
# No public_ip_address_id — access via Bastion only
}
}
# ES-1: MDE via Defender for Servers auto-provisioning
# (Deployed automatically when Defender for Servers Plan 2 is enabled)
# BR-1: Azure Backup
resource "azurerm_backup_protected_vm" "main" {
resource_group_name = azurerm_resource_group.compute.name
recovery_vault_name = azurerm_recovery_services_vault.main.name
source_vm_id = azurerm_linux_virtual_machine.main.id
backup_policy_id = azurerm_backup_policy_vm.daily.id
}resource "azurerm_linux_virtual_machine" "main" {
name = "vm-app-prod-001"
resource_group_name = azurerm_resource_group.compute.name
location = azurerm_resource_group.compute.location
size = "Standard_D4s_v5"
admin_username = "azureadmin"
zone = "1"
# IM-3: Managed Identity
identity {
type = "SystemAssigned"
}
# IM-1: Entra ID SSH login (no password auth)
admin_ssh_key {
username = "azureadmin"
public_key = file("~/.ssh/id_rsa.pub")
}
disable_password_authentication = true
# NS: No Public IP — use Azure Bastion for access
network_interface_ids = [azurerm_network_interface.vm_nic.id]
os_disk {
caching = "ReadWrite"
storage_account_type = "Premium_LRS"
# DP: Disk encryption set with CMK
disk_encryption_set_id = azurerm_disk_encryption_set.main.id
}
source_image_reference {
publisher = "Canonical"
offer = "0001-com-ubuntu-server-jammy"
sku = "22_04-lts-gen2"
version = "latest"
}
# PV: Trusted Launch
secure_boot_enabled = true
vtpm_enabled = true
# DP: Encryption at host (all temp disks and caches encrypted)
encryption_at_host_enabled = true
}
# NS-1: NIC in private subnet, no public IP
resource "azurerm_network_interface" "vm_nic" {
name = "nic-vm-app-prod-001"
location = azurerm_resource_group.compute.location
resource_group_name = azurerm_resource_group.compute.name
ip_configuration {
name = "internal"
subnet_id = azurerm_subnet.compute.id
private_ip_address_allocation = "Dynamic"
# No public_ip_address_id — access via Bastion only
}
}
# ES-1: MDE via Defender for Servers auto-provisioning
# (Deployed automatically when Defender for Servers Plan 2 is enabled)
# BR-1: Azure Backup
resource "azurerm_backup_protected_vm" "main" {
resource_group_name = azurerm_resource_group.compute.name
recovery_vault_name = azurerm_recovery_services_vault.main.name
source_vm_id = azurerm_linux_virtual_machine.main.id
backup_policy_id = azurerm_backup_policy_vm.daily.id
}Azure Portal Instructions
Step-by-step instructions for configuring each control through the Azure Portal.
NS-1 — Virtual Network Deployment & NSGs
- Navigate to your Virtual Machine resource in the Azure Portal
- In the left menu under Settings, select Networking
- Click the Network security group link to view or edit NSG rules
- Add inbound/outbound security rules following least-privilege principles
- Use Application Security Groups (ASGs) to group VMs by role for simplified rules
- Verify that no Allow-All rules exist in the effective security rules view
NS-2 — Disable Public IP
- Navigate to your Virtual Machine resource in the Azure Portal
- In the left menu under Settings, select Networking
- Click on the Network Interface name
- Select IP configurations and click on the IP configuration entry
- Under Public IP address, select Disassociate and click Save
- To enable secure access, deploy Azure Bastion in the VNet — search for "Bastions" in the portal and click Create
IM-3 — Managed Identities
- Navigate to your Virtual Machine resource in the Azure Portal
- In the left menu under Settings, select Identity
- On the System assigned tab, set Status to On and click Save
- Copy the Object (principal) ID for use in role assignments
- Navigate to the target resource and assign appropriate RBAC roles to this identity
DP-4 — Disk Encryption (PMK)
This feature is managed by Microsoft and enabled by default. No portal configuration is required — all managed disks are encrypted at rest with platform-managed keys automatically.
DP-5 — Azure Disk Encryption (CMK)
- Navigate to Disk Encryption Sets in the Azure Portal and click Create
- Select the subscription, resource group, and region
- Choose the encryption type (e.g., Encryption at rest with a customer-managed key)
- Select your Key Vault and encryption key
- Click Review + Create to provision the disk encryption set
- Navigate to your VM > Disks, click on each disk, and under Encryption select the disk encryption set you created
ES-1 — Microsoft Defender for Endpoint
- Navigate to Microsoft Defender for Cloud in the Azure Portal
- In the left menu, select Environment settings
- Select your subscription
- Under Cloud Workload Protection (CWP), find the Servers row
- Toggle the plan to On and select Plan 2 (includes MDE)
- Click Settings for the Servers plan and ensure Endpoint protection is enabled
- Click Save — MDE will be auto-provisioned to eligible VMs
PV-3 — Secure Configurations
- Navigate to Microsoft Defender for Cloud > Recommendations
- Filter by resource type "Virtual machines" to see configuration recommendations
- Review and remediate flagged items (e.g., "Machines should have vulnerability assessment solution")
- For proactive hardening, navigate to Azure Policy and assign the "Azure Security Benchmark" initiative
- To enable Azure Automanage, navigate to the VM > Operations > Automanage and select the Best Practices profile
BR-1 — Azure Backup
- Navigate to your Virtual Machine resource in the Azure Portal
- In the left menu under Operations, select Backup
- Select an existing Recovery Services vault or create a new one
- Choose or create a backup policy (e.g., daily backup with 30-day retention)
- Click Enable backup
- To verify, go to the Recovery Services vault > Backup items > Azure Virtual Machines
JIT VM Access Pattern
Azure Functions
Serverless compute that runs code on demand. Functions share much of the App Service infrastructure under the hood, which means the same network and identity controls apply — but the consumption model introduces unique considerations around trigger security and execution context.
| Property | Value |
|---|---|
| Defender Plan | Defender for App Service |
| Azure Policy Built-in | Yes — shares App Service initiative |
| VNet Support | Premium Plan or ASE required (Consumption = no VNet integration) |
| Trigger Security | Function keys (default), Entra ID (recommended), API Management |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | VNet Integration (Premium) | ✓ | Manual | Customer |
| NS | Private Endpoint | ✓ | Manual | Customer |
| IM | Managed Identity | ✓ | Manual | Customer |
| IM | Disable Function Keys | ✓ | Manual | Customer |
| DP | HTTPS Only | ✓ | Default | Microsoft |
| DP | Encryption at Rest | ✓ | Default | Microsoft |
| LT | Diagnostic Settings | ✓ | Manual | Customer |
VNet Integration (Premium)
Private Endpoint
Managed Identity
Disable Function Keys
HTTPS Only
Encryption at Rest
Diagnostic Settings
Terraform: Secure Function App (Premium Plan)
resource "azurerm_service_plan" "func" {
name = "asp-func-prod"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
os_type = "Linux"
sku_name = "EP1" # Premium Elastic for VNet
}
resource "azurerm_linux_function_app" "main" {
name = "func-process-prod"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
service_plan_id = azurerm_service_plan.func.id
storage_account_name = azurerm_storage_account.func.name
storage_account_access_key = azurerm_storage_account.func.primary_access_key
# IM-3: Managed Identity
identity {
type = "SystemAssigned"
}
site_config {
minimum_tls_version = "1.2"
ftps_state = "Disabled"
vnet_route_all_enabled = true
remote_debugging_enabled = false
application_stack {
dotnet_version = "8.0"
use_dotnet_isolated_runtime = true
}
}
# DP-3: HTTPS only
https_only = true
app_settings = {
"FUNCTIONS_WORKER_RUNTIME" = "dotnet-isolated"
"WEBSITE_RUN_FROM_PACKAGE" = "1"
# DP: Use Key Vault references instead of connection strings
"ServiceBusConnection__fullyQualifiedNamespace" = "sb-prod.servicebus.windows.net"
}
}
# NS-2: VNet Integration
resource "azurerm_app_service_virtual_network_swift_connection" "func" {
app_service_id = azurerm_linux_function_app.main.id
subnet_id = azurerm_subnet.func_integration.id
}resource "azurerm_service_plan" "func" {
name = "asp-func-prod"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
os_type = "Linux"
sku_name = "EP1" # Premium Elastic for VNet
}
resource "azurerm_linux_function_app" "main" {
name = "func-process-prod"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
service_plan_id = azurerm_service_plan.func.id
storage_account_name = azurerm_storage_account.func.name
storage_account_access_key = azurerm_storage_account.func.primary_access_key
# IM-3: Managed Identity
identity {
type = "SystemAssigned"
}
site_config {
minimum_tls_version = "1.2"
ftps_state = "Disabled"
vnet_route_all_enabled = true
remote_debugging_enabled = false
application_stack {
dotnet_version = "8.0"
use_dotnet_isolated_runtime = true
}
}
# DP-3: HTTPS only
https_only = true
app_settings = {
"FUNCTIONS_WORKER_RUNTIME" = "dotnet-isolated"
"WEBSITE_RUN_FROM_PACKAGE" = "1"
# DP: Use Key Vault references instead of connection strings
"ServiceBusConnection__fullyQualifiedNamespace" = "sb-prod.servicebus.windows.net"
}
}
# NS-2: VNet Integration
resource "azurerm_app_service_virtual_network_swift_connection" "func" {
app_service_id = azurerm_linux_function_app.main.id
subnet_id = azurerm_subnet.func_integration.id
}Azure Portal Instructions
Step-by-step instructions for configuring each control through the Azure Portal.
NS-1 — Virtual Network Integration
- Navigate to your Function App resource in the Azure Portal
- In the left menu under Settings, select Networking
- Under Outbound traffic configuration, click VNet Integration
- Click Add VNet Integration and select your target VNet and subnet
- Toggle Route All to send all outbound traffic through the VNet
- Note: VNet Integration requires a Premium (Elastic) or Dedicated (App Service) plan
NS-2 — Private Endpoints
- Navigate to your Function App resource in the Azure Portal
- In the left menu under Settings, select Networking
- Under Inbound traffic configuration, click Private endpoints
- Click + Add to create a new private endpoint
- Select the VNet, subnet, and configure the private DNS zone
- Go back to Networking and set "Public network access" to Disabled
IM-1 — Entra ID Authentication
- Navigate to your Function App resource in the Azure Portal
- In the left menu under Settings, select Authentication
- Click Add identity provider
- Select Microsoft as the identity provider
- Configure the App Registration (use Express for automatic setup)
- Set "Restrict access" to Require authentication
- Set unauthenticated requests to return HTTP 401
IM-3 — Managed Identities
- Navigate to your Function App resource in the Azure Portal
- In the left menu under Settings, select Identity
- On the System assigned tab, set Status to On and click Save
- To use Key Vault references, go to Configuration > Application settings
- Use the format
@Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/)as the setting value - Grant the managed identity access to Key Vault via Access control (IAM) on the Key Vault resource
DP-3 — Data in Transit Encryption
- Navigate to your Function App resource in the Azure Portal
- In the left menu under Settings, select Configuration
- Select the General settings tab
- Set Minimum TLS version to 1.2
- Set HTTPS Only to On
- Click Save to apply the changes
DP-4 — Data at Rest Encryption
This feature is managed by Microsoft and enabled by default. No portal configuration is required — function app content in Azure Storage is automatically encrypted.
LT-1 — Defender for App Service
- Navigate to Microsoft Defender for Cloud in the Azure Portal
- In the left menu, select Environment settings
- Select your subscription
- Under Cloud Workload Protection (CWP), find the App Service row
- Toggle the plan to On (this covers both App Service and Functions)
- Click Save to enable threat detection for all function apps in the subscription
Consumption Plan Limitations
Azure Container Apps
Serverless container platform built on Kubernetes with built-in autoscaling, Dapr integration, and KEDA event-driven scaling. Container Apps abstracts Kubernetes complexity while providing production-grade network isolation and identity controls.
| Property | Value |
|---|---|
| Defender Plan | Defender for Containers |
| Azure Policy Built-in | Yes — managed identity, ingress settings |
| VNet Support | Custom VNet with internal-only or external load balancer |
| Key Feature | Serverless containers with VNet isolation and managed identity |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Internal VNet Environment | ✓ | Manual | Customer |
| NS | Private Endpoint | ✓ | Manual | Customer |
| IM | Managed Identity | ✓ | Manual | Customer |
| DP | HTTPS Ingress (TLS 1.2) | ✓ | Manual | Customer |
| DP | Secret Management | ✓ | Manual | Customer |
| LT | Defender for Containers | ✓ | Manual | Customer |
Internal VNet Environment
Private Endpoint
Managed Identity
HTTPS Ingress (TLS 1.2)
Secret Management
Defender for Containers
Terraform: Secure Container Apps Environment
resource "azurerm_container_app_environment" "main" {
name = "cae-prod"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
# NS-1: Internal VNet deployment
infrastructure_subnet_id = azurerm_subnet.container_apps.id
internal_load_balancer_enabled = true # Internal-only access
}
resource "azurerm_container_app" "api" {
name = "api-prod"
container_app_environment_id = azurerm_container_app_environment.main.id
resource_group_name = azurerm_resource_group.app.name
revision_mode = "Single"
# IM-3: Managed Identity
identity {
type = "SystemAssigned"
}
template {
container {
name = "api"
image = "myacr.azurecr.io/api:latest"
cpu = 0.5
memory = "1Gi"
}
}
# DP-3: HTTPS-only ingress
ingress {
external_enabled = false # Internal only
target_port = 8080
transport = "http"
traffic_weight {
percentage = 100
latest_revision = true
}
}
secret {
name = "db-connection"
key_vault_secret_id = azurerm_key_vault_secret.db_conn.versionless_id
identity = "System" # DP: Key Vault reference
}
}resource "azurerm_container_app_environment" "main" {
name = "cae-prod"
resource_group_name = azurerm_resource_group.app.name
location = azurerm_resource_group.app.location
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
# NS-1: Internal VNet deployment
infrastructure_subnet_id = azurerm_subnet.container_apps.id
internal_load_balancer_enabled = true # Internal-only access
}
resource "azurerm_container_app" "api" {
name = "api-prod"
container_app_environment_id = azurerm_container_app_environment.main.id
resource_group_name = azurerm_resource_group.app.name
revision_mode = "Single"
# IM-3: Managed Identity
identity {
type = "SystemAssigned"
}
template {
container {
name = "api"
image = "myacr.azurecr.io/api:latest"
cpu = 0.5
memory = "1Gi"
}
}
# DP-3: HTTPS-only ingress
ingress {
external_enabled = false # Internal only
target_port = 8080
transport = "http"
traffic_weight {
percentage = 100
latest_revision = true
}
}
secret {
name = "db-connection"
key_vault_secret_id = azurerm_key_vault_secret.db_conn.versionless_id
identity = "System" # DP: Key Vault reference
}
}Azure Virtual Desktop
Enterprise virtual desktop infrastructure (VDI) with multi-session Windows 11, FSLogix profile management, and Entra ID integration. AVD session hosts are full Windows VMs — they require the same endpoint security, network isolation, and identity controls as traditional VMs.
| Property | Value |
|---|---|
| Defender Plan | Defender for Servers P2 (MDE on session hosts) |
| Azure Policy Built-in | Yes — session host configuration, encryption, extensions |
| Identity Model | Entra ID Join with SSO (preferred) or Hybrid AD Join |
| Key Feature | Private endpoints for host pool, workspace, and feed access |
Baseline Controls
| Domain | Feature | Supported | Default | Responsibility |
|---|---|---|---|---|
| NS | Private Endpoint | ✓ | Manual | Customer |
| NS | RDP Shortpath | ✓ | Manual | Customer |
| IM | Entra ID Join | ✓ | Manual | Customer |
| IM | Managed Identity | ✓ | Manual | Customer |
| DP | Disk Encryption (CMK) | ✓ | Manual | Customer |
| DP | RDP Properties Hardening | ✓ | Manual | Customer |
| ES | MDE on Session Hosts | ✓ | Manual | Customer |
| LT | Connection Diagnostics | ✓ | Manual | Customer |
Private Endpoint
RDP Shortpath
Entra ID Join
Managed Identity
Disk Encryption (CMK)
RDP Properties Hardening
MDE on Session Hosts
Connection Diagnostics
Terraform: Hardened AVD Host Pool
resource "azurerm_virtual_desktop_host_pool" "main" {
name = "hp-prod"
resource_group_name = azurerm_resource_group.avd.name
location = azurerm_resource_group.avd.location
type = "Pooled"
load_balancer_type = "BreadthFirst"
# DP-7: RDP Properties Hardening
custom_rdp_properties = join(";", [
"targetisaadjoined:i:1", # IM-1: Entra ID Join
"enablecredsspsupport:i:1", # NLA enabled
"redirectclipboard:i:0", # Disable clipboard
"redirectprinters:i:0", # Disable printers
"drivestoredirect:s:", # Disable drive redirection
"audiocapturemode:i:0", # Disable mic
"camerastoredirect:s:", # Disable camera
"devicestoredirect:s:", # Disable USB devices
])
scheduled_agent_updates {
enabled = true
timezone = "UTC"
schedule {
day_of_week = "Saturday"
hour_of_day = 2
}
}
}
resource "azurerm_virtual_desktop_workspace" "main" {
name = "ws-prod"
resource_group_name = azurerm_resource_group.avd.name
location = azurerm_resource_group.avd.location
}
# LT-3: Diagnostic settings for connection monitoring
resource "azurerm_monitor_diagnostic_setting" "hp" {
name = "diag-hp-prod"
target_resource_id = azurerm_virtual_desktop_host_pool.main.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
enabled_log { category = "Checkpoint" }
enabled_log { category = "Connection" }
enabled_log { category = "Error" }
enabled_log { category = "HostRegistration" }
enabled_log { category = "AgentHealthStatus" }
}resource "azurerm_virtual_desktop_host_pool" "main" {
name = "hp-prod"
resource_group_name = azurerm_resource_group.avd.name
location = azurerm_resource_group.avd.location
type = "Pooled"
load_balancer_type = "BreadthFirst"
# DP-7: RDP Properties Hardening
custom_rdp_properties = join(";", [
"targetisaadjoined:i:1", # IM-1: Entra ID Join
"enablecredsspsupport:i:1", # NLA enabled
"redirectclipboard:i:0", # Disable clipboard
"redirectprinters:i:0", # Disable printers
"drivestoredirect:s:", # Disable drive redirection
"audiocapturemode:i:0", # Disable mic
"camerastoredirect:s:", # Disable camera
"devicestoredirect:s:", # Disable USB devices
])
scheduled_agent_updates {
enabled = true
timezone = "UTC"
schedule {
day_of_week = "Saturday"
hour_of_day = 2
}
}
}
resource "azurerm_virtual_desktop_workspace" "main" {
name = "ws-prod"
resource_group_name = azurerm_resource_group.avd.name
location = azurerm_resource_group.avd.location
}
# LT-3: Diagnostic settings for connection monitoring
resource "azurerm_monitor_diagnostic_setting" "hp" {
name = "diag-hp-prod"
target_resource_id = azurerm_virtual_desktop_host_pool.main.id
log_analytics_workspace_id = azurerm_log_analytics_workspace.main.id
enabled_log { category = "Checkpoint" }
enabled_log { category = "Connection" }
enabled_log { category = "Error" }
enabled_log { category = "HostRegistration" }
enabled_log { category = "AgentHealthStatus" }
}Session Host Security