Private/aks/aks-functions.ps1
function Invoke-AKSBestPractices { param ( [string]$SubscriptionId, [string]$ResourceGroup, [string]$ClusterName, [switch]$FailedOnly, [switch]$Html, [switch]$json, [object]$KubeData ) function Validate-Context { param ($ResourceGroup, $ClusterName) if ($KubeData) { return $true | Out-Null } $currentContext = kubectl config current-context try { $aksContext = az aks show --resource-group $ResourceGroup --name $ClusterName --query "name" -o tsv --only-show-errors 2>&1 if ($LASTEXITCODE -ne 0 -or -not $aksContext) { throw "Failed to retrieve AKS context. Please verify the resource group and cluster name." } } catch { Write-Error "❌ Error fetching AKS context: $_" throw "Critical error: Unable to continue without AKS context." } if ($Global:MakeReport) { Write-Host "🔄 Checking Kubernetes context..." -ForegroundColor Cyan Write-Host " - Current context: '$currentContext'" -ForegroundColor Yellow Write-Host " - Expected AKS cluster: '$aksContext'" -ForegroundColor Yellow if ($currentContext -eq $aksContext) { Write-Host "✅ Kubernetes context matches. Proceeding with the scan." -ForegroundColor Green return $true } else { Write-Host "⚠️ WARNING: Context mismatch." -ForegroundColor Red Write-ToReport " - Skipping validation due to mismatched context." return $false } } $msg = @( "🔄 Checking your Kubernetes context...", "", " - You're currently using context: '$currentContext'.", " - The expected AKS cluster context is: '$aksContext'.", "" ) if ($currentContext -eq $aksContext) { $msg += @("✅ The context is correct.") Write-SpeechBubble -msg $msg -color "Green" -icon "🤖" return $true } else { $msg += @( "⚠️ WARNING: Context mismatch!", "", "❌ Commands may target the wrong cluster.", "", "💡 Run: kubectl config use-context $aksContext" ) Write-SpeechBubble -msg $msg -color "Yellow" -icon "🤖" -lastColor "Red" if ($yes) { Write-SpeechBubble -msg @("🤖 Skipping context confirmation.") -color "Red" -icon "🤖" return $true } Write-SpeechBubble -msg @("🤖 Please confirm if you want to continue.") -color "Yellow" -icon "🤖" $confirmation = Read-Host "🤖 Continue anyway? (yes/no)" Clear-Host if ($confirmation -match "^(y|yes)$") { Write-SpeechBubble -msg @("⚠️ Proceeding despite mismatch...") -color "Yellow" -icon "🤖" return $true } else { Write-SpeechBubble -msg @("❌ Exiting to prevent incorrect execution.") -color "Red" -icon "🤖" exit 1 } } } function Get-AKSClusterInfo { param ( [string]$SubscriptionId, [string]$ResourceGroup, [string]$ClusterName, [object]$KubeData ) Write-Host -NoNewline "`n🤖 Fetching AKS cluster details..." -ForegroundColor Cyan $clusterInfo = $null $constraints = @() try { if ($KubeData -and $KubeData.AksCluster -and $KubeData.Constraints) { $clusterInfo = $KubeData.AksCluster $constraints = $KubeData.Constraints Write-Host "`r🤖 Using cached AKS cluster data. " -ForegroundColor Green } else { # Use Azure CLI to get an access token $accessToken = az account get-access-token --resource https://management.azure.com/ --query accessToken -o tsv if (-not $accessToken) { throw "Access token not retrieved." } $headers = @{ Authorization = "Bearer $accessToken" } $apiVersion = "2025-01-01" $uri = "https://management.azure.com/subscriptions/$SubscriptionId/resourceGroups/$ResourceGroup/providers/Microsoft.ContainerService/managedClusters/${ClusterName}?api-version=${apiVersion}" $clusterInfo = Invoke-RestMethod -Uri $uri -Headers $headers -UseBasicParsing Write-Host "`r🤖 Live cluster data fetched. " -ForegroundColor Green Write-Host -NoNewline "`n🤖 Fetching Kubernetes constraints..." -ForegroundColor Cyan $constraints = kubectl get constraints -A -o json | ConvertFrom-Json | Select-Object -ExpandProperty items Write-Host "`r🤖 Constraints fetched." -ForegroundColor Green } # Attach constraints regardless of source $clusterInfo | Add-Member -MemberType NoteProperty -Name "KubeData" -Value @{ Constraints = $constraints } return $clusterInfo } catch { Write-Host "`r❌ Error retrieving AKS or constraint data: $($_.Exception.Message)" -ForegroundColor Red return $null } } # Collect all checks $checks = @() Get-Variable -Name "*Checks" | ForEach-Object { $checks += $_.Value } $checks = $checks | Group-Object -Property ID | ForEach-Object { $_.Group[0] } function Run-Checks { param ($clusterInfo) if (-not $HtmlReport -and -not $jsonReport -and -not $Global:MakeReport){ Write-Host -NoNewline "`n🤖 Running best practice checks..." -ForegroundColor Cyan } if ($Global:MakeReport) { Write-ToReport "`n[✅ AKS Best Practices Check]`n" } $categories = @{ "Security" = @(); "Networking" = @(); "Resource Management" = @(); "Monitoring & Logging" = @(); "Identity & Access" = @(); "Disaster Recovery" = @(); "Best Practices" = @(); } if (-not $Global:MakeReport -and -not $HtmlReport -and -not $jsonReport) { Clear-Host } foreach ($check in $checks) { try { $value = if ($check.Value -is [scriptblock]) { $vars = [System.Collections.Generic.List[System.Management.Automation.PSVariable]]::new() $vars.Add([System.Management.Automation.PSVariable]::new('clusterInfo', $clusterInfo)) $check.Value.InvokeWithContext($null, $vars) } elseif ($check.Value -match "^(True|False|[0-9]+)$") { [bool]([System.Convert]::ChangeType($check.Value, [boolean])) } else { Invoke-Expression ($check.Value -replace '\$clusterInfo', '$clusterInfo') } $result = if ($value -eq $check.Expected) { "✅ PASS" } else { "❌ FAIL" } if (-not $categories.ContainsKey($check.Category)) { $categories[$check.Category] = @() } $categories[$check.Category] += [PSCustomObject]@{ ID = $check.ID; Check = $check.Name; Severity = $check.Severity; Category = $check.Category; Status = $result; Recommendation = if ($result -eq "✅ PASS") { "$($check.Name) is enabled." } else { $check.FailMessage } URL = $check.URL } if ($Global:MakeReport) { Write-ToReport "[$($check.Category)] $($check.Name) - $result" Write-ToReport " 🔹 Severity: $($check.Severity)" Write-ToReport " 🔹 Recommendation: $($categories[$check.Category][-1].Recommendation)" Write-ToReport " 🔹 Info: $($check.URL)`n" } } catch { Write-Host "Error processing check: $($check.Name). $_" -ForegroundColor Red } } return $categories } function Display-Results { param ( [hashtable]$categories, [switch]$FailedOnly, [switch]$Html, [switch]$json ) $passCount = 0 $failCount = 0 $reportData = @() foreach ($category in $categories.Keys) { $checks = $categories[$category] if ($FailedOnly) { $checks = $checks | Where-Object { $_.Status -eq "❌ FAIL" } } if ($checks.Count -gt 0 -and -not $Html -and -not $jsonReport -and -not $Global:MakeReport) { Write-Host "`n=== $category === " -ForegroundColor Cyan $checks | Format-Table ID, Check, Severity, Category, Status, Recommendation, @{Label="URL";Expression={$_."URL"}} -AutoSize | out-string | write-host Write-Host "`nPress any key to continue..." -ForegroundColor Magenta -NoNewline $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") if ($Host.Name -match "ConsoleHost") { [Console]::SetCursorPosition(0, [Console]::CursorTop - 1) Write-Host (" " * 50) -NoNewline [Console]::SetCursorPosition(0, [Console]::CursorTop) } else { Write-Host "`e[1A`e[2K" -NoNewline } } else { # Format URL as hyperlink for HTML output $reportData += $checks | Select-Object ID, Check, Severity, Category, Status, Recommendation, @{ Name = 'URL'; Expression = { if ($_.URL) { "<a href='$($_.URL)' target='_blank'>Learn More</a>" } else { "" } } } } $passCount += ($categories[$category] | Where-Object { $_.Status -eq "✅ PASS" }).Count $failCount += ($categories[$category] | Where-Object { $_.Status -eq "❌ FAIL" }).Count } $total = $passCount + $failCount $score = if ($total -eq 0) { 0 } else { [math]::Round(($passCount / $total) * 100, 2) } $rating = @(switch ($score) { { $_ -ge 90 } { "A" } { $_ -ge 80 } { "B" } { $_ -ge 70 } { "C" } { $_ -ge 60 } { "D" } default { "F" } })[0] $ratingColor = switch ($rating) { "A" { "Green" } "B" { "Yellow" } "C" { "DarkYellow" } "D" { "Red" } "F" { "DarkRed" } default { "Gray" } } if (-not $Html -and -not $jsonReport -and -not $Global:MakeReport) { Write-Host "`nSummary & Rating: " -ForegroundColor Green $header = "{0,-12} {1,-12} {2,-12} {3,-12} {4,-8}" -f "Passed", "Failed", "Total", "Score (%)", "Rating" $separator = "============================================================" $row = "{0,-12} {1,-12} {2,-12} {3,-12}" -f "✅ $passCount", "❌ $failCount", "$total", "$score" Write-Host $header -ForegroundColor Cyan Write-Host $separator -ForegroundColor Cyan Write-Host "$row " -NoNewline Write-Host "$rating" -ForegroundColor $ratingColor } if ($Global:MakeReport) { Write-ToReport "`nSummary & Rating: " $header = "{0,-12} {1,-12} {2,-12} {3,-12} {4,-8}" -f "Passed", "Failed", "Total", "Score (%)", "Rating" $separator = "============================================================" $row = "{0,-12} {1,-12} {2,-12} {3,-12}" -f "✅ $passCount", "❌ $failCount", "$total", "$score" Write-ToReport $header Write-ToReport $separator Write-ToReport "$row " -NoNewline Write-ToReport "$rating" } if ($Html) { $htmlTable = if ($reportData.Count -gt 0) { $sortedReportData = $reportData | Sort-Object @{Expression = { $_.Status -eq "❌ FAIL" } ; Descending = $true }, Category # Generate HTML table manually to prevent escaping of HTML in the URL column $htmlRows = $sortedReportData | ForEach-Object { $id = $_.ID $check = $_.Check $severity = $_.Severity $category = $_.Category $status = $_.Status $recommendation = $_.Recommendation $url = $_.URL # This is already an HTML anchor tag, do not escape "<tr><td>$id</td><td>$check</td><td>$severity</td><td>$category</td><td>$status</td><td>$recommendation</td><td>$url</td></tr>" } $htmlTableContent = "<table>`n<thead><tr><th>ID</th><th>Check</th><th>Severity</th><th>Category</th><th>Status</th><th>Recommendation</th><th>URL</th></tr></thead>`n<tbody>`n" + ($htmlRows -join "`n") + "`n</tbody>`n</table>" $htmlTableContent } else { "<p><strong>No best practice violations detected.</strong></p>" } return [PSCustomObject]@{ Passed = $passCount Failed = $failCount Total = $total Score = $score Rating = "$rating" Data = $htmlTable } } } # Main Execution Flow if ($Global:MakeReport) { Write-Host -NoNewline "`n🤖 Starting AKS Best Practices Check...`n" -ForegroundColor Cyan } Validate-Context -ResourceGroup $ResourceGroup -ClusterName $ClusterName $clusterInfo = Get-AKSClusterInfo -SubscriptionId $SubscriptionId -ResourceGroup $ResourceGroup -ClusterName $ClusterName -KubeData $KubeData $checkResults = Run-Checks -clusterInfo $clusterInfo if ($Html) { return Display-Results -categories $checkResults -FailedOnly:$FailedOnly -Html } else { Display-Results -categories $checkResults -FailedOnly:$FailedOnly if (-not $Global:MakeReport -and -not $json) { Write-Host "`nPress Enter to return to the menu..." -ForegroundColor Yellow Read-Host } } if ($Global:MakeReport) { Write-Host "``r✅ AKS Best Practices Check Completed." -ForegroundColor Green } } |