Intermediate ⏱ 100 min 📋 12 Steps

Configure JIT VM Access and Adaptive Controls

Set up Just-in-Time VM access policies, configure adaptive application controls, enable file integrity monitoring, create workflow automations for auto-remediation, and build a monitoring dashboard for healthcare VM environments.

๐Ÿ“‹ Overview

About This Lab

Just-in-Time (JIT) VM access reduces the attack surface by closing management ports on Azure VMs and opening them only when an administrator needs access for a defined time window. Adaptive application controls use machine learning to recommend application allowlists for your VMs, blocking unauthorized executables from running on production workloads. File Integrity Monitoring (FIM) tracks changes to critical system files, registry keys, and configuration files, alerting you when unexpected modifications occur. Workflow automation with Logic Apps enables auto-remediation of security alerts, closing the loop between detection and response without manual intervention. In this lab you will configure all four capabilities across a fleet of Azure VMs and build a unified monitoring dashboard.

๐Ÿข Enterprise Use Case

Scenario: MedSecure Health operates 150 Azure VMs across three regions handling electronic health records (EHR) and patient billing data subject to HIPAA regulations. System administrators require RDP and SSH access during scheduled maintenance windows but management ports must remain closed at all other times to satisfy audit requirements.

The security team needs to ensure only approved healthcare applications run on production VMs, preventing unauthorized software installations by clinical staff. Compliance auditors require evidence that critical system files on patient-facing servers have not been tampered with between audit cycles. The SOC team wants automated responses when a JIT access request is denied or when an unapproved application attempts to execute.

Success criteria: JIT access configured on all 150 VMs, adaptive application controls enforced, FIM active on patient data servers, and automated remediation workflows operational.

๐ŸŽฏ What You Will Learn

  1. Understand how JIT VM access modifies NSG rules dynamically to reduce the attack surface
  2. Enable JIT on target VMs and configure custom port policies with time-range restrictions
  3. Request and approve JIT access through the portal, CLI, and PowerShell
  4. Audit JIT access requests using Azure Activity Logs and KQL queries
  5. Enable and configure adaptive application controls with machine-learning-based allowlists
  6. Handle adaptive application control alerts and manage policy exceptions
  7. Configure File Integrity Monitoring to track changes on critical system paths
  8. Review FIM change detection results and correlate them with security events
  9. Create workflow automations using Logic Apps for auto-remediation of security alerts
  10. Build a consolidated monitoring dashboard for JIT, adaptive controls, and FIM

๐Ÿ”‘ Why This Matters

JIT VM Access and Adaptive Controls are critical for enterprise security because they enforce the principle of least privilege at the network and application layers. Open management ports are among the most exploited attack vectors, and JIT eliminates persistent exposure by granting time-limited access only when needed. Adaptive application controls prevent unauthorized software from executing on production workloads, reducing the risk of malware, ransomware, and insider threats. Combined with File Integrity Monitoring and workflow automation, these capabilities create a closed-loop defense that detects, prevents, and remediates threats without manual intervention. essential for regulated industries like healthcare where compliance evidence must be continuous and auditable.

โš™๏ธ Prerequisites

  • Azure Subscription: An active subscription with at least two Azure VMs deployed (Windows and Linux recommended)
  • Defender for Servers Plan 2: JIT, adaptive application controls, and FIM require Defender for Servers Plan 2 enabled on the subscription
  • Role: Security Admin or Contributor role; Owner role is required for Logic App connections
  • Completed Labs 01 and 02: Familiarity with Defender for Cloud basics from Lab 01 and Lab 02
  • Azure CLI: Version 2.50 or later installed locally or use Azure Cloud Shell
  • PowerShell: Az.Security and Az.Compute modules installed
  • Log Analytics Workspace: An existing workspace connected to Defender for Cloud for continuous export
  • Network Security Groups: At least one NSG attached to the target VMs (JIT modifies NSG rules dynamically)
Note: Defender for Servers Plan 2 is a paid plan. Review the pricing page before enabling on production subscriptions. A free trial subscription works for this lab.

Step 1 ยท Understand JIT VM Access Concepts

Just-in-Time VM access is a Defender for Cloud feature that locks down inbound traffic to management ports on Azure VMs. When an administrator needs access, they submit a request specifying the port, source IP, and duration. Defender for Cloud then creates a temporary NSG rule to allow the connection.

How JIT Works

  • When JIT is enabled on a VM, Defender for Cloud adds deny-all inbound rules for the configured ports to the associated NSG
  • When an access request is approved, a time-limited allow rule is inserted with higher priority than the deny rule
  • The allow rule automatically expires after the specified duration, restoring the deny state
  • All requests are logged in the Azure Activity Log, providing a full audit trail of who accessed which VM and when

Supported Ports and Protocols

  • RDP (3389): Windows remote desktop; the most commonly targeted management port
  • SSH (22): Linux secure shell; frequently scanned by automated attack tools
  • WinRM (5985/5986): Windows Remote Management for PowerShell remoting
  • Custom ports: Any TCP port can be configured, such as custom application management ports

Why JIT Matters for Healthcare

  • HIPAA requires that access to systems handling protected health information (PHI) be limited to the minimum necessary
  • Open management ports are a primary attack vector for ransomware targeting healthcare organizations
  • JIT provides auditable, time-limited access that satisfies both security best practices and regulatory requirements
Key Insight: JIT does not replace network segmentation or identity-based access controls. It is one layer in a defense-in-depth strategy. Combine JIT with Azure Bastion, Privileged Identity Management, and conditional access for maximum protection.

Step 2 ยท Enable JIT VM Access on Target VMs

Enable JIT on your target VMs to lock down management ports. You can enable JIT through the Azure portal, Azure CLI, or PowerShell.

Enable via the Azure Portal

  1. Navigate to Microsoft Defender for Cloud in the Azure portal
  2. Select Workload protections from the left menu
  3. Click Just-in-time VM access under Advanced protection
  4. On the Not configured tab, select the VMs you want to protect
  5. Click Enable JIT on selected VMs
  6. Review the default port configuration (22, 3389, 5985, 5986) and click Save

Enable via Azure CLI

# Set the target subscription where your VMs reside.
# Replace YOUR_SUBSCRIPTION_ID with your Azure subscription GUID
# (find it via: az account list -o table).
az account set --subscription "YOUR_SUBSCRIPTION_ID"

# Enable JIT on a specific VM with SSH (22) and RDP (3389) ports.
# --resource-group: the RG containing the target VM.
# --location: Azure region of the VM (must match exactly).
# --name "default": JIT policy name; "default" is the standard convention.
# "maxRequestAccessDuration": "PT3H" = max 3-hour access window per request
#   (ISO 8601 duration - PT1H = 1 hour, PT30M = 30 minutes).
# WHY: Once enabled, Defender for Cloud adds deny-all NSG rules on these
# ports, closing them to all inbound traffic until a JIT request is approved.
az security jit-policy create \
  --resource-group "rg-healthcare-vms" \
  --location "eastus" \
  --name "default" \
  --virtual-machines "[{ \
    \"id\":\"/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-healthcare-vms/providers/Microsoft.Compute/virtualMachines/vm-ehr-01\", \
    \"ports\":[{\"number\":22,\"protocol\":\"TCP\",\"maxRequestAccessDuration\":\"PT3H\"}, \
               {\"number\":3389,\"protocol\":\"TCP\",\"maxRequestAccessDuration\":\"PT3H\"}] \
  }]"

# Verify JIT is enabled by listing protected VMs.
# Expected output: a table showing the full resource ID of each VM
# covered by the JIT policy. If empty, the policy did not apply.
az security jit-policy show \
  --resource-group "rg-healthcare-vms" \
  --location "eastus" \
  --name "default" \
  --query "virtualMachines[].id" -o table

Enable via PowerShell

# Authenticate to Azure and set the subscription context.
# Replace YOUR_SUBSCRIPTION_ID with your Azure subscription GUID.
Connect-AzAccount
Set-AzContext -SubscriptionId "YOUR_SUBSCRIPTION_ID"

# Define the JIT policy for a single VM.
# "id": full ARM resource ID of the VM to protect.
# "ports": array of management ports to lock down.
#   number 22  = SSH (Linux); number 3389 = RDP (Windows).
#   maxRequestAccessDuration "PT3H" = admins can request up to 3 hours
#   of access per session (ISO 8601 duration format).
$jitPolicy = @{
    id    = "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-healthcare-vms/providers/Microsoft.Compute/virtualMachines/vm-ehr-01"
    ports = @(
        @{ number = 22;   protocol = "TCP"; maxRequestAccessDuration = "PT3H" }
        @{ number = 3389; protocol = "TCP"; maxRequestAccessDuration = "PT3H" }
    )
}

# Apply the JIT policy. This adds deny-all inbound NSG rules for
# the specified ports immediately, blocking all management access
# until a time-limited JIT request is approved.
# -Kind "Basic": standard JIT policy type.
Set-AzJitNetworkAccessPolicy `
  -ResourceGroupName "rg-healthcare-vms" `
  -Location "eastus" `
  -Name "default" `
  -VirtualMachine $jitPolicy `
  -Kind "Basic"

# Confirm the policy was created. The output lists all VMs and ports
# protected by JIT in this resource group and region.
Get-AzJitNetworkAccessPolicy -ResourceGroupName "rg-healthcare-vms" -Location "eastus"
Tip: After enabling JIT, check the NSG attached to your VM. You will see new deny rules for the configured ports with the description "SecurityCenter-JITRule." These rules block all inbound traffic to management ports until a JIT request is approved.

Step 3 ยท Request and Approve JIT Access

Once JIT is enabled, administrators must request access each time they need to connect to a protected VM. Requests can be submitted through the portal, CLI, or API.

Request Access via the Portal

  1. Navigate to Defender for Cloud and select Just-in-time VM access
  2. On the Configured tab, locate your target VM
  3. Click Request access
  4. Select the ports to open (e.g., RDP 3389)
  5. Set the duration (e.g., 1 hour) and optionally restrict to your source IP
  6. Add a justification note (e.g., "Scheduled patching for EHR application")
  7. Click Open ports to submit the request

Request Access via Azure CLI

# Request time-limited RDP access to a JIT-protected VM.
# This creates a temporary allow rule in the NSG for the specified port.
# --name "default": must match the JIT policy name set during enablement.
# "allowedSourceAddressPrefix": restrict access to YOUR IP only (never use
#   "*" - a wildcard defeats the purpose of JIT by allowing any source).
# "endTimeUtc": ISO 8601 timestamp when access expires automatically.
#   The NSG allow rule is removed after this time, re-blocking the port.
# WHY: Provides auditable, time-limited access that satisfies HIPAA
# minimum-necessary requirements and prevents persistent port exposure.
az security jit-policy initiate \
  --resource-group "rg-healthcare-vms" \
  --location "eastus" \
  --name "default" \
  --virtual-machines "[{ \
    \"id\":\"/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-healthcare-vms/providers/Microsoft.Compute/virtualMachines/vm-ehr-01\", \
    \"ports\":[{\"number\":3389,\"allowedSourceAddressPrefix\":\"203.0.113.50\",\"endTimeUtc\":\"2026-03-06T18:00:00Z\"}] \
  }]"

Request Access via PowerShell

# Define the JIT access request for RDP (port 3389).
# "allowedSourceAddressPrefix": your workstation IP - only this IP can
#   connect during the access window. Use your actual public IP here.
# "endTimeUtc": calculates a 2-hour window from now. The temporary
#   NSG allow rule auto-expires after this timestamp.
# WHY: Every JIT request is logged in Azure Activity Log, creating a
# full audit trail of who accessed which VM, when, and from where.
$request = @{
    id    = "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-healthcare-vms/providers/Microsoft.Compute/virtualMachines/vm-ehr-01"
    ports = @(
        @{
            number                   = 3389
            allowedSourceAddressPrefix = "203.0.113.50"
            endTimeUtc               = (Get-Date).AddHours(2).ToUniversalTime().ToString("o")
        }
    )
}

# Submit the JIT access request. On success, the NSG is updated with
# a time-limited allow rule. You can now RDP to the VM from 203.0.113.50.
# Expected output: the request status and the ports that were opened.
Start-AzJitNetworkAccessPolicy `
  -ResourceGroupName "rg-healthcare-vms" `
  -Location "eastus" `
  -Name "default" `
  -VirtualMachine $request
Important: Always restrict the source IP to a known address rather than using "Any." Using a wildcard source IP defeats the purpose of JIT by allowing connections from any location during the access window.

Step 4 ยท Configure Custom JIT Policies with Specific Ports and Time Ranges

Default JIT policies cover common ports, but enterprise environments often need custom configurations. You can define per-port maximum durations, restrict source IP ranges, and add application-specific management ports.

Custom Policy for Healthcare VMs

# Custom JIT policy with per-port restrictions tailored for healthcare VMs.
# RDP (3389): max 1-hour window - sufficient for routine maintenance.
# SSH (22):  max 2-hour window - for Linux admin tasks.
# Port 8443: custom EHR management port, max 30 minutes - tightest window
#   because EHR admin interfaces are high-value targets.
# "allowedSourceAddressPrefixes": restricts which networks can request
#   access. RFC 1918 ranges (10.x, 172.16.x) ensure only internal
#   corporate networks are allowed - no internet-based requests.
# WHY: Per-port durations follow least-privilege principles. Short windows
# for sensitive ports reduce the attack surface during maintenance.
az security jit-policy create \
  --resource-group "rg-healthcare-vms" \
  --location "eastus" \
  --name "default" \
  --virtual-machines "[{ \
    \"id\":\"/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-healthcare-vms/providers/Microsoft.Compute/virtualMachines/vm-ehr-01\", \
    \"ports\":[ \
      {\"number\":3389,\"protocol\":\"TCP\",\"maxRequestAccessDuration\":\"PT1H\",\"allowedSourceAddressPrefixes\":[\"10.0.0.0/8\",\"172.16.0.0/12\"]}, \
      {\"number\":22,\"protocol\":\"TCP\",\"maxRequestAccessDuration\":\"PT2H\",\"allowedSourceAddressPrefixes\":[\"10.0.0.0/8\"]}, \
      {\"number\":8443,\"protocol\":\"TCP\",\"maxRequestAccessDuration\":\"PT30M\",\"allowedSourceAddressPrefixes\":[\"10.10.0.0/16\"]} \
    ] \
  }]"

Bulk Enable JIT on Multiple VMs with PowerShell

# Define a standard port policy applied uniformly across all VMs.
# PT2H/PT1H/PT30M = max access durations (2h SSH, 1h RDP, 30m EHR port).
# Using consistent policies across a fleet simplifies auditing and ensures
# no VM is left with overly permissive access windows.
$standardPorts = @(
    @{ number = 22;   protocol = "TCP"; maxRequestAccessDuration = "PT2H" }
    @{ number = 3389; protocol = "TCP"; maxRequestAccessDuration = "PT1H" }
    @{ number = 8443; protocol = "TCP"; maxRequestAccessDuration = "PT30M" }
)

# Retrieve all VMs in the resource group to build policies dynamically.
# This avoids hardcoding VM IDs and ensures new VMs are included.
$vms = Get-AzVM -ResourceGroupName "rg-healthcare-vms"

# Build a JIT policy object for each VM using the standard port template.
# ForEach-Object maps each VM's ARM resource ID to the port policy.
$jitVMs = $vms | ForEach-Object {
    @{
        id    = $_.Id
        ports = $standardPorts
    }
}

# Apply the JIT policy to ALL VMs in a single API call.
# This is far more efficient than enabling JIT per-VM individually.
# Expected output: confirmation that the policy covers N VMs.
Set-AzJitNetworkAccessPolicy `
  -ResourceGroupName "rg-healthcare-vms" `
  -Location "eastus" `
  -Name "default" `
  -VirtualMachine $jitVMs `
  -Kind "Basic"

Write-Host "JIT enabled on $($vms.Count) VMs"
Tip: Set the maximum request duration to the shortest practical window. For routine maintenance, 1 hour is usually sufficient. For emergency access, consider a separate policy with a 3-hour maximum that requires additional approval.

Step 5 ยท Audit JIT Access Requests with Activity Logs

Every JIT access request generates entries in the Azure Activity Log. You can query these logs with KQL to build audit reports for compliance teams.

View JIT Activity in the Portal

  1. Navigate to Defender for Cloud and select Just-in-time VM access
  2. On the Configured tab, select a VM and click the ellipsis menu
  3. Select Activity Log to view all JIT-related operations
  4. Filter by operation name Initiate JIT Network Access Policy to see access requests

KQL: Query JIT Access Requests

// Retrieve all JIT access requests from the last 30 days.
// WHY: Builds an audit trail for HIPAA compliance - auditors require
// evidence of who accessed patient-facing servers and when.
// OperationNameValue filter targets only JIT initiate actions,
// excluding policy creation/deletion events.
// OUTPUT: Table of timestamped requests showing the requesting user
// (Caller), target resource group, specific VM, approval status,
// and the justification provided for the access request.
AzureActivity
| where TimeGenerated > ago(30d)
| where OperationNameValue == "Microsoft.Security/locations/jitNetworkAccessPolicies/initiate/action"
| project
    TimeGenerated,
    Caller,                                                      // User or SPN who requested access
    ResourceGroup,
    ResourceId = tostring(parse_json(Properties).resourceId),     // Target VM resource ID
    Status = ActivityStatusValue,                                 // Succeeded = approved, Failed = denied
    Justification = tostring(parse_json(Properties).justification)
| order by TimeGenerated desc

KQL: Identify Denied JIT Requests

// Find denied or failed JIT access attempts in the past 7 days.
// WHY: Repeated denied requests may indicate a compromised account
// trying to gain access, or a misconfigured JIT policy blocking
// legitimate administrators. Both scenarios require investigation.
// has "jitNetworkAccessPolicies": catches all JIT operations (initiate,
//   create, delete) to surface any failures across the policy lifecycle.
// OUTPUT: Count of denied attempts per user (Caller) and resource group,
// with the most recent attempt timestamp. Investigate callers with high
// DeniedCount values as priority security incidents.
AzureActivity
| where TimeGenerated > ago(7d)
| where OperationNameValue has "jitNetworkAccessPolicies"
| where ActivityStatusValue in ("Failed", "Denied")
| summarize
    DeniedCount = count(),
    LastAttempt = max(TimeGenerated)
    by Caller, ResourceGroup
| order by DeniedCount desc
Key Insight: Repeated denied JIT requests from the same user may indicate a compromised account attempting to gain access, or a misconfigured policy. Investigate denied requests as potential security incidents.

Step 6 ยท Enable Adaptive Application Controls

Adaptive application controls analyze the applications running on your VMs over a learning period and recommend allowlist policies. Once enforced, only approved applications can execute on protected machines.

Enable via the Portal

  1. In Defender for Cloud, select Workload protections
  2. Click Adaptive application controls under Advanced protection
  3. Review the VM groups that Defender for Cloud has automatically identified based on workload similarity
  4. Select a group (e.g., "Group_EHR_Servers") and review the recommended allowlist
  5. The recommendations are based on a 14-day learning period analyzing executable activity on the VMs
  6. Click Audit to start monitoring without blocking, or Enforce to actively block unapproved applications

Enforcement Modes

  • Audit mode: Logs violations but does not block execution; recommended for the initial rollout period
  • Enforce mode: Actively blocks unapproved applications using Windows AppLocker or Linux auditd
  • None: Disables the policy for the VM group

Recommended Rollout Strategy

  • Start in Audit mode for 2 to 4 weeks to identify false positives
  • Review alerts and add legitimate applications to the allowlist
  • Move to Enforce mode only after the allowlist is validated by application owners
  • Maintain a change management process for adding new approved applications
Important: Do not enable Enforce mode without completing the Audit phase. Blocking legitimate healthcare applications could disrupt patient care and violate availability requirements.

Step 7 ยท Review and Approve Application Allowlists

After the learning period, review the recommended allowlists to verify that all legitimate applications are included and no malicious software has been inadvertently allowlisted.

Review Allowlist Recommendations

  1. In Adaptive application controls, select the target VM group
  2. Review each recommended application entry: publisher, path, and hash
  3. Verify that known healthcare applications (e.g., EHR client, DICOM viewers, lab software) are in the list
  4. Remove any entries that appear suspicious or should not run in production
  5. Add custom rules for applications that were not detected during the learning period
  6. Click Save to apply the updated allowlist

Allowlist Rule Types

  • Publisher rules: Allow applications signed by a specific certificate publisher (recommended for vendor software)
  • Path rules: Allow executables from a specific file path (use cautiously as attackers can place files in allowed paths)
  • Hash rules: Allow a specific file hash; most restrictive but breaks when the application is updated

KQL: Review Adaptive Application Control Violations

// List adaptive application control violations from the last 7 days.
// WHY: Each violation means an unapproved executable ran (or attempted
// to run) on a protected VM. In audit mode this is logged; in enforce
// mode the application was blocked. Either way, review is required to
// determine if it is a legitimate new application or malicious activity.
// OUTPUT: Table of alerts showing the affected VM (CompromisedEntity),
// description of the blocked application, and suggested remediation steps.
// High volume from one VM may indicate malware or a poorly tuned allowlist.
SecurityAlert
| where TimeGenerated > ago(7d)
| where AlertType has "AdaptiveApplication"
| project
    TimeGenerated,
    AlertName,
    CompromisedEntity,
    Description,
    RemediationSteps
| order by TimeGenerated desc
Tip: Use publisher-based rules for vendor applications that receive frequent updates. This avoids breaking the allowlist every time the vendor releases a patch, while still validating the code signature.

Step 8 ยท Handle Adaptive Application Control Alerts

When an unapproved application runs on a protected VM, Defender for Cloud generates a security alert. Proper alert handling ensures real threats are investigated while false positives are resolved quickly.

Alert Triage Process

  1. Navigate to Defender for Cloud and select Security alerts
  2. Filter alerts by type: Adaptive application control policy violation
  3. Click an alert to see the details: the blocked application, file path, publisher, hash, and the VM it ran on
  4. Determine if the application is legitimate (e.g., a newly installed medical device driver) or suspicious
  5. If legitimate: add the application to the allowlist and dismiss the alert
  6. If suspicious: isolate the VM, investigate the file, and escalate as a security incident

Common False Positive Scenarios

  • Windows Update installing new system components not in the original learning period
  • IT deploying a new version of approved software with a different file hash
  • Temporary diagnostic tools uploaded by vendor support during a troubleshooting session
  • Antivirus signature updates that modify executable components

KQL: Alert Frequency by VM

// Identify which VMs generate the most application control alerts.
// WHY: A VM with many alerts is either running unapproved software
// (potential compromise) or has a misconfigured allowlist (false positives).
// UniqueApps: count of distinct blocked application names - a high number
//   suggests the allowlist is missing many legitimate apps; a low number
//   with high AlertCount suggests the same app keeps executing (persistent
//   malware or a recurring false positive that should be allow-listed).
// OUTPUT: Table ranked by AlertCount. Investigate the top entries first.
SecurityAlert
| where TimeGenerated > ago(30d)
| where AlertType has "AdaptiveApplication"
| summarize
    AlertCount = count(),
    UniqueApps = dcount(tostring(parse_json(ExtendedProperties).["Application Name"]))
    by CompromisedEntity
| order by AlertCount desc
Key Insight: A VM generating a high volume of application control alerts may indicate either a poorly tuned allowlist or active malicious activity. Investigate the root cause before simply adding exceptions.

Step 9 ยท Configure File Integrity Monitoring (FIM)

File Integrity Monitoring tracks changes to operating system files, application binaries, Windows registry keys, and configuration files. It alerts you when critical files are modified unexpectedly.

Enable FIM via the Portal

  1. In Defender for Cloud, select Workload protections
  2. Click File integrity monitoring under Advanced protection
  3. Select the Log Analytics workspace connected to your VMs
  4. Click Enable to start monitoring with default paths
  5. After enabling, click Settings to customize monitored paths

Configure Monitored Paths for Healthcare VMs

  • Windows files: C:\Windows\System32\drivers\etc\hosts, C:\Windows\System32\*.dll
  • Windows registry: HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Run, HKLM\SYSTEM\CurrentControlSet\Services
  • Linux files: /etc/passwd, /etc/shadow, /etc/ssh/sshd_config, /etc/hosts
  • Application files: Add custom paths for your EHR application binaries and configuration files

PowerShell: Configure FIM Custom Paths

# Point to the Log Analytics workspace where FIM data will be stored.
# Replace YOUR_SUBSCRIPTION_ID with your actual subscription GUID.
$workspaceId = "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-security/providers/Microsoft.OperationalInsights/workspaces/law-healthcare"

# Enable the ChangeTracking solution in the workspace.
# This is the backend service that powers FIM - it collects file,
# registry, and Linux file change events from connected VMs.
# WHY: Without this solution enabled, FIM has no data pipeline.
Set-AzOperationalInsightsSolution `
  -ResourceGroupName "rg-security" `
  -WorkspaceName "law-healthcare" `
  -SolutionType "ChangeTracking"

# Configure a custom file tracking rule for EHR application configs.
# Kind "WindowsPath": monitors files on Windows VMs at the specified path.
# Path wildcard *.xml: tracks all XML config files in the EHR directory.
# Recurse $true: includes subdirectories for complete coverage.
# GroupTag: labels these changes in reports for easy filtering.
# WHY: EHR config tampering could alter patient data processing rules,
# medication dosage calculations, or billing integrations - all critical
# for HIPAA compliance and patient safety.
$fileTrackingConfig = @{
    Name         = "EHR_App_Config"
    ResourceGroup = "rg-security"
    WorkspaceName = "law-healthcare"
    Kind         = "WindowsPath"
    Path         = "C:\\EHRApp\\Config\\*.xml"
    Recurse      = $true
    GroupTag      = "EHR Application"
}

# Apply the tracking configuration to start monitoring.
# Changes to these files will appear in the ConfigurationChange table.
New-AzOperationalInsightsDataSource @fileTrackingConfig
Important: FIM generates data that is ingested into Log Analytics. Monitor the data volume to avoid unexpected costs. Start with critical paths only and expand coverage gradually.

Step 10 ยท Review FIM Change Detection Results

After FIM is active, changes to monitored files and registry keys appear in the Log Analytics workspace. Use KQL to query change records and identify unauthorized modifications.

View Changes in the Portal

  1. In Defender for Cloud, select File integrity monitoring
  2. Select the Log Analytics workspace
  3. The dashboard shows a summary of changes categorized by type: files, registry, and Linux files
  4. Click a category to drill down into individual change records
  5. Review each change: what file was modified, the old content, the new content, and which user made the change

KQL: Query File Changes

// List all monitored file changes from the last 7 days.
// WHY: Unexpected file modifications on patient-facing servers may
// indicate malware installation, unauthorized config changes, or
// insider threats. HIPAA requires evidence that critical files have
// not been tampered with between audit cycles.
// ConfigChangeType == "Files": filters to file-system changes only
//   (excludes registry and Linux file changes).
// OUTPUT: Each row shows the timestamp, affected machine, file path,
// change category (Modified/Created/Deleted), before/after file sizes,
// and the account that made the change. Sudden size changes or
// unexpected accounts are red flags.
ConfigurationChange
| where TimeGenerated > ago(7d)
| where ConfigChangeType == "Files"
| project
    TimeGenerated,
    Computer,
    FileSystemPath,
    ChangeCategory,
    PreviousSize = FileContentOldSize,
    CurrentSize = FileContentNewSize,
    ModifiedBy = Account
| order by TimeGenerated desc

KQL: Detect Suspicious Registry Changes

// Detect registry changes to auto-start and service registration keys.
// WHY: Attackers commonly add entries to CurrentVersion\Run (auto-start
// on login) or CurrentControlSet\Services (install as a Windows service)
// to achieve persistence after initial compromise. Changes to these keys
// outside of approved maintenance windows are high-priority alerts.
// has_any: matches if the registry key path contains either substring.
// OUTPUT: Each row shows the machine, exact registry key, value name,
// value data (e.g., path to a new executable), and whether it was
// Created, Modified, or Deleted. New entries pointing to unexpected
// executables warrant immediate investigation.
ConfigurationChange
| where TimeGenerated > ago(7d)
| where ConfigChangeType == "Registry"
| where RegistryKey has_any ("CurrentVersion\\Run", "CurrentControlSet\\Services")
| project
    TimeGenerated,
    Computer,
    RegistryKey,
    RegistryValueName = ValueName,
    RegistryValueData = ValueData,
    ChangeCategory
| order by TimeGenerated desc

KQL: Correlate FIM Changes with Security Alerts

// Correlate file changes with security alerts on the same machine
// within a 60-minute window. This is a powerful indicator of compromise:
// if a file changed shortly before or after a security alert fired,
// the file change is likely related to the attack.
// WHY: Isolated file changes are often benign (patches, updates), but
// changes that coincide with alerts (e.g., malware detection, brute-force
// attempt) strongly suggest an active threat requiring urgent response.
// abs(datetime_diff("minute"...)): matches changes within ยฑ60 minutes of
//   the alert, regardless of which happened first.
// OUTPUT: Paired rows showing the file change and the correlated alert.
// Prioritise these over standalone FIM or alert events.
let fileChanges = ConfigurationChange
| where TimeGenerated > ago(7d)
| where ConfigChangeType == "Files"
| project ChangeTime = TimeGenerated, Computer, FileSystemPath;
let alerts = SecurityAlert
| where TimeGenerated > ago(7d)
| project AlertTime = TimeGenerated, CompromisedEntity, AlertName;
fileChanges
| join kind=inner (alerts) on $left.Computer == $right.CompromisedEntity
| where abs(datetime_diff("minute", ChangeTime, AlertTime)) < 60
| project ChangeTime, Computer, FileSystemPath, AlertTime, AlertName
| order by ChangeTime desc
Tip: File changes that coincide with security alerts within 60 minutes are strong indicators of compromise. Prioritize investigation of correlated events over isolated file changes.

Step 11 ยท Create Workflow Automation with Logic Apps for Auto-Remediation

Workflow automation in Defender for Cloud triggers Logic Apps in response to security alerts and recommendations. You can build automated remediation flows that respond to JIT, adaptive application control, and FIM events.

Create an Automation in the Portal

  1. In Defender for Cloud, select Workflow automation from the left menu
  2. Click Add workflow automation
  3. Set the trigger type to Threat detection alert
  4. Filter by alert name: select alerts related to adaptive application controls
  5. Set the severity filter to High and Medium
  6. Select an existing Logic App or create a new one
  7. Click Create to activate the automation

PowerShell: Create a Workflow Automation

# Define the scope and Logic App target for the workflow automation.
# $automationScope: the subscription to monitor for security alerts.
# $logicAppId: the ARM resource ID of the Logic App that will execute
#   when alerts match the filter criteria.
# Replace YOUR_SUBSCRIPTION_ID with your actual subscription GUID.
$automationScope = "/subscriptions/YOUR_SUBSCRIPTION_ID"
$logicAppId = "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-security/providers/Microsoft.Logic/workflows/auto-remediate-jit"

# Build the workflow automation definition as a JSON payload.
# isEnabled: activates the automation immediately after creation.
# scopes: which subscriptions/resource groups to monitor.
# sources.eventSource "Alerts": triggers on Defender for Cloud security alerts.
# ruleSets.rules: filter to High-severity alerts only - avoids noisy
#   automation triggers from informational or low-severity findings.
# actions.actionType "LogicApp": calls the specified Logic App when
#   a matching alert fires. The Logic App handles the actual remediation
#   (e.g., isolating a VM, notifying the SOC, creating a ticket).
# WHY: Automated response closes the gap between alert detection and
# remediation, critical in healthcare where every minute of exposure
# to an active threat risks patient data compromise.
$body = @{
    properties = @{
        description = "Auto-remediate JIT and adaptive control alerts"
        isEnabled   = $true
        scopes      = @(@{ scopePath = $automationScope })
        sources     = @(@{
            eventSource = "Alerts"
            ruleSets    = @(@{
                rules = @(
                    @{
                        propertyJPath  = "Severity"
                        propertyType   = "String"
                        expectedValue  = "High"
                        operator       = "Equals"
                    }
                )
            })
        })
        actions     = @(@{
            actionType     = "LogicApp"
            logicAppResourceId = $logicAppId
            uri            = "https://prod-eastus.logic.azure.com:443/workflows/YOUR_WORKFLOW_ID/triggers/manual/paths/invoke"
        })
    }
} | ConvertTo-Json -Depth 10

# Deploy the automation via the Azure REST API.
# PUT method creates or updates the automation resource.
# api-version 2019-01-01-preview: the Security automations API version.
# Expected output: HTTP 200/201 with the created automation resource JSON.
Invoke-AzRestMethod -Method PUT `
  -Path "/subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/rg-security/providers/Microsoft.Security/automations/jit-auto-remediation?api-version=2019-01-01-preview" `
  -Payload $body

Logic App Design: Auto-Isolate VM on Critical Alert

  1. Trigger: When an HTTP request is received (from Defender for Cloud workflow automation)
  2. Parse JSON: Extract the alert details including CompromisedEntity and AlertSeverity
  3. Condition: Check if AlertSeverity equals "High"
  4. If true: Call the Azure VM API to apply a restrictive NSG that blocks all inbound and outbound traffic
  5. Send notification: Post a message to the SOC Teams channel with the VM name, alert details, and isolation confirmation
  6. Create ticket: Optionally create a ServiceNow or Jira incident for the security team
Important: Test automated remediation in a non-production environment first. Auto-isolating a VM running critical healthcare applications could cause patient care disruptions. Include approval gates for production workloads.

Step 12 ยท Build a JIT and Adaptive Controls Monitoring Dashboard

Create an Azure Monitor Workbook that consolidates JIT access activity, adaptive application control alerts, and FIM change data into a single dashboard for the security operations team.

Create the Workbook

  1. Navigate to Azure Monitor and select Workbooks
  2. Click New to create a blank workbook
  3. Add a text block with the title "JIT and Adaptive Controls Dashboard"
  4. Add query panels for each data source as described below

Panel 1: JIT Access Request Summary

// Daily summary of JIT access requests over 30 days.
// WHY: Trends reveal whether JIT adoption is growing, whether denied
// requests are increasing (potential policy issues), and whether
// access patterns align with scheduled maintenance windows.
// OUTPUT: Time series with daily counts of total, approved, and denied
// requests. Visualise as a line chart to spot anomalies (e.g., weekend
// spikes or sudden increases in denied requests).
AzureActivity
| where TimeGenerated > ago(30d)
| where OperationNameValue has "jitNetworkAccessPolicies"
| summarize
    TotalRequests = count(),
    Approved = countif(ActivityStatusValue == "Succeeded"),
    Denied = countif(ActivityStatusValue == "Failed")
    by bin(TimeGenerated, 1d)
| order by TimeGenerated asc

Panel 2: Top JIT Users

// Top 10 users requesting JIT access in the last 30 days.
// WHY: Identifies which administrators use JIT most frequently.
// Unusually high request counts from a single user may indicate
// shared credentials, automation misconfiguration, or a compromised
// account systematically trying to gain access to VMs.
// OUTPUT: Bar chart of users ranked by request volume.
AzureActivity
| where TimeGenerated > ago(30d)
| where OperationNameValue has "jitNetworkAccessPolicies/initiate"
| summarize RequestCount = count() by Caller
| top 10 by RequestCount desc

Panel 3: Adaptive Application Control Alert Trend

// Daily trend of adaptive application control violations over 30 days.
// WHY: A declining trend after initial rollout is expected as the
// allowlist matures. A sudden spike may indicate new malware, an
// unauthorised software installation, or a policy change that
// invalidated existing allowlist entries.
// OUTPUT: Line chart showing daily alert counts. Correlate spikes
// with known change events (patches, deployments) to distinguish
// false positives from genuine threats.
SecurityAlert
| where TimeGenerated > ago(30d)
| where AlertType has "AdaptiveApplication"
| summarize AlertCount = count() by bin(TimeGenerated, 1d)
| order by TimeGenerated asc

Panel 4: FIM Changes by Category

// Distribution of FIM-detected changes by type over 30 days.
// Categories: Files, Registry, Linux files.
// WHY: Gives a quick health check - a balanced distribution is normal,
// but a sudden dominance of one category (e.g., many Registry changes)
// may signal an attack targeting persistence mechanisms.
// OUTPUT: Pie chart showing the proportion of each change type.
// Use this to decide where to focus deeper investigation.
ConfigurationChange
| where TimeGenerated > ago(30d)
| summarize ChangeCount = count() by ConfigChangeType
| order by ChangeCount desc
  1. Set Panel 1 to Line chart to visualize JIT request trends over time
  2. Set Panel 2 to Bar chart for top JIT users
  3. Set Panel 3 to Line chart for adaptive control alert trends
  4. Set Panel 4 to Pie chart for FIM change distribution
  5. Click Save and name the workbook "JIT and Adaptive Controls Dashboard"
  6. Pin the workbook to an Azure Dashboard and share it with the SOC team
Tip: Add a time range parameter to the workbook so users can adjust the analysis window. Include a subscription filter if your organization manages multiple subscriptions from a single dashboard.

Summary

What You Accomplished

  • Learned how JIT VM access dynamically modifies NSG rules to close management ports and open them on demand
  • Enabled JIT on target VMs using the portal, Azure CLI, and PowerShell
  • Requested and approved JIT access with source IP restrictions and time-limited windows
  • Configured custom JIT policies with per-port duration limits and bulk-enabled JIT across multiple VMs
  • Audited JIT access requests using KQL queries against the Azure Activity Log
  • Enabled adaptive application controls and reviewed machine-learning-based allowlist recommendations
  • Handled adaptive application control alerts and managed allowlist exceptions
  • Configured File Integrity Monitoring to track changes to critical system files and registry keys
  • Reviewed FIM change detection results and correlated file changes with security alerts
  • Created workflow automations with Logic Apps for automated alert response
  • Built a consolidated monitoring dashboard for JIT, adaptive controls, and FIM

Cost Considerations

  • JIT, adaptive application controls, and FIM are included with Defender for Servers Plan 2 at no additional per-feature cost
  • FIM data ingestion into Log Analytics generates data volume charges; monitor ingestion rates
  • Logic Apps are billed per execution; high-volume automations may incur costs depending on trigger frequency
  • If using this in a lab environment, disable Defender for Servers Plan 2 after completing the lab

Cleanup (Lab Environment Only)

  • Remove JIT policies: az security jit-policy delete --resource-group "rg-healthcare-vms" --location "eastus" --name "default"
  • Disable adaptive application controls: set the VM group policy to "None" in the portal
  • Disable FIM: remove the ChangeTracking solution from the Log Analytics workspace
  • Delete workflow automations and Logic Apps created during the lab

Next Steps

  • Next Lab: Configure Defender for Containers and Container Registry Scanning
  • Integrate JIT access logs with Microsoft Sentinel for cross-platform correlation
  • Combine adaptive application controls with Azure Policy for enforcement at the subscription level
  • Extend FIM to monitor Kubernetes pod configurations in containerized healthcare workloads
  • Implement Azure Bastion alongside JIT for a fully browser-based, port-free management experience

๐Ÿ“š Documentation Resources

ResourceDescription
Just-in-time VM access usage and configurationConfigure and manage time-limited VM port access requests
Adaptive application controls overviewUse ML-based allowlists to block unauthorized applications on VMs
File Integrity Monitoring in Defender for CloudTrack changes to critical system files and registry keys
Automate responses to Defender for Cloud triggersCreate Logic App workflows for automated alert remediation
Understanding JIT VM accessLearn how JIT modifies NSG rules to reduce attack surface
Defender for Servers plans overviewCompare Plan 1 and Plan 2 features for server workloads
Azure Monitor WorkbooksBuild interactive dashboards for security monitoring and reporting
Azure Logic Apps overviewDesign automated workflows for cloud security operations
Microsoft Defender for Cloud pricingReview per-server hourly billing and plan cost estimates
← All Labs Next Lab →