Task 05: Security & Compliance Validation
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 Path | Type | Description |
|---|---|---|
azure.subscription.id | String | Azure subscription ID for Defender/policy queries |
azure.resource_group.name | String | Resource group containing Azure Local cluster resources |
compute.nodes[].name | String | Node hostnames for per-node security checks |
operations.policy.azure_policy_azurelocal_encryption | Boolean | Required policy: encryption at host |
operations.policy.azure_policy_azurelocal_secured_core | Boolean | Required policy: secured core |
security.keyvault.name | String | Key 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
| Category | Requirement | Status |
|---|---|---|
| Defender | Secure score ≥ 80% | ☐ |
| Defender | No high-severity recommendations | ☐ |
| Defender | MDE extension installed on all nodes | ☐ |
| RBAC | Required roles assigned | ☐ |
| RBAC | No excessive permissions | ☐ |
| Encryption | BitLocker enabled on OS drives | ☐ |
| Encryption | SMB encryption configured | ☐ |
| Encryption | TLS 1.2 enabled, legacy disabled | ☐ |
| Firewall | All profiles enabled | ☐ |
| Antivirus | Real-time protection active | ☐ |
| Antivirus | Signatures ≤ 7 days old | ☐ |
| Policy | All 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
| Issue | Cause | Resolution |
|---|---|---|
| Secure score shows 0% or no data | Defender for Cloud plans not enabled or initial scan pending | Enable all required Defender plans; wait 24 hours for first assessment cycle |
| BitLocker encryption fails | TPM not available or not initialized | Verify TPM: Get-Tpm; initialize if needed: Initialize-Tpm; retry BitLocker enablement |
| Policy compliance shows unknown state | Azure Monitor Agent not reporting or DCR misconfigured | Verify 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.
- Manual
- Orchestrated Script
- Standalone Script
When to use: Use this option for manual step-by-step execution.
See procedure steps above for manual execution guidance.
When to use: Use this option when deploying across multiple nodes from a management server using ariables.yml.
Script: See azurelocal-toolkit for the orchestrated script for this task.
Orchestrated script content references the toolkit repository.
When to use: Use this option for a self-contained deployment without a shared configuration file.
Script: See azurelocal-toolkit for the standalone script for this task.
Standalone script content references the toolkit repository.
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/.
| Method | Description |
|---|---|
| Azure CLI | PowerShell-based Azure CLI scripts for Azure resource operations |
| Bash | Linux/macOS compatible shell scripts for pipeline environments |
Navigation
| Previous | Up | Next |
|---|---|---|
| ← Task 4: High Availability Testing | Testing & Validation | Task 6: Backup & DR Validation → |
Version Control
| Version | Date | Author | Changes |
|---|---|---|---|
| 1.0.0 | 2026-03-24 | Azure Local Cloud | Initial release |