| title | Task 05: Security & Compliance Validation |
|---|---|
| sidebar_label | Task 05: Security & Compliance |
| sidebar_position | 5 |
| description | Validate security configuration, Defender for Cloud, RBAC, and compliance posture |
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
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.
- [ ] 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
All validation results are saved to:
\\<ClusterName>\ClusterStorage$\Collect\validation-reports\05-security-compliance-report-YYYYMMDD.txt
# 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"`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# 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# 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"`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
}
}"`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
}# 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
}"`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
}
}"`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
}
}"`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
}"`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
}"`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
}
}"`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
}"`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
}"`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
}# 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
}
}$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| 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 | ☐ |
# Apply security recommendations via Azure portal:
# Azure Portal → Defender for Cloud → Recommendations → Apply# Enable BitLocker on cluster volumes
Enable-BitLocker -MountPoint "C:" -EncryptionMethod XtsAes256 -RecoveryPasswordProtector# Force signature update
Update-MpSignature -UpdateSource MicrosoftUpdateServerProceed to Task 6: Backup & DR Validation once security validation is complete.