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.
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 Cloudnology Team | Initial release |