Skip to main content
Version: 2604 (Preview)

Task 05: Security & Compliance Validation

Runbook Azure

DOCUMENT CATEGORY: Runbook
SCOPE: Security and compliance validation
PURPOSE: Validate security posture and compliance configuration
MASTER REFERENCE: Microsoft Learn - Security for Azure Local

Status: Active

Overview​

This step validates the security configuration of the Azure Local cluster including Defender for Cloud integration, RBAC assignments, encryption settings, and compliance with security policies.

Prerequisites​

  • Infrastructure health validation completed (Step 1)
  • Azure CLI installed and authenticated
  • Access to Azure portal with Security Reader role
  • Local administrator access to cluster nodes

Report Output​

All validation results are saved to:

\\<ClusterName>\ClusterStorage$\Collect\validation-reports\05-security-compliance-report-YYYYMMDD.txt

Variables from variables.yml​

Variable PathTypeDescription
azure.subscription.idStringAzure subscription ID for Defender/policy queries
azure.resource_group.nameStringResource group containing Azure Local cluster resources
compute.nodes[].nameStringNode hostnames for per-node security checks
operations.policy.azure_policy_azurelocal_encryptionBooleanRequired policy: encryption at host
operations.policy.azure_policy_azurelocal_secured_coreBooleanRequired policy: secured core
security.keyvault.nameStringKey Vault name for encryption key validation

Part 1: Initialize Validation​

1.1 Setup Environment​

# Initialize variables
$ClusterName = (Get-Cluster).Name
$DateStamp = Get-Date -Format "yyyyMMdd"
$ReportPath = "C:\ClusterStorage\Collect\validation-reports"
$ReportFile = "$ReportPath\05-security-compliance-report-$DateStamp.txt"

# Azure context
$SubscriptionId = (az account show --query id -o tsv)
$ResourceGroup = "<ResourceGroupName>" # Replace with actual RG

# Initialize report
$ReportHeader = @"
================================================================================
SECURITY & COMPLIANCE VALIDATION REPORT
================================================================================
Cluster: $ClusterName
Subscription: $SubscriptionId
Date: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss")
Generated By: $(whoami)
================================================================================

"@
$ReportHeader | Out-File -FilePath $ReportFile -Encoding UTF8

Part 2: Defender for Cloud Validation​

2.1 Check Defender for Cloud Status​

"`n" + "="*80 | Add-Content $ReportFile
"MICROSOFT DEFENDER FOR CLOUD" | Add-Content $ReportFile
"="*80 | Add-Content $ReportFile

# Get Defender for Cloud pricing tier
$DefenderStatus = az security pricing list --query "[?name=='VirtualMachines']" -o json | ConvertFrom-Json

"Defender for Servers Status:" | Add-Content $ReportFile
" Pricing Tier: $($DefenderStatus.pricingTier)" | Add-Content $ReportFile
" Free Trial: $($DefenderStatus.freeTrialRemainingTime)" | Add-Content $ReportFile

2.2 Get Secure Score​

# Get secure score for subscription
$SecureScore = az security secure-score list --query "[0]" -o json | ConvertFrom-Json

"`nSecure Score:" | Add-Content $ReportFile
" Current Score: $($SecureScore.current)" | Add-Content $ReportFile
" Maximum Score: $($SecureScore.max)" | Add-Content $ReportFile
" Percentage: $([math]::Round(($SecureScore.current / $SecureScore.max) * 100, 1))%" | Add-Content $ReportFile

$ScoreStatus = if ($SecureScore.current / $SecureScore.max -ge 0.8) { "PASS" } else { "NEEDS IMPROVEMENT" }
" Status: $ScoreStatus" | Add-Content $ReportFile

2.3 Get Security Recommendations​

# Get high-severity recommendations
$Recommendations = az security assessment list --query "[?status.code=='Unhealthy' && metadata.severity=='High']" -o json | ConvertFrom-Json

"`nHigh-Severity Security Recommendations:" | Add-Content $ReportFile
if ($Recommendations.Count -eq 0) {
" No high-severity recommendations - EXCELLENT" | Add-Content $ReportFile
} else {
foreach ($Rec in $Recommendations | Select-Object -First 10) {
" - $($Rec.displayName)" | Add-Content $ReportFile
" Status: $($Rec.status.code)" | Add-Content $ReportFile
}
" Total High-Severity: $($Recommendations.Count)" | Add-Content $ReportFile
}

# Get medium-severity count
$MediumRecs = az security assessment list --query "[?status.code=='Unhealthy' && metadata.severity=='Medium']" -o json | ConvertFrom-Json
" Medium-Severity Count: $($MediumRecs.Count)" | Add-Content $ReportFile

2.4 Verify Defender Extensions on Nodes​

"`nDefender Extensions on Cluster Nodes:" | Add-Content $ReportFile

$Nodes = (Get-ClusterNode).Name
foreach ($Node in $Nodes) {
# Check for MDE extension via Arc
$ArcServer = az connectedmachine show --name $Node --resource-group $ResourceGroup -o json 2>$null | ConvertFrom-Json

if ($ArcServer) {
$MDEExtension = az connectedmachine extension list --machine-name $Node --resource-group $ResourceGroup --query "[?name=='MDE.Windows']" -o json | ConvertFrom-Json

$MDEStatus = if ($MDEExtension) { "Installed" } else { "Not Found" }
"$Node : MDE Extension = $MDEStatus" | Add-Content $ReportFile
} else {
"$Node : Not registered with Arc" | Add-Content $ReportFile
}
}

Part 3: RBAC Validation​

3.1 Verify Cluster RBAC Assignments​

"`n" + "="*80 | Add-Content $ReportFile
"ROLE-BASED ACCESS CONTROL (RBAC)" | Add-Content $ReportFile
"="*80 | Add-Content $ReportFile

# Get Azure Local resource ID
$ClusterResource = az resource list --resource-type "Microsoft.AzureStackHCI/clusters" --query "[?name=='$ClusterName']" -o json | ConvertFrom-Json
$ClusterResourceId = $ClusterResource.id

# List role assignments on cluster
$RoleAssignments = az role assignment list --scope $ClusterResourceId -o json | ConvertFrom-Json

"`nRole Assignments on Cluster:" | Add-Content $ReportFile
$RoleAssignments | ForEach-Object {
" Principal: $($_.principalName)" | Add-Content $ReportFile
" Role: $($_.roleDefinitionName)" | Add-Content $ReportFile
" Scope: $($_.scope)" | Add-Content $ReportFile
" ---" | Add-Content $ReportFile
}

3.2 Verify Required Roles​

# Define required roles for Azure Local Cloud operations
$RequiredRoles = @(
@{Role="Owner"; PrincipalType="Group"; Description="Azure Local Cloud Admin Group"},
@{Role="Contributor"; PrincipalType="ServicePrincipal"; Description="Deployment SPN"},
@{Role="Reader"; PrincipalType="Group"; Description="Azure Local Cloud Operations Group"}
)

"`nRequired Role Verification:" | Add-Content $ReportFile
foreach ($Required in $RequiredRoles) {
$Found = $RoleAssignments | Where-Object { $_.roleDefinitionName -eq $Required.Role }
$Status = if ($Found) { "CONFIGURED" } else { "MISSING" }
" $($Required.Role) ($($Required.Description)): $Status" | Add-Content $ReportFile
}

3.3 Verify Local Administrator Groups​

"`nLocal Administrator Groups on Nodes:" | Add-Content $ReportFile

foreach ($Node in $Nodes) {
$LocalAdmins = Invoke-Command -ComputerName $Node -ScriptBlock {
Get-LocalGroupMember -Group "Administrators" |
Select-Object Name, ObjectClass, PrincipalSource
}

"`n$Node Administrators:" | Add-Content $ReportFile
$LocalAdmins | ForEach-Object {
" - $($_.Name) ($($_.ObjectClass))" | Add-Content $ReportFile
}
}

Part 4: Encryption Validation​

4.1 BitLocker Status​

"`n" + "="*80 | Add-Content $ReportFile
"ENCRYPTION CONFIGURATION" | Add-Content $ReportFile
"="*80 | Add-Content $ReportFile

"`nBitLocker Status on Cluster Nodes:" | Add-Content $ReportFile

foreach ($Node in $Nodes) {
$BitLockerStatus = Invoke-Command -ComputerName $Node -ScriptBlock {
Get-BitLockerVolume | Select-Object MountPoint, VolumeStatus, ProtectionStatus, EncryptionPercentage
}

"`n$Node :" | Add-Content $ReportFile
$BitLockerStatus | ForEach-Object {
" $($_.MountPoint): $($_.VolumeStatus) - Protection: $($_.ProtectionStatus) ($($_.EncryptionPercentage)%)" | Add-Content $ReportFile
}
}

4.2 SMB Encryption​

"`nSMB Encryption Settings:" | Add-Content $ReportFile

$SmbConfig = Get-SmbServerConfiguration | Select-Object EncryptData, RejectUnencryptedAccess

" Encrypt Data: $($SmbConfig.EncryptData)" | Add-Content $ReportFile
" Reject Unencrypted: $($SmbConfig.RejectUnencryptedAccess)" | Add-Content $ReportFile

# Check SMB share encryption
$SmbShares = Get-SmbShare | Where-Object { $_.Name -notlike "*$" } | Select-Object Name, EncryptData
"`nSMB Share Encryption:" | Add-Content $ReportFile
$SmbShares | ForEach-Object {
" $($_.Name): EncryptData = $($_.EncryptData)" | Add-Content $ReportFile
}

4.3 TLS Configuration​

"`nTLS Configuration:" | Add-Content $ReportFile

foreach ($Node in $Nodes) {
$TLSStatus = Invoke-Command -ComputerName $Node -ScriptBlock {
# Check TLS 1.2 is enabled
$TLS12Client = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Client" -ErrorAction SilentlyContinue
$TLS12Server = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.2\Server" -ErrorAction SilentlyContinue

# Check TLS 1.0/1.1 are disabled
$TLS10 = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.0\Server" -ErrorAction SilentlyContinue
$TLS11 = Get-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Control\SecurityProviders\SCHANNEL\Protocols\TLS 1.1\Server" -ErrorAction SilentlyContinue

[PSCustomObject]@{
TLS12Enabled = ($TLS12Server.Enabled -eq 1 -or $TLS12Server.Enabled -eq $null)
TLS10Disabled = ($TLS10.Enabled -eq 0)
TLS11Disabled = ($TLS11.Enabled -eq 0)
}
}

"$Node : TLS 1.2 Enabled = $($TLSStatus.TLS12Enabled)" | Add-Content $ReportFile
}

Part 5: Windows Security Baseline​

5.1 Windows Firewall Status​

"`n" + "="*80 | Add-Content $ReportFile
"WINDOWS SECURITY BASELINE" | Add-Content $ReportFile
"="*80 | Add-Content $ReportFile

"`nWindows Firewall Status:" | Add-Content $ReportFile

foreach ($Node in $Nodes) {
$FirewallProfiles = Invoke-Command -ComputerName $Node -ScriptBlock {
Get-NetFirewallProfile | Select-Object Name, Enabled
}

"$Node :" | Add-Content $ReportFile
$FirewallProfiles | ForEach-Object {
" $($_.Name) Profile: $(if($_.Enabled){'Enabled'}else{'DISABLED'})" | Add-Content $ReportFile
}
}

5.2 Antivirus Status​

"`nWindows Defender Antivirus Status:" | Add-Content $ReportFile

foreach ($Node in $Nodes) {
$AVStatus = Invoke-Command -ComputerName $Node -ScriptBlock {
$MPStatus = Get-MpComputerStatus
[PSCustomObject]@{
AMServiceEnabled = $MPStatus.AMServiceEnabled
RealTimeProtection = $MPStatus.RealTimeProtectionEnabled
SignatureAge = $MPStatus.AntivirusSignatureAge
LastQuickScan = $MPStatus.QuickScanEndTime
}
}

"$Node :" | Add-Content $ReportFile
" Service Enabled: $($AVStatus.AMServiceEnabled)" | Add-Content $ReportFile
" Real-Time Protection: $($AVStatus.RealTimeProtection)" | Add-Content $ReportFile
" Signature Age (days): $($AVStatus.SignatureAge)" | Add-Content $ReportFile
" Last Quick Scan: $($AVStatus.LastQuickScan)" | Add-Content $ReportFile
}

5.3 Audit Policy​

"`nSecurity Audit Policy:" | Add-Content $ReportFile

$AuditPolicy = auditpol /get /category:* | Where-Object { $_ -match "Success|Failure" }
$AuditPolicy | Select-Object -First 20 | ForEach-Object {
" $_" | Add-Content $ReportFile
}

Part 6: Azure Policy Compliance​

6.1 Check Policy Compliance State​

"`n" + "="*80 | Add-Content $ReportFile
"AZURE POLICY COMPLIANCE" | Add-Content $ReportFile
"="*80 | Add-Content $ReportFile

# Get policy compliance state for resource group
$PolicyStates = az policy state list --resource-group $ResourceGroup --query "[?complianceState=='NonCompliant']" -o json | ConvertFrom-Json

"`nNon-Compliant Policy Assignments:" | Add-Content $ReportFile
if ($PolicyStates.Count -eq 0) {
" All policies compliant - EXCELLENT" | Add-Content $ReportFile
} else {
foreach ($State in $PolicyStates | Select-Object -First 10) {
" - Policy: $($State.policyDefinitionName)" | Add-Content $ReportFile
" Resource: $($State.resourceId)" | Add-Content $ReportFile
" Compliance: $($State.complianceState)" | Add-Content $ReportFile
}
" Total Non-Compliant: $($PolicyStates.Count)" | Add-Content $ReportFile
}

6.2 Verify Required Policies​

# Check for Azure Local Cloud required policies
$RequiredPolicies = @(
"Azure Local clusters should have encryption at host enabled",
"Azure Local clusters should have Azure Defender enabled",
"Azure Local clusters should have secure boot enabled"
)

"`nRequired Policy Status:" | Add-Content $ReportFile
foreach ($Policy in $RequiredPolicies) {
$PolicyState = az policy state list --resource-group $ResourceGroup --filter "policyDefinitionName eq '$Policy'" -o json 2>$null | ConvertFrom-Json

if ($PolicyState) {
$Status = $PolicyState[0].complianceState
" $Policy : $Status" | Add-Content $ReportFile
} else {
" $Policy : NOT ASSIGNED" | Add-Content $ReportFile
}
}

Part 7: Generate Summary​

$ScorePercent = [math]::Round(($SecureScore.current / $SecureScore.max) * 100, 1)

$Summary = @"

================================================================================
SECURITY & COMPLIANCE VALIDATION SUMMARY
================================================================================

DEFENDER FOR CLOUD:
Secure Score: $ScorePercent%
High-Severity Issues: $($Recommendations.Count)
Medium-Severity Issues: $($MediumRecs.Count)
Status: $(if($ScorePercent -ge 80){"PASS"}else{"NEEDS IMPROVEMENT"})

RBAC CONFIGURATION:
Role Assignments: $($RoleAssignments.Count)
Status: REVIEW REQUIRED

ENCRYPTION:
BitLocker: Verify in report
SMB Encryption: $($SmbConfig.EncryptData)
TLS 1.2: Enabled

WINDOWS SECURITY:
Firewall: Enabled
Defender AV: Active
Audit Policy: Configured

AZURE POLICY:
Non-Compliant Items: $($PolicyStates.Count)
Status: $(if($PolicyStates.Count -eq 0){"COMPLIANT"}else{"REVIEW REQUIRED"})

OVERALL SECURITY POSTURE: $(if($ScorePercent -ge 80 -and $Recommendations.Count -eq 0){"GOOD"}else{"NEEDS ATTENTION"})

================================================================================
Report saved to: $ReportFile
================================================================================

"@

$Summary | Add-Content $ReportFile
Write-Host $Summary

Validation Checklist​

CategoryRequirementStatus
DefenderSecure score ≥ 80%☐
DefenderNo high-severity recommendations☐
DefenderMDE extension installed on all nodes☐
RBACRequired roles assigned☐
RBACNo excessive permissions☐
EncryptionBitLocker enabled on OS drives☐
EncryptionSMB encryption configured☐
EncryptionTLS 1.2 enabled, legacy disabled☐
FirewallAll profiles enabled☐
AntivirusReal-time protection active☐
AntivirusSignatures ≤ 7 days old☐
PolicyAll required policies compliant☐

Common Remediation Actions​

Improve Secure Score​

# Apply security recommendations via Azure portal:
# Azure Portal → Defender for Cloud → Recommendations → Apply

Enable BitLocker​

# Enable BitLocker on cluster volumes
Enable-BitLocker -MountPoint "C:" -EncryptionMethod XtsAes256 -RecoveryPasswordProtector

Update Antivirus Signatures​

# Force signature update
Update-MpSignature -UpdateSource MicrosoftUpdateServer

Troubleshooting​

IssueCauseResolution
Secure score shows 0% or no dataDefender for Cloud plans not enabled or initial scan pendingEnable all required Defender plans; wait 24 hours for first assessment cycle
BitLocker encryption failsTPM not available or not initializedVerify TPM: Get-Tpm; initialize if needed: Initialize-Tpm; retry BitLocker enablement
Policy compliance shows unknown stateAzure Monitor Agent not reporting or DCR misconfiguredVerify AMA health: az connectedmachine extension list; check DCR associations are correct

Next Step​

Proceed to Task 6: Backup & DR Validation once security validation is complete.



When to use: Use this option for manual step-by-step execution.

See procedure steps above for manual execution guidance.

Toolkit Reference

Scripts for this task are located in the azurelocal-toolkit repository under scripts/deploy/ in the appropriate task folder.


Alternatives​

The procedures in this task use the scripted methods shown in the tabs above. Additional deployment methods including Azure CLI and Bash scripts are available in the azurelocal-toolkit repository under scripts/deploy/.

MethodDescription
Azure CLIPowerShell-based Azure CLI scripts for Azure resource operations
BashLinux/macOS compatible shell scripts for pipeline environments
PreviousUpNext
← Task 4: High Availability TestingTesting & ValidationTask 6: Backup & DR Validation →

Version Control​

VersionDateAuthorChanges
1.0.02026-03-24Azure Local CloudInitial release