modules/NPS_RADIUS/NPS_RADIUS.psm1
|
<#
NPS_RADIUS.psm1 - NPS / RADIUS / VPN / MFA discovery (read-only). Role-gated. NEVER collects RADIUS shared secrets. #> function Get-DiscoveryModuleMetadata { [pscustomobject]@{ ModuleName='NPS_RADIUS'; DisplayName='NPS / RADIUS / VPN / MFA'; Category='Security'; Version='1.0.0' DefaultInFast=$true; DefaultInDeep=$true; RequiresAdmin=$true; RequiresDomainContext=$false RequiresRole='NPAS'; EstimatedImpact='Low'; CanRunAsSystem=$true ProducesDatasets=@('NpsRadiusDiscovery') ProducesRisks=$true; ProducesFollowUpQuestions=$true; SupportsDeepMode=$true; SupportsComplianceLens=$true } } function Test-NpsPresent { if (Get-Service -Name 'IAS' -ErrorAction SilentlyContinue) { return $true } if (Test-RegistryPathSafe 'HKLM:\SYSTEM\CurrentControlSet\Services\IAS') { return $true } return $false } function Test-DiscoveryPrerequisites { param([object]$Context) $rolePresent = $false if ($Context.DataSets.Contains('RolesFeatures')) { $rolePresent = (@($Context.DataSets['RolesFeatures'].Rows | Where-Object { $_.Name -match '(?i)NPAS|Policy-Server' }).Count -gt 0) } if ($rolePresent -or (Test-NpsPresent)) { return [pscustomobject]@{ ModuleName='NPS_RADIUS'; CanRun=$true; Status='Ready'; Reason=''; Limitations=@() } } [pscustomobject]@{ ModuleName='NPS_RADIUS'; CanRun=$false; Status='NotApplicable'; Reason='NPS/RADIUS role not detected.'; Limitations=@() } } function Invoke-DiscoveryCollection { param([object]$Context) $rows=[System.Collections.Generic.List[object]]::new() $rows.Add([pscustomobject]@{ Item='NPS'; Type='Role'; Detail='NPS/RADIUS indicators present on this server.' }) # Shared secrets are intentionally NOT collected. Add-Unknown -Context $Context -Unknown 'RADIUS shared secrets are intentionally not collected.' -WhyItMatters 'Secrets must be re-coordinated with network devices during migration.' -Module 'NPS_RADIUS' -RecommendedValidationQuestion 'Who holds the RADIUS shared secrets for each client device?' | Out-Null # Config summary via netsh nps show config (READ-ONLY show) try { $r = Invoke-CommandLineSafe -FilePath 'netsh.exe' -Arguments @('nps','show','config') -TimeoutSeconds 45 if ($r.Succeeded -and $r.StdOut) { # Real output is "<Section> configuration:" headers, each object opened by "Name = X" # followed by "Key = value" lines. Only allow-listed keys are read, so the # "Shared secret" line is never captured. $type = ''; $cur = $null foreach ($ln in ($r.StdOut -split "`r?`n")) { if ($ln -match '(?i)^\s*(Client|Connection request policy|Network policy|Remote RADIUS server group)\s+configuration\s*:') { $type = switch -Regex ($Matches[1]) { '(?i)^client' { 'RadiusClient' } '(?i)^connection' { 'ConnectionRequestPolicy' } '(?i)^network' { 'NetworkPolicy' } default { 'RemoteRadiusServerGroup' } } $cur = $null; continue } if (-not $type) { continue } if ($ln -match '^\s*Name\s*=\s*(.+?)\s*$') { $cur = [pscustomobject]@{ Item=$Matches[1]; Type=$type; Detail='' }; $rows.Add($cur); continue } if ($cur -and $ln -match '^\s*(Address|State|Processing order|Vendor)\s*=\s*(.+?)\s*$') { $kv = '{0}={1}' -f $Matches[1], $Matches[2] $cur.Detail = if ($cur.Detail) { $cur.Detail + '; ' + $kv } else { $kv } } } } else { Add-Limitation -Context $Context -Module 'NPS_RADIUS' -Message 'netsh nps show config unavailable; NPS present but detail limited.' | Out-Null } } catch { Add-Limitation -Context $Context -Module 'NPS_RADIUS' -Message 'NPS config query failed.' -Reason $_.Exception.Message | Out-Null } # Azure MFA NPS extension indicator try { if (Test-RegistryPathSafe 'HKLM:\SOFTWARE\Microsoft\AzureMfa') { $rows.Add([pscustomobject]@{ Item='Azure MFA NPS Extension'; Type='MFA'; Detail='Registry indicator present' }) } } catch { } return ,@{ NpsRadiusDiscovery=@($rows) } } function ConvertTo-DiscoveryDatasets { param([object]$Context, $RawData) $rows = @(if ($RawData -and $RawData.NpsRadiusDiscovery) { @($RawData.NpsRadiusDiscovery) } else { @() }) Add-DataSet -Context $Context -Name 'NpsRadiusDiscovery' -Description 'NPS/RADIUS indicators (no shared secrets).' -Rows $rows -Visibility 'Internal' -SourceModule 'NPS_RADIUS' | Out-Null } function Get-DiscoveryFollowUpQuestions { param([object]$Context) try { if ($Context.DataSets.Contains('NpsRadiusDiscovery') -and @($Context.DataSets['NpsRadiusDiscovery'].Rows).Count -gt 0) { Add-FollowUpQuestion -Context $Context -Category 'Security / compliance' -Module 'NPS_RADIUS' -Audience 'Both' -Question 'Which devices (VPN/Wi-Fi/switches) authenticate through this NPS/RADIUS server, and who manages the shared secrets and certificates?' | Out-Null } } catch { } } Export-ModuleMember -Function 'Get-DiscoveryModuleMetadata','Test-DiscoveryPrerequisites','Invoke-DiscoveryCollection','ConvertTo-DiscoveryDatasets','Get-DiscoveryFollowUpQuestions','Test-NpsPresent' |