Public/Test-GSATenantInternetDnsResolution.ps1
|
# # Function: Test-GSATenantInternetDnsResolution # Validates tenant Internet forwarding DNS hostnames and detects local hosts-file overrides. # function Test-GSATenantInternetDnsResolution { <# .SYNOPSIS Checks tenant Internet forwarding DNS hostnames for local hosts-file override issues. .DESCRIPTION Reads tenantId from the locally cached ForwardingProfile registry JSON and validates: - <tenantId>.internet.client.globalsecureaccess.microsoft.com - <tenantId>.internet-backup.client.globalsecureaccess.microsoft.com For each hostname, validates that normal system resolution returns the expected synthetic `6.6.0.0/16` GSA address and logs the CNAME chain/aliases. It also compares with direct DNS (`Resolve-DnsName -NoHostsFile`) and checks the local hosts file to detect overrides. .PARAMETER RegistryValueName Registry value containing the forwarding profile JSON. Defaults to ForwardingProfile. .PARAMETER LogLevel Optional per-call log verbosity override. #> [CmdletBinding()] param ( [string]$RegistryValueName = "ForwardingProfile", [ValidateSet("ERROR", "WARNING", "INFO", "DEBUG", "SUCCESS")] [string]$LogLevel ) if ($PSBoundParameters.ContainsKey('LogLevel')) { $script:LogLevel = $LogLevel } # Helper function to extract IP addresses from DNS records. function Get-DnsIPAddresses { param ( [object[]]$Records ) if (-Not $Records) { return @() } return @( $Records | Where-Object -FilterScript { $_.PSObject.Properties.Name -contains 'IPAddress' -and -Not [string]::IsNullOrWhiteSpace($_.IPAddress) } | Select-Object -ExpandProperty IPAddress -Unique ) } # Helper function to retrieve lines from the local hosts file that match a given FQDN. function Get-HostsFileLinesForFqdn { param ( [string]$Fqdn ) $hostsPath = Join-Path $env:WINDIR 'System32\drivers\etc\hosts' if (-Not (Test-Path -LiteralPath $hostsPath)) { return @() } $lines = @() try { $lines = @(Get-Content -LiteralPath $hostsPath -ErrorAction Stop) } catch { Write-Log "Could not read hosts file '$hostsPath': $($_.Exception.Message)" "WARNING" return @() } $matchingLines = @() foreach ($line in $lines) { if ([string]::IsNullOrWhiteSpace($line)) { continue } if ($line.TrimStart().StartsWith('#')) { continue } $withoutInlineComment = ($line -replace '\s+#.*$', '').Trim() if ([string]::IsNullOrWhiteSpace($withoutInlineComment)) { continue } $parts = @($withoutInlineComment -split '\s+' | Where-Object -FilterScript { $_ }) if ($parts.Count -lt 2) { continue } $entryHostnames = @($parts[1..($parts.Count - 1)]) if ($entryHostnames -icontains $Fqdn) { $matchingLines += $line.Trim() } } return $matchingLines } Write-Log "Checking tenant Internet forwarding DNS hostnames for local override issues..." "INFO" # Iterate over each tenant FQDN and check for local hosts file overrides. $profileJson = Get-JsonFromRegistry -ValueName $RegistryValueName if (-Not $profileJson -or -Not $profileJson.policy -or [string]::IsNullOrWhiteSpace($profileJson.policy.tenantId)) { Write-Log "TenantId was not found in '$RegistryValueName' registry JSON. Cannot validate tenant Internet DNS hostnames." "ERROR" $script:_testResult = [PSCustomObject]@{ Passed = $false; Warnings = 0 } return } # Extract the tenant ID from the registry JSON. $tenantId = "$($profileJson.policy.tenantId)".Trim() $tenantFqdns = @( "$tenantId.internet.client.globalsecureaccess.microsoft.com", "$tenantId.internet-backup.client.globalsecureaccess.microsoft.com" ) # Define the synthetic IP range used by the GSA for local DNS resolution. $syntheticRangeStart = 101056512 # 6.6.0.0 $syntheticRangeEnd = 101122047 # 6.6.255.255 $allPassed = $true # Iterate over each tenant FQDN and perform DNS resolution checks. foreach ($tenantFqdn in $tenantFqdns) { Write-Log "Validating tenant hostname '$tenantFqdn'..." "INFO" $realRecords = $null try { $realRecords = @(Resolve-DnsName -Name $tenantFqdn -NoHostsFile -ErrorAction Stop) } catch { Write-Log "Direct DNS lookup (NoHostsFile) failed for '$tenantFqdn': $($_.Exception.Message)" "ERROR" $allPassed = $false continue } # Compare the real DNS records with the system DNS records to detect any discrepancies. $systemRecords = $null try { $systemRecords = @(Resolve-DnsName -Name $tenantFqdn -ErrorAction Stop) } catch { Write-Log "System DNS lookup failed for '$tenantFqdn': $($_.Exception.Message)" "ERROR" $allPassed = $false continue } # Extract the IP addresses from the real and system DNS records for comparison. $realIPs = @(Get-DnsIPAddresses -Records $realRecords) $systemIPs = @(Get-DnsIPAddresses -Records $systemRecords) if ($realIPs.Count -eq 0) { Write-Log "Direct DNS lookup for '$tenantFqdn' returned no A/AAAA records." "ERROR" $allPassed = $false continue } # Log the resolved IP addresses for both direct and system DNS lookups. Write-Log "Direct DNS (NoHostsFile): $($realIPs -join ', ')" "INFO" if ($systemIPs.Count -gt 0) { Write-Log "System DNS: $($systemIPs -join ', ')" "INFO" } else { Write-Log "System DNS for '$tenantFqdn' returned no A/AAAA records." "ERROR" $allPassed = $false continue } # Extract the CNAME/alias targets from the system DNS records. $cnameRecords = @( $systemRecords | Where-Object -FilterScript { $_.PSObject.Properties.Name -contains 'QueryType' -and $_.QueryType -eq 'CNAME' -and $_.PSObject.Properties.Name -contains 'NameHost' -and -Not [string]::IsNullOrWhiteSpace($_.NameHost) } | Select-Object -ExpandProperty NameHost -Unique ) if ($cnameRecords.Count -gt 0) { Write-Log "CNAME/alias targets: $($cnameRecords -join ' -> ')" "DEBUG" } # Check for local hosts file overrides for the tenant FQDN. $hostsFileLines = @(Get-HostsFileLinesForFqdn -Fqdn $tenantFqdn) if ($hostsFileLines.Count -gt 0) { Write-Log "Hosts-file override found for '$tenantFqdn'." "ERROR" foreach ($hostLine in $hostsFileLines) { Write-Log " hosts: $hostLine" "ERROR" } Write-Log "Recommended action: Remove this hosts-file entry so '$tenantFqdn' resolves to the real DNS IP." "WARNING" $allPassed = $false } # Compare the real DNS IP addresses with the system DNS IP addresses to detect discrepancies. $realIpSet = @($realIPs | ForEach-Object { "$_".ToLowerInvariant() } | Sort-Object -Unique) $systemIpSet = @($systemIPs | ForEach-Object { "$_".ToLowerInvariant() } | Sort-Object -Unique) $dnsDiff = @(Compare-Object -ReferenceObject $realIpSet -DifferenceObject $systemIpSet -CaseSensitive:$false) if ($dnsDiff.Count -gt 0) { Write-Log "System DNS differs from direct DNS for '$tenantFqdn'. This can indicate a local DNS override/interception path." "WARNING" } $syntheticSystemIPv4 = @( $systemIPs | Where-Object -FilterScript { $_ -match '^\d{1,3}(\.\d{1,3}){3}$' -and (Test-GSAIPInRange -Start $syntheticRangeStart -End $syntheticRangeEnd -IPAddress $_) } ) if ($syntheticSystemIPv4.Count -eq 0) { Write-Log "System DNS for '$tenantFqdn' did not return an expected synthetic 6.6.0.0/16 address." "ERROR" Write-Log "Expected output is similar to: Address: 6.6.x.x with aliases under ztna3p-*.ztna3p-msedge.net." "WARNING" $allPassed = $false } else { Write-Log "System DNS returned expected synthetic GSA address(es): $($syntheticSystemIPv4 -join ', ')." "SUCCESS" } # Check for private IPv4 addresses in the system DNS results, which should not be present for synthetic GSA addresses. $privateSystemIPv4 = @($systemIPs | Where-Object -FilterScript { Test-GSAIsPrivateIPv4 -IPAddress $_ }) if ($privateSystemIPv4.Count -gt 0) { Write-Log "System DNS for '$tenantFqdn' includes private IPv4 address(es): $($privateSystemIPv4 -join ', ')." "ERROR" Write-Log "Expected result is synthetic GSA address(es) in 6.6.0.0/16, not RFC1918 private IPs." "WARNING" $allPassed = $false } } if ($allPassed) { Write-Log "Tenant Internet forwarding DNS hostnames resolve to real DNS addresses with no hosts-file override detected." "SUCCESS" } $script:_testResult = [PSCustomObject]@{ Passed = $allPassed; Warnings = 0 } } |