Private/aks/aks-functions.ps1
# Main Script: AKS Best Practices Checklist function Invoke-AKSBestPractices { param ( [string]$SubscriptionId, [string]$ResourceGroup, [string]$ClusterName, [switch]$FailedOnly, [switch]$html ) # Authenticate with Azure and fetch cluster details function Authenticate { Write-Host "🤖 Authenticating with Azure..." -ForegroundColor Cyan az login --use-device-code --output none az account set --subscription $SubscriptionId if ($?) { Write-Host "🤖 Authentication successful." -ForegroundColor Green } else { Write-Host "🤖 Authentication failed. Exiting..." -ForegroundColor Red exit 1 } } function Validate-Context { param ( [string]$ResourceGroup, [string]$ClusterName ) # Get the current Kubernetes context $currentContext = kubectl config current-context # Get the AKS cluster details and extract the correct context $aksContext = az aks show --resource-group $ResourceGroup --name $ClusterName --query "name" -o tsv # If report mode is enabled, log the context check but don’t print anything to CLI 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: The current Kubernetes context ('$currentContext') does NOT match the expected AKS cluster ('$aksContext')." -ForegroundColor Red Write-ToReport " - Cluster validation skipped due to mismatched context." return $false # Skip cluster validation in report mode but continue execution } } # Speech bubble message for CLI output (only if not in report mode) $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. Proceeding with the scan.") Write-SpeechBubble -msg $msg -color "Green" -icon "🤖" return $true } else { $msg += @( "⚠️ WARNING: The current Kubernetes context does NOT match the AKS cluster!", "", "❌ Running commands in the wrong context may impact the wrong cluster!", "", "💡 To set the correct context, run the following command:", " kubectl config use-context $aksContext", "", "Then re-run this script." ) Write-SpeechBubble -msg $msg -color "Yellow" -icon "🤖" -lastColor "Red" $confirmation = Read-Host "🤖 Do you want to continue anyway? (yes/no)" Clear-Host if ($confirmation -match "^(y|yes)$") { $msg = @("⚠️ Proceeding with mismatched context...") Write-SpeechBubble -msg $msg -color "Yellow" -icon "🤖" return $true } else { $msg = @( "❌ Exiting to prevent incorrect cluster impact.", "", "💡 Run the following command to switch to the correct AKS context:", " kubectl config use-context $aksContext", "", "Once the correct context is set, you can rerun this script." ) Write-SpeechBubble -msg $msg -color "Red" -icon "🤖" exit 1 } } } function Get-AKSClusterInfo { param ( [string]$SubscriptionId, [string]$ResourceGroup, [string]$ClusterName ) Write-Host -no "`n🤖 Fetching AKS cluster details..." -ForegroundColor Cyan $clusterInfo = az aks show --resource-group $ResourceGroup --name $ClusterName --output json | ConvertFrom-Json if (-not $clusterInfo) { Write-Host "🤖 Error: Failed to fetch cluster details. Exiting..." -ForegroundColor Red exit 1 } # Fetch Kubernetes constraint data in a single command Write-Host "🤖 Fetching Kubernetes constraint data..." -ForegroundColor Cyan $kubeData = @{ Constraints = kubectl get constraints -A -o json | ConvertFrom-Json } # Attach KubeData to clusterInfo $clusterInfo | Add-Member -MemberType NoteProperty -Name "KubeData" -Value $kubeData return $clusterInfo } # Combine all checks from variables ending with 'Checks' $checks = @() Get-Variable -Name "*Checks" | ForEach-Object { $checks += $_.Value } # Remove duplicate checks based on their ID $checks = $checks | Group-Object -Property ID | ForEach-Object { $_.Group[0] } function Run-Checks { param ( $clusterInfo ) Write-Host "🤖 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) { Clear-Host } foreach ($check in $checks) { try { # Write-Host "Evaluating Check: $($check.Name)" # Write-Host "Expression: $($check.Value)" # If the check is stored as a ScriptBlock, execute it if ($check.Value -is [scriptblock]) { $value = & $check.Value } else { # Fix: Ensure we only evaluate valid PowerShell expressions if ($check.Value -match "^(True|False|[0-9]+)$") { $value = [bool]([System.Convert]::ChangeType($check.Value, [boolean])) } else { $value = Invoke-Expression ($check.Value -replace '\$clusterInfo', '$clusterInfo') } } # Write-Host "Evaluated Value: $value" # Write-Host "Expected Value: $($check.Expected)" if ($value -eq $check.Expected) { $categories[$check.Category] += [PSCustomObject]@{ ID = $check.ID; Check = $check.Name; Severity = $check.Severity; Category = $check.Category; Status = "✅ PASS"; Recommendation = "$($check.Name) is enabled."; URL = $check.URL } } else { $categories[$check.Category] += [PSCustomObject]@{ ID = $check.ID; Check = $check.Name; Severity = $check.Severity; Category = $check.Category; Status = "❌ FAIL"; Recommendation = $check.FailMessage URL = $check.URL } } # Log to text report if ($Global:MakeReport) { Write-ToReport "[$($check.Category)] $($check.Name) - Status: $($categories[$check.Category][-1].Status)" Write-ToReport " 🔹 Severity: $($check.Severity)" Write-ToReport " 🔹 Recommendation: $($categories[$check.Category][-1].Recommendation)" Write-ToReport " 🔹 More Info: $($check.URL)`n" } } catch { Write-Host "Error processing check: $($check.Name). Skipping... $_" -ForegroundColor Red } } return $categories } function Display-Results { param ( [hashtable]$categories, [switch]$FailedOnly, [switch]$Html ) $passCount = 0 $failCount = 0 $reportData = @() # ✅ Initialize empty array to prevent null reference foreach ($category in $categories.Keys) { # Filter checks if -FailedOnly is specified $checks = $categories[$category] if ($FailedOnly) { $checks = $checks | Where-Object { $_.Status -eq "❌ FAIL" } } if ($checks.Count -gt 0 -and -not $Html -and -not $Global:MakeReport) { Write-Host "`n=== $category === " -ForegroundColor Cyan $checks | Format-Table ID, Check, Severity, Category, Status, Recommendation, URL -AutoSize # ✅ Show "Press any key to continue..." message Write-Host "`nPress any key to continue..." -ForegroundColor Magenta -NoNewline # ✅ Wait for keypress $null = $Host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") # ✅ Move cursor up one line and clear it if ($Host.Name -match "ConsoleHost") { # Windows Terminal / Standard PowerShell console [Console]::SetCursorPosition(0, [Console]::CursorTop - 1) Write-Host (" " * 50) -NoNewline [Console]::SetCursorPosition(0, [Console]::CursorTop) } else { # ANSI escape codes for clearing a line (Linux/macOS) Write-Host "`e[1A`e[2K" -NoNewline } } else { # ✅ Append check results to $reportData $reportData += $checks | Select-Object ID, Check, Severity, Category, Status, Recommendation, URL } # Count passed and failed checks $passCount += ($categories[$category] | Where-Object { $_.Status -eq "✅ PASS" }).Count $failCount += ($categories[$category] | Where-Object { $_.Status -eq "❌ FAIL" }).Count } # **Summary Calculation** $total = $passCount + $failCount $score = if ($total -eq 0) { 0 } else { [math]::Round(($passCount / $total) * 100, 2) } # **Fix: Pick only the first rating letter** $rating = @(switch ($score) { { $_ -ge 90 } { "A" } { $_ -ge 80 } { "B" } { $_ -ge 70 } { "C" } { $_ -ge 60 } { "D" } default { "F" } })[0] # Picks only the FIRST rating letter # **Assign Color for Rating** $ratingColor = switch ($rating) { "A" { "Green" } "B" { "Yellow" } "C" { "DarkYellow" } "D" { "Red" } "F" { "DarkRed" } default { "Gray" } } # **CLI Output for Summary** if (-not $Html -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 # Rating is colored correctly } if ($global:MakeReport) { Write-ToReport "`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-ToReport $header Write-ToReport $separator Write-ToReport "$row " -NoNewline Write-ToReport "$rating" } # ✅ **HTML Output: Return Key Values** if ($Html) { $htmlTable = if ($reportData.Count -gt 0) { $sortedReportData = $reportData | Sort-Object @{Expression = { $_.Status -eq "❌ FAIL" } ; Descending = $true }, Category $sortedReportData | ConvertTo-Html -Fragment -Property ID, Check, Severity, Category, Status, Recommendation, URL | Out-String } 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 # ✅ Execute the checks if ($Global:MakeReport) { Write-Host "`n🤖 Starting AKS Best Practices Check...`n" -ForegroundColor Green } #Authenticate Validate-Context -ResourceGroup $ResourceGroup -ClusterName $ClusterName $clusterInfo = Get-AKSClusterInfo -SubscriptionId $SubscriptionId -ResourceGroup $ResourceGroup -ClusterName $ClusterName $checkResults = Run-Checks -clusterInfo $clusterInfo if ($html) { # Capture HTML output and return it $htmlContent = Display-Results -categories $checkResults -FailedOnly:$FailedOnly -Html return $htmlContent } else { # Display results in console Display-Results -categories $checkResults -FailedOnly:$FailedOnly # ✅ Keep the script open until the user presses Enter If (-not $Global:MakeReport) { Write-Host "`nPress Enter to return to the menu..." -ForegroundColor Yellow Read-Host } } # ✅ Close the report when done if ($Global:MakeReport) { Write-Host "`n✅ AKS Best Practices Check Completed.`n" -ForegroundColor Green } } |