Compute

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

graph LR subgraph Internet User[User / Client] end subgraph Edge["Edge Protection"] FD[Front Door + WAF] end subgraph Compute["Compute Tier"] APP[App Service] VM[Virtual Machine] FUNC[Azure Functions] end subgraph Backend["Backend Services"] KV[Key Vault] SQL[SQL Database] SA[Storage Account] end User --> FD FD --> APP FD --> FUNC APP -->|Managed Identity| KV APP -->|Managed Identity| SQL FUNC -->|Managed Identity| SA VM -->|Private Endpoint| SQL style Internet stroke:#f59e0b,stroke-width:2px style Edge stroke:#ec4899,stroke-width:2px style Compute stroke:#22d3ee,stroke-width:2px style Backend stroke:#4ade80,stroke-width:2px

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.

PropertyValue
Defender PlanDefender for App Service
Azure Policy Built-inYes — App Service initiative (MCSB-aligned)
Diagnostic LogsAppServiceHTTPLogs, AppServiceConsoleLogs, AppServiceAuditLogs
Recommended SKUPremium v3 (VNet integration, private endpoints, zone redundancy)

Baseline Controls

NS✓ Supported

VNet Integration

○ ManualCustomer
NS✓ Supported

Private Endpoint

○ ManualCustomer
NS✓ Supported

TLS 1.2 Minimum

● DefaultMicrosoft
IM✓ Supported

Managed Identity

○ ManualCustomer
IM✓ Supported

Disable Basic Auth

○ ManualCustomer
DP✓ Supported

HTTPS Only

● DefaultMicrosoft
DP✓ Supported

Encryption at Rest

● DefaultMicrosoft
NS✓ Supported

WAF Protection

○ ManualCustomer
LT✓ Supported

Defender for App Service

○ ManualCustomer
LT✓ Supported

Diagnostic Settings

○ ManualCustomer

Terraform: Hardened App Service

hcl
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
  1. Navigate to your App Service resource in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Under Outbound traffic configuration, click VNet Integration
  4. Click Add VNet Integration and select your target VNet and subnet
  5. Enable Route All to send all outbound traffic through the VNet
  6. Verify connectivity by checking the Networking status panel

Microsoft Docs Reference →

NS-2 — Azure Private Link
  1. Navigate to your App Service resource in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Under Inbound traffic configuration, click Private endpoints
  4. Click + Add to create a new private endpoint
  5. Select the target VNet, subnet, and configure the private DNS zone
  6. After creation, go to Networking > Access restriction and set "Public network access" to Disabled

Microsoft Docs Reference →

NS-6 — Web Application Firewall
  1. Create or navigate to your Azure Front Door or Application Gateway resource
  2. Under Settings, select Web Application Firewall (WAF) policies
  3. Click Create or assign a WAF policy with OWASP 3.2+ managed ruleset
  4. Set the WAF mode to Prevention
  5. Navigate back to the App Service > Networking > Access Restrictions
  6. Add a rule to allow traffic only from the Front Door or Application Gateway service tag
  7. Set the default action to Deny to block direct access

Microsoft Docs Reference →

IM-1 — Entra ID Authentication
  1. Navigate to your App Service resource in the Azure Portal
  2. In the left menu under Settings, select Authentication
  3. Click Add identity provider
  4. Select Microsoft as the identity provider
  5. Configure the App Registration settings (use Express for automatic setup)
  6. Set "Restrict access" to Require authentication
  7. Under Unauthenticated requests, select "HTTP 401 Unauthorized"

Microsoft Docs Reference →

IM-3 — Managed Identities
  1. Navigate to your App Service resource in the Azure Portal
  2. In the left menu under Settings, select Identity
  3. On the System assigned tab, set Status to On and click Save
  4. Copy the Object (principal) ID for role assignment
  5. Navigate to the target resource (e.g., Key Vault, Storage Account)
  6. Go to Access control (IAM) > Add role assignment
  7. Assign the appropriate role (e.g., Key Vault Secrets User) to the managed identity

Microsoft Docs Reference →

DP-3 — Data in Transit Encryption
  1. Navigate to your App Service resource in the Azure Portal
  2. In the left menu under Settings, select Configuration
  3. Select the General settings tab
  4. Set Minimum TLS version to 1.2 (or 1.3 if supported)
  5. Set HTTPS Only to On to redirect all HTTP traffic
  6. Click Save to apply the changes

Microsoft Docs Reference →

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)
  1. Create or navigate to your Azure Key Vault and create an encryption key
  2. Navigate to your App Service resource in the Azure Portal
  3. In the left menu under Settings, select Configuration
  4. Under Application settings, use Key Vault references (@Microsoft.KeyVault(SecretUri=...)) for all sensitive settings
  5. For full CMK encryption of the underlying storage, this requires ARM/CLI deployment — the portal does not yet expose this setting directly
  6. Verify the managed identity has Get and Unwrap Key permissions on the Key Vault

Microsoft Docs Reference →

LT-1 — Microsoft Defender for App Service
  1. Navigate to Microsoft Defender for Cloud in the Azure Portal
  2. In the left menu, select Environment settings
  3. Select your subscription
  4. Under Cloud Workload Protection (CWP), find the App Service row
  5. Toggle the plan to On
  6. Click Save to enable Defender for App Service on all App Services in the subscription

Microsoft Docs Reference →

BR-1 — Azure Backup
  1. Navigate to your App Service resource in the Azure Portal
  2. In the left menu under Settings, select Backups
  3. Click Configure and select a Storage Account and container for backup storage
  4. Set the backup schedule (e.g., daily) and retention period
  5. Optionally check Include database to back up linked databases
  6. Click Save to enable scheduled backups

Microsoft Docs Reference →

Critical: Disable Basic Auth

App Service enables FTP and SCM basic authentication by default. These bypass Entra ID completely. Set 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.

PropertyValue
Defender PlanDefender for Servers (Plan 2)
Azure Policy Built-inYes — VM initiative + Guest Configuration
Diagnostic LogsSyslog, Security Events, Performance Counters via AMA
Key FeatureTrusted Launch with vTPM and Secure Boot

Baseline Controls

NS✓ Supported

NSG on NIC/Subnet

○ ManualCustomer
NS✓ Supported

No Public IP

○ ManualCustomer
IM✓ Supported

Entra ID Login

○ ManualCustomer
IM✓ Supported

Managed Identity

○ ManualCustomer
DP✓ Supported

Encryption at Host

○ ManualCustomer
DP✓ Supported

Azure Disk Encryption

○ ManualCustomer
ES✓ Supported

MDE (Endpoint Detection)

○ ManualCustomer
PV✓ Supported

Trusted Launch

● DefaultMicrosoft
PV✓ Supported

Azure Update Manager

○ ManualCustomer
BR✓ Supported

Azure Backup

○ ManualCustomer

Terraform: Hardened Virtual Machine

hcl
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
  1. Navigate to your Virtual Machine resource in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Click the Network security group link to view or edit NSG rules
  4. Add inbound/outbound security rules following least-privilege principles
  5. Use Application Security Groups (ASGs) to group VMs by role for simplified rules
  6. Verify that no Allow-All rules exist in the effective security rules view
NS-2 — Disable Public IP
  1. Navigate to your Virtual Machine resource in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Click on the Network Interface name
  4. Select IP configurations and click on the IP configuration entry
  5. Under Public IP address, select Disassociate and click Save
  6. To enable secure access, deploy Azure Bastion in the VNet — search for "Bastions" in the portal and click Create
IM-3 — Managed Identities
  1. Navigate to your Virtual Machine resource in the Azure Portal
  2. In the left menu under Settings, select Identity
  3. On the System assigned tab, set Status to On and click Save
  4. Copy the Object (principal) ID for use in role assignments
  5. 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)
  1. Navigate to Disk Encryption Sets in the Azure Portal and click Create
  2. Select the subscription, resource group, and region
  3. Choose the encryption type (e.g., Encryption at rest with a customer-managed key)
  4. Select your Key Vault and encryption key
  5. Click Review + Create to provision the disk encryption set
  6. Navigate to your VM > Disks, click on each disk, and under Encryption select the disk encryption set you created

Microsoft Docs Reference →

ES-1 — Microsoft Defender for Endpoint
  1. Navigate to Microsoft Defender for Cloud in the Azure Portal
  2. In the left menu, select Environment settings
  3. Select your subscription
  4. Under Cloud Workload Protection (CWP), find the Servers row
  5. Toggle the plan to On and select Plan 2 (includes MDE)
  6. Click Settings for the Servers plan and ensure Endpoint protection is enabled
  7. Click Save — MDE will be auto-provisioned to eligible VMs
PV-3 — Secure Configurations
  1. Navigate to Microsoft Defender for Cloud > Recommendations
  2. Filter by resource type "Virtual machines" to see configuration recommendations
  3. Review and remediate flagged items (e.g., "Machines should have vulnerability assessment solution")
  4. For proactive hardening, navigate to Azure Policy and assign the "Azure Security Benchmark" initiative
  5. To enable Azure Automanage, navigate to the VM > Operations > Automanage and select the Best Practices profile
BR-1 — Azure Backup
  1. Navigate to your Virtual Machine resource in the Azure Portal
  2. In the left menu under Operations, select Backup
  3. Select an existing Recovery Services vault or create a new one
  4. Choose or create a backup policy (e.g., daily backup with 30-day retention)
  5. Click Enable backup
  6. To verify, go to the Recovery Services vault > Backup items > Azure Virtual Machines

JIT VM Access Pattern

Never expose management ports (SSH/RDP) permanently. Use Defender for Cloud JIT VM Access to open ports only when needed, for specific IPs, with time-limited windows. Alternatively, use Azure Bastion for browser-based access without any public IP on the VM.

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.

PropertyValue
Defender PlanDefender for App Service
Azure Policy Built-inYes — shares App Service initiative
VNet SupportPremium Plan or ASE required (Consumption = no VNet integration)
Trigger SecurityFunction keys (default), Entra ID (recommended), API Management

Baseline Controls

NS✓ Supported

VNet Integration (Premium)

○ ManualCustomer
NS✓ Supported

Private Endpoint

○ ManualCustomer
IM✓ Supported

Managed Identity

○ ManualCustomer
IM✓ Supported

Disable Function Keys

○ ManualCustomer
DP✓ Supported

HTTPS Only

● DefaultMicrosoft
DP✓ Supported

Encryption at Rest

● DefaultMicrosoft
LT✓ Supported

Diagnostic Settings

○ ManualCustomer

Terraform: Secure Function App (Premium Plan)

hcl
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
  1. Navigate to your Function App resource in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Under Outbound traffic configuration, click VNet Integration
  4. Click Add VNet Integration and select your target VNet and subnet
  5. Toggle Route All to send all outbound traffic through the VNet
  6. Note: VNet Integration requires a Premium (Elastic) or Dedicated (App Service) plan
NS-2 — Private Endpoints
  1. Navigate to your Function App resource in the Azure Portal
  2. In the left menu under Settings, select Networking
  3. Under Inbound traffic configuration, click Private endpoints
  4. Click + Add to create a new private endpoint
  5. Select the VNet, subnet, and configure the private DNS zone
  6. Go back to Networking and set "Public network access" to Disabled
IM-1 — Entra ID Authentication
  1. Navigate to your Function App resource in the Azure Portal
  2. In the left menu under Settings, select Authentication
  3. Click Add identity provider
  4. Select Microsoft as the identity provider
  5. Configure the App Registration (use Express for automatic setup)
  6. Set "Restrict access" to Require authentication
  7. Set unauthenticated requests to return HTTP 401
IM-3 — Managed Identities
  1. Navigate to your Function App resource in the Azure Portal
  2. In the left menu under Settings, select Identity
  3. On the System assigned tab, set Status to On and click Save
  4. To use Key Vault references, go to Configuration > Application settings
  5. Use the format @Microsoft.KeyVault(SecretUri=https://myvault.vault.azure.net/secrets/mysecret/) as the setting value
  6. Grant the managed identity access to Key Vault via Access control (IAM) on the Key Vault resource
DP-3 — Data in Transit Encryption
  1. Navigate to your Function App resource in the Azure Portal
  2. In the left menu under Settings, select Configuration
  3. Select the General settings tab
  4. Set Minimum TLS version to 1.2
  5. Set HTTPS Only to On
  6. 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
  1. Navigate to Microsoft Defender for Cloud in the Azure Portal
  2. In the left menu, select Environment settings
  3. Select your subscription
  4. Under Cloud Workload Protection (CWP), find the App Service row
  5. Toggle the plan to On (this covers both App Service and Functions)
  6. Click Save to enable threat detection for all function apps in the subscription

Consumption Plan Limitations

Azure Functions on the Consumption plan does not support VNet integration or private endpoints. For production workloads requiring network isolation, use the Premium (Elastic) plan or deploy to an App Service Environment (ASE).

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.

PropertyValue
Defender PlanDefender for Containers
Azure Policy Built-inYes — managed identity, ingress settings
VNet SupportCustom VNet with internal-only or external load balancer
Key FeatureServerless containers with VNet isolation and managed identity

Baseline Controls

NS✓ Supported

Internal VNet Environment

○ ManualCustomer
NS✓ Supported

Private Endpoint

○ ManualCustomer
IM✓ Supported

Managed Identity

○ ManualCustomer
DP✓ Supported

HTTPS Ingress (TLS 1.2)

○ ManualCustomer
DP✓ Supported

Secret Management

○ ManualCustomer
LT✓ Supported

Defender for Containers

○ ManualCustomer

Terraform: Secure Container Apps Environment

hcl
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.

PropertyValue
Defender PlanDefender for Servers P2 (MDE on session hosts)
Azure Policy Built-inYes — session host configuration, encryption, extensions
Identity ModelEntra ID Join with SSO (preferred) or Hybrid AD Join
Key FeaturePrivate endpoints for host pool, workspace, and feed access

Baseline Controls

NS✓ Supported

Private Endpoint

○ ManualCustomer
NS✓ Supported

RDP Shortpath

○ ManualCustomer
IM✓ Supported

Entra ID Join

○ ManualCustomer
IM✓ Supported

Managed Identity

○ ManualCustomer
DP✓ Supported

Disk Encryption (CMK)

○ ManualCustomer
DP✓ Supported

RDP Properties Hardening

○ ManualCustomer
ES✓ Supported

MDE on Session Hosts

○ ManualCustomer
LT✓ Supported

Connection Diagnostics

○ ManualCustomer

Terraform: Hardened AVD Host Pool

hcl
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

AVD session hosts are full Windows VMs with user-interactive sessions. Apply CIS benchmarks, enable MDE via Defender for Servers P2, and restrict RDP redirections (clipboard, drives, USB) using host pool RDP properties — especially for environments handling regulated data.