源码快照 @ 2026-09-02 (source-only, 剔除非源码素材)
This commit is contained in:
@@ -0,0 +1,607 @@
|
||||
param(
|
||||
[string]$WebUrl = "",
|
||||
[string]$ApiUrl = "",
|
||||
[string]$ExpectedIssuerMatch = "DigiCert"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Continue"
|
||||
$script:VerdictHints = @()
|
||||
|
||||
function Write-Report {
|
||||
param([string]$Message = "")
|
||||
Write-Output $Message
|
||||
}
|
||||
|
||||
function Add-VerdictHint {
|
||||
param([string]$Hint)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Hint)) {
|
||||
return
|
||||
}
|
||||
|
||||
if ($script:VerdictHints -notcontains $Hint) {
|
||||
$script:VerdictHints += $Hint
|
||||
}
|
||||
}
|
||||
|
||||
function Write-ExceptionChain {
|
||||
param(
|
||||
[System.Exception]$Exception,
|
||||
[string]$Indent = " "
|
||||
)
|
||||
|
||||
$current = $Exception
|
||||
$depth = 0
|
||||
while ($current -ne $null -and $depth -lt 10) {
|
||||
Write-Report ("{0}- {1}: {2}" -f $Indent, $current.GetType().FullName, $current.Message)
|
||||
$current = $current.InnerException
|
||||
$depth += 1
|
||||
}
|
||||
}
|
||||
|
||||
function Get-TargetPort {
|
||||
param([System.Uri]$Uri)
|
||||
|
||||
if ($Uri.Port -gt 0) {
|
||||
return $Uri.Port
|
||||
}
|
||||
|
||||
if ($Uri.Scheme -eq "http") {
|
||||
return 80
|
||||
}
|
||||
|
||||
return 443
|
||||
}
|
||||
|
||||
function Test-InternalIssuer {
|
||||
param([string]$Issuer)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Issuer)) {
|
||||
return $false
|
||||
}
|
||||
|
||||
$lower = $Issuer.ToLowerInvariant()
|
||||
$keywords = @(
|
||||
"internal",
|
||||
"corp",
|
||||
"corporate",
|
||||
"proxy",
|
||||
"zscaler",
|
||||
"netskope",
|
||||
"microsoft",
|
||||
"active directory",
|
||||
"domain ca",
|
||||
"ssl inspection",
|
||||
"fortinet",
|
||||
"palo alto",
|
||||
"checkpoint",
|
||||
"cisco umbrella"
|
||||
)
|
||||
|
||||
foreach ($keyword in $keywords) {
|
||||
if ($lower.Contains($keyword)) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
function Find-OpenSsl {
|
||||
try {
|
||||
$command = Get-Command openssl -ErrorAction SilentlyContinue
|
||||
if ($command -ne $null -and -not [string]::IsNullOrWhiteSpace($command.Source)) {
|
||||
return $command.Source
|
||||
}
|
||||
}
|
||||
catch {
|
||||
}
|
||||
|
||||
$paths = @(
|
||||
"C:\Program Files\Git\usr\bin\openssl.exe",
|
||||
"C:\Program Files\Git\mingw64\bin\openssl.exe",
|
||||
"C:\Program Files (x86)\Git\usr\bin\openssl.exe",
|
||||
"C:\Program Files (x86)\Git\mingw64\bin\openssl.exe"
|
||||
)
|
||||
|
||||
foreach ($path in $paths) {
|
||||
try {
|
||||
if (Test-Path -LiteralPath $path) {
|
||||
return $path
|
||||
}
|
||||
}
|
||||
catch {
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Invoke-DnsProbe {
|
||||
param([string]$TargetHost)
|
||||
|
||||
Write-Report "DNS:"
|
||||
try {
|
||||
$resolveCommand = Get-Command Resolve-DnsName -ErrorAction SilentlyContinue
|
||||
if ($resolveCommand -ne $null) {
|
||||
Write-Report " Engine: Resolve-DnsName"
|
||||
try {
|
||||
$records = Resolve-DnsName -Name $TargetHost -ErrorAction Stop
|
||||
if ($records -eq $null) {
|
||||
Write-Report " No records returned."
|
||||
Add-VerdictHint ("LIKELY DNS ISSUE: {0} returned no DNS records." -f $TargetHost)
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($record in $records) {
|
||||
$parts = @()
|
||||
if ($record.Name -ne $null) { $parts += ("name={0}" -f $record.Name) }
|
||||
if ($record.Type -ne $null) { $parts += ("type={0}" -f $record.Type) }
|
||||
if ($record.IPAddress -ne $null) { $parts += ("address={0}" -f $record.IPAddress) }
|
||||
if ($record.NameHost -ne $null) { $parts += ("target={0}" -f $record.NameHost) }
|
||||
if ($record.QueryType -ne $null -and $record.Type -eq $null) { $parts += ("type={0}" -f $record.QueryType) }
|
||||
if ($parts.Count -eq 0) { $parts += ($record | Out-String).Trim() }
|
||||
Write-Report (" - {0}" -f ($parts -join " | "))
|
||||
}
|
||||
return
|
||||
}
|
||||
catch {
|
||||
Write-Report (" Resolve-DnsName failed or NXDOMAIN: {0}" -f $_.Exception.Message)
|
||||
Write-Report " Falling back to [System.Net.Dns]::GetHostAddresses."
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Report " Engine: [System.Net.Dns]::GetHostAddresses (Resolve-DnsName unavailable)"
|
||||
}
|
||||
|
||||
try {
|
||||
$addresses = [System.Net.Dns]::GetHostAddresses($TargetHost)
|
||||
if ($addresses -eq $null -or $addresses.Count -eq 0) {
|
||||
Write-Report " No addresses returned."
|
||||
Add-VerdictHint ("LIKELY DNS ISSUE: {0} returned no DNS records." -f $TargetHost)
|
||||
return
|
||||
}
|
||||
|
||||
foreach ($address in $addresses) {
|
||||
Write-Report (" - address={0}" -f $address.IPAddressToString)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Report (" NXDOMAIN or DNS lookup failed: {0}" -f $_.Exception.Message)
|
||||
Add-VerdictHint ("LIKELY DNS ISSUE: {0} did not resolve." -f $TargetHost)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Report " DNS probe failed unexpectedly:"
|
||||
Write-ExceptionChain -Exception $_.Exception -Indent " "
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-TcpProbe {
|
||||
param(
|
||||
[string]$TargetHost,
|
||||
[int]$Port
|
||||
)
|
||||
|
||||
Write-Report "TCP:"
|
||||
$client = $null
|
||||
try {
|
||||
$client = New-Object System.Net.Sockets.TcpClient
|
||||
$async = $client.BeginConnect($TargetHost, $Port, $null, $null)
|
||||
$connected = $async.AsyncWaitHandle.WaitOne(5000, $false)
|
||||
if (-not $connected) {
|
||||
try { $client.Close() } catch { }
|
||||
Write-Report (" FAILED: timeout connecting to {0}:{1}." -f $TargetHost, $Port)
|
||||
Add-VerdictHint ("LIKELY NETWORK/FIREWALL ISSUE: TCP {0}:{1} timed out." -f $TargetHost, $Port)
|
||||
return
|
||||
}
|
||||
|
||||
$client.EndConnect($async)
|
||||
Write-Report (" OK: connected to {0}:{1}." -f $TargetHost, $Port)
|
||||
}
|
||||
catch {
|
||||
Write-Report (" FAILED: could not connect to {0}:{1}." -f $TargetHost, $Port)
|
||||
Write-ExceptionChain -Exception $_.Exception -Indent " "
|
||||
Add-VerdictHint ("LIKELY NETWORK/FIREWALL ISSUE: TCP {0}:{1} failed." -f $TargetHost, $Port)
|
||||
}
|
||||
finally {
|
||||
if ($client -ne $null) {
|
||||
try { $client.Close() } catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-TlsProbe {
|
||||
param(
|
||||
[string]$TargetHost,
|
||||
[int]$Port,
|
||||
[string]$ExpectedIssuerMatch
|
||||
)
|
||||
|
||||
Write-Report "TLS handshake and certificate chain:"
|
||||
$client = $null
|
||||
$sslStream = $null
|
||||
$capture = New-Object PSObject -Property @{
|
||||
CallbackSeen = $false
|
||||
LeafSubject = ""
|
||||
LeafIssuer = ""
|
||||
LeafThumbprint = ""
|
||||
LeafNotAfter = ""
|
||||
PolicyErrors = ""
|
||||
ChainElements = @()
|
||||
ChainStatusFlags = @()
|
||||
ChainStatusLines = @()
|
||||
}
|
||||
|
||||
try {
|
||||
$callback = {
|
||||
param($sender, $certificate, $chain, $sslPolicyErrors)
|
||||
|
||||
$capture.CallbackSeen = $true
|
||||
$capture.PolicyErrors = $sslPolicyErrors.ToString()
|
||||
|
||||
if ($certificate -ne $null) {
|
||||
$leaf = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $certificate
|
||||
$capture.LeafSubject = $leaf.Subject
|
||||
$capture.LeafIssuer = $leaf.Issuer
|
||||
$capture.LeafThumbprint = $leaf.Thumbprint
|
||||
$capture.LeafNotAfter = $leaf.NotAfter.ToString("u")
|
||||
}
|
||||
|
||||
if ($chain -ne $null) {
|
||||
$elements = @()
|
||||
foreach ($element in $chain.ChainElements) {
|
||||
$elements += ("subject={0} | issuer={1}" -f $element.Certificate.Subject, $element.Certificate.Issuer)
|
||||
}
|
||||
$capture.ChainElements = $elements
|
||||
|
||||
$statusFlags = @()
|
||||
$statusLines = @()
|
||||
foreach ($status in $chain.ChainStatus) {
|
||||
$statusFlags += $status.Status.ToString()
|
||||
$statusLines += ("{0}: {1}" -f $status.Status, $status.StatusInformation.Trim())
|
||||
}
|
||||
$capture.ChainStatusFlags = $statusFlags
|
||||
$capture.ChainStatusLines = $statusLines
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
$client = New-Object System.Net.Sockets.TcpClient
|
||||
$async = $client.BeginConnect($TargetHost, $Port, $null, $null)
|
||||
$connected = $async.AsyncWaitHandle.WaitOne(10000, $false)
|
||||
if (-not $connected) {
|
||||
try { $client.Close() } catch { }
|
||||
Write-Report (" FAILED: timeout connecting to {0}:{1} before TLS." -f $TargetHost, $Port)
|
||||
Add-VerdictHint ("LIKELY NETWORK/FIREWALL ISSUE: TLS TCP connect to {0}:{1} timed out." -f $TargetHost, $Port)
|
||||
return
|
||||
}
|
||||
|
||||
$client.EndConnect($async)
|
||||
$client.ReceiveTimeout = 10000
|
||||
$client.SendTimeout = 10000
|
||||
$networkStream = $client.GetStream()
|
||||
$networkStream.ReadTimeout = 10000
|
||||
$networkStream.WriteTimeout = 10000
|
||||
$sslStream = New-Object System.Net.Security.SslStream -ArgumentList @($networkStream, $false, ([System.Net.Security.RemoteCertificateValidationCallback]$callback))
|
||||
$sslStream.AuthenticateAsClient($TargetHost)
|
||||
|
||||
Write-Report " Handshake: completed. The validation callback accepted the cert for diagnostics; policy errors below are the real validation result."
|
||||
}
|
||||
catch {
|
||||
Write-Report " Handshake: FAILED. Exception chain:"
|
||||
Write-ExceptionChain -Exception $_.Exception -Indent " "
|
||||
Add-VerdictHint ("TLS HANDSHAKE FAILED: {0}:{1} did not complete TLS negotiation." -f $TargetHost, $Port)
|
||||
}
|
||||
finally {
|
||||
if ($sslStream -ne $null) { try { $sslStream.Dispose() } catch { } }
|
||||
if ($client -ne $null) { try { $client.Close() } catch { } }
|
||||
}
|
||||
|
||||
if ($capture.CallbackSeen) {
|
||||
Write-Report (" Leaf subject: {0}" -f $capture.LeafSubject)
|
||||
Write-Report (" Leaf issuer: {0}" -f $capture.LeafIssuer)
|
||||
Write-Report (" Leaf thumbprint: {0}" -f $capture.LeafThumbprint)
|
||||
Write-Report (" Leaf notAfter: {0}" -f $capture.LeafNotAfter)
|
||||
Write-Report (" SslPolicyErrors: {0}" -f $capture.PolicyErrors)
|
||||
|
||||
Write-Report " Chain elements:"
|
||||
if ($capture.ChainElements.Count -eq 0) {
|
||||
Write-Report " (none captured)"
|
||||
}
|
||||
else {
|
||||
$elementIndex = 0
|
||||
foreach ($elementLine in $capture.ChainElements) {
|
||||
$elementIndex += 1
|
||||
Write-Report (" [{0}] {1}" -f $elementIndex, $elementLine)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Report " Chain status:"
|
||||
if ($capture.ChainStatusLines.Count -eq 0) {
|
||||
Write-Report " (none)"
|
||||
}
|
||||
else {
|
||||
foreach ($statusLine in $capture.ChainStatusLines) {
|
||||
Write-Report (" - {0}" -f $statusLine)
|
||||
}
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($ExpectedIssuerMatch) -and $capture.LeafIssuer -notlike ("*{0}*" -f $ExpectedIssuerMatch) -and (Test-InternalIssuer -Issuer $capture.LeafIssuer)) {
|
||||
$hint = ("LIKELY TLS INTERCEPTION (proxy re-signing): issuer is {0}, expected {1}" -f $capture.LeafIssuer, $ExpectedIssuerMatch)
|
||||
Write-Report (" {0}" -f $hint)
|
||||
Add-VerdictHint $hint
|
||||
}
|
||||
|
||||
$hasTrustOrChainProblem = $false
|
||||
foreach ($flag in $capture.ChainStatusFlags) {
|
||||
if ($flag -match "PartialChain|UntrustedRoot") {
|
||||
$hasTrustOrChainProblem = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($hasTrustOrChainProblem) {
|
||||
$hint = "LIKELY MISSING INTERMEDIATE OR UNTRUSTED ROOT"
|
||||
Write-Report (" {0}" -f $hint)
|
||||
Add-VerdictHint ("{0}: {1}" -f $hint, $TargetHost)
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Report " No certificate callback data was captured."
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-OpenSslProbe {
|
||||
param(
|
||||
[string]$TargetHost,
|
||||
[int]$Port
|
||||
)
|
||||
|
||||
Write-Report "Served-chain probe with openssl:"
|
||||
try {
|
||||
$opensslPath = Find-OpenSsl
|
||||
if ([string]::IsNullOrWhiteSpace($opensslPath)) {
|
||||
Write-Report " SKIPPED: openssl not found on PATH or common Git-for-Windows paths."
|
||||
return
|
||||
}
|
||||
|
||||
Write-Report (" Engine: {0}" -f $opensslPath)
|
||||
$process = New-Object System.Diagnostics.Process
|
||||
$process.StartInfo.FileName = $opensslPath
|
||||
$process.StartInfo.Arguments = ('s_client -showcerts -connect "{0}:{1}" -servername "{0}"' -f $TargetHost, $Port)
|
||||
$process.StartInfo.UseShellExecute = $false
|
||||
$process.StartInfo.RedirectStandardInput = $true
|
||||
$process.StartInfo.RedirectStandardOutput = $true
|
||||
$process.StartInfo.RedirectStandardError = $true
|
||||
$process.StartInfo.CreateNoWindow = $true
|
||||
|
||||
[void]$process.Start()
|
||||
$stdoutTask = $process.StandardOutput.ReadToEndAsync()
|
||||
$stderrTask = $process.StandardError.ReadToEndAsync()
|
||||
try { $process.StandardInput.Close() } catch { }
|
||||
|
||||
$exited = $process.WaitForExit(10000)
|
||||
if (-not $exited) {
|
||||
try { $process.Kill() } catch { }
|
||||
Write-Report " FAILED: openssl s_client timed out after 10 seconds."
|
||||
return
|
||||
}
|
||||
|
||||
$process.WaitForExit()
|
||||
$output = $stdoutTask.Result + $stderrTask.Result
|
||||
$certCount = ([regex]::Matches($output, "-----BEGIN CERTIFICATE-----")).Count
|
||||
Write-Report (" Certificates served by server: {0}" -f $certCount)
|
||||
|
||||
$openSslSubject = ""
|
||||
$openSslIssuer = ""
|
||||
foreach ($line in ($output -split "`r?`n")) {
|
||||
if ([string]::IsNullOrWhiteSpace($openSslSubject) -and $line -match "^subject=(.*)$") {
|
||||
$openSslSubject = $matches[1].Trim()
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($openSslIssuer) -and $line -match "^issuer=(.*)$") {
|
||||
$openSslIssuer = $matches[1].Trim()
|
||||
}
|
||||
}
|
||||
|
||||
if ($certCount -eq 1) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($openSslSubject) -and $openSslSubject -eq $openSslIssuer) {
|
||||
$hint = ("UNTRUSTED SELF-SIGNED LEAF: {0}:{1} served one self-signed certificate." -f $TargetHost, $Port)
|
||||
}
|
||||
else {
|
||||
$hint = ("MISSING INTERMEDIATE CONFIRMED BY OPENSSL: {0}:{1} served one certificate (leaf only)." -f $TargetHost, $Port)
|
||||
}
|
||||
Write-Report (" {0}" -f $hint)
|
||||
Add-VerdictHint $hint
|
||||
}
|
||||
elseif ($certCount -gt 1) {
|
||||
Write-Report " Server served more than one certificate. If clients still fail, inspect trust roots or interception."
|
||||
}
|
||||
else {
|
||||
Write-Report " No PEM certificates found in openssl output."
|
||||
}
|
||||
|
||||
$interestingLines = @()
|
||||
foreach ($line in ($output -split "`r?`n")) {
|
||||
if ($line -match "^(depth=|verify error:|Verify return code:|subject=|issuer=)") {
|
||||
$interestingLines += $line
|
||||
}
|
||||
}
|
||||
|
||||
if ($interestingLines.Count -gt 0) {
|
||||
Write-Report " OpenSSL summary lines:"
|
||||
foreach ($line in ($interestingLines | Select-Object -First 12)) {
|
||||
Write-Report (" {0}" -f $line)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Report " OpenSSL probe failed:"
|
||||
Write-ExceptionChain -Exception $_.Exception -Indent " "
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-ProxyProbe {
|
||||
param([System.Uri]$Uri)
|
||||
|
||||
Write-Report "Proxy:"
|
||||
try {
|
||||
$netshCommand = Get-Command netsh -ErrorAction SilentlyContinue
|
||||
if ($netshCommand -ne $null) {
|
||||
try {
|
||||
$netshOutput = netsh winhttp show proxy 2>&1 | Out-String
|
||||
Write-Report " netsh winhttp show proxy:"
|
||||
foreach ($line in ($netshOutput -split "`r?`n")) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($line)) {
|
||||
Write-Report (" {0}" -f $line)
|
||||
}
|
||||
}
|
||||
if ($netshOutput -and $netshOutput -notmatch "Direct access") {
|
||||
Add-VerdictHint "PROXY DETECTED: WinHTTP proxy is configured."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Report (" netsh failed: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Report " netsh unavailable on this host."
|
||||
}
|
||||
|
||||
try {
|
||||
$proxy = [System.Net.WebRequest]::DefaultWebProxy
|
||||
if ($proxy -eq $null) {
|
||||
Write-Report " DefaultWebProxy: none"
|
||||
return
|
||||
}
|
||||
|
||||
$proxyUri = $proxy.GetProxy($Uri)
|
||||
if ($proxyUri -eq $null) {
|
||||
Write-Report " DefaultWebProxy.GetProxy: none"
|
||||
}
|
||||
elseif ($proxyUri.AbsoluteUri -eq $Uri.AbsoluteUri) {
|
||||
Write-Report (" DefaultWebProxy.GetProxy({0}): direct" -f $Uri.AbsoluteUri)
|
||||
}
|
||||
else {
|
||||
$hint = ("PROXY DETECTED: DefaultWebProxy routes {0} via {1}" -f $Uri.AbsoluteUri, $proxyUri.AbsoluteUri)
|
||||
Write-Report (" {0}" -f $hint)
|
||||
Add-VerdictHint $hint
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Report " DefaultWebProxy probe failed:"
|
||||
Write-ExceptionChain -Exception $_.Exception -Indent " "
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Report " Proxy probe failed unexpectedly:"
|
||||
Write-ExceptionChain -Exception $_.Exception -Indent " "
|
||||
}
|
||||
}
|
||||
|
||||
function Write-EnvironmentReport {
|
||||
Write-Report "===== ENVIRONMENT ====="
|
||||
try {
|
||||
$editionText = "Desktop"
|
||||
if ($PSVersionTable.ContainsKey("PSEdition")) {
|
||||
$editionText = $PSVersionTable.PSEdition
|
||||
}
|
||||
Write-Report ("PowerShell: {0} ({1})" -f $PSVersionTable.PSVersion.ToString(), $editionText)
|
||||
if ($PSVersionTable.PSVersion.Major -le 5) {
|
||||
Write-Report "Compatibility: Windows PowerShell 5.1-compatible mode."
|
||||
}
|
||||
else {
|
||||
Write-Report "Compatibility: running on newer PowerShell; script avoids PS7-only syntax/APIs and prints fallbacks used."
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Report ("PowerShell: unable to read PSVersionTable: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
try {
|
||||
Write-Report ("OS: {0}" -f [System.Environment]::OSVersion.VersionString)
|
||||
}
|
||||
catch {
|
||||
Write-Report ("OS: unable to read OSVersion: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
try {
|
||||
$nodeExtraCaCerts = [System.Environment]::GetEnvironmentVariable("NODE_EXTRA_CA_CERTS")
|
||||
if ([string]::IsNullOrWhiteSpace($nodeExtraCaCerts)) {
|
||||
Write-Report "NODE_EXTRA_CA_CERTS: not set"
|
||||
}
|
||||
else {
|
||||
Write-Report ("NODE_EXTRA_CA_CERTS: {0}" -f $nodeExtraCaCerts)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Report ("NODE_EXTRA_CA_CERTS: unable to read: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
|
||||
Write-Report "Features used: Resolve-DnsName if available; [System.Net.Dns] fallback; TcpClient timeout; System.Net.Security.SslStream diagnostic callback; openssl if available."
|
||||
Write-Report ""
|
||||
}
|
||||
|
||||
function Invoke-UrlProbe {
|
||||
param([string]$Url)
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($Url)) {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Report ("===== URL: {0} =====" -f $Url)
|
||||
try {
|
||||
$uri = New-Object System.Uri -ArgumentList $Url
|
||||
$targetHost = $uri.DnsSafeHost
|
||||
$port = Get-TargetPort -Uri $uri
|
||||
Write-Report ("Parsed target: host={0} port={1} scheme={2}" -f $targetHost, $port, $uri.Scheme)
|
||||
|
||||
Invoke-DnsProbe -TargetHost $targetHost
|
||||
Invoke-TcpProbe -TargetHost $targetHost -Port $port
|
||||
Invoke-TlsProbe -TargetHost $targetHost -Port $port -ExpectedIssuerMatch $ExpectedIssuerMatch
|
||||
Invoke-OpenSslProbe -TargetHost $targetHost -Port $port
|
||||
Invoke-ProxyProbe -Uri $uri
|
||||
}
|
||||
catch {
|
||||
Write-Report "URL probe failed unexpectedly:"
|
||||
Write-ExceptionChain -Exception $_.Exception -Indent " "
|
||||
Add-VerdictHint ("URL PROBE FAILED: {0}" -f $Url)
|
||||
}
|
||||
Write-Report ""
|
||||
}
|
||||
|
||||
$timestamp = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ssK")
|
||||
$emdash = [char]0x2014
|
||||
Write-Report ("OpenWork Network Doctor v1 {0} {1}" -f $emdash, $timestamp)
|
||||
Write-Report "This entire output is safe to copy/paste back to OpenWork support."
|
||||
Write-Report ("Expected issuer match: {0}" -f $ExpectedIssuerMatch)
|
||||
if ([string]::IsNullOrWhiteSpace($WebUrl)) {
|
||||
Write-Report "WebUrl: (empty; skipped)"
|
||||
}
|
||||
else {
|
||||
Write-Report ("WebUrl: {0}" -f $WebUrl)
|
||||
}
|
||||
if ([string]::IsNullOrWhiteSpace($ApiUrl)) {
|
||||
Write-Report "ApiUrl: (empty; skipped)"
|
||||
}
|
||||
else {
|
||||
Write-Report ("ApiUrl: {0}" -f $ApiUrl)
|
||||
}
|
||||
Write-Report ""
|
||||
|
||||
Write-EnvironmentReport
|
||||
Invoke-UrlProbe -Url $WebUrl
|
||||
if (-not [string]::IsNullOrWhiteSpace($ApiUrl)) {
|
||||
Invoke-UrlProbe -Url $ApiUrl
|
||||
}
|
||||
|
||||
Write-Report "===== VERDICT HINTS ====="
|
||||
if ($script:VerdictHints.Count -eq 0) {
|
||||
Write-Report "- No likely root cause was detected by this script. If the app still fails, send this full report to OpenWork support."
|
||||
}
|
||||
else {
|
||||
foreach ($hint in $script:VerdictHints) {
|
||||
Write-Report ("- {0}" -f $hint)
|
||||
}
|
||||
}
|
||||
Write-Report "===== COPY EVERYTHING ABOVE THIS LINE ====="
|
||||
@@ -0,0 +1,394 @@
|
||||
param(
|
||||
[string]$Hostname = "poc.openwork.test",
|
||||
[int]$HealthyPort = 8443,
|
||||
[int]$BrokenPort = 9443,
|
||||
[switch]$Cleanup
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$Marker = "OpenWork TLS Repro"
|
||||
$ReproDir = Join-Path (Get-Location).Path "tls-repro"
|
||||
$StatePath = Join-Path $ReproDir "state.txt"
|
||||
$HostsMarker = "# OpenWork TLS repro"
|
||||
$SslAppId = "{1f6c8f8b-6b57-4a0b-8a1c-8d7e3d8f0d31}"
|
||||
|
||||
function Write-Step {
|
||||
param([string]$Message = "")
|
||||
Write-Host $Message
|
||||
}
|
||||
|
||||
function Assert-WindowsAdmin {
|
||||
if ([System.Environment]::OSVersion.Platform -ne [System.PlatformID]::Win32NT) {
|
||||
Write-Step "ERROR: This setup script is Windows-only because it uses New-SelfSignedCertificate, Cert:\LocalMachine, netsh, and HttpListener TLS bindings."
|
||||
exit 1
|
||||
}
|
||||
|
||||
try {
|
||||
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
|
||||
$principal = New-Object System.Security.Principal.WindowsPrincipal -ArgumentList $identity
|
||||
$isAdmin = $principal.IsInRole([System.Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
if (-not $isAdmin) {
|
||||
Write-Step "ERROR: Run this from an elevated PowerShell prompt (Run as Administrator)."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Step "ERROR: Could not verify administrator privileges. Run this from elevated Windows PowerShell."
|
||||
Write-Step $_.Exception.Message
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function Read-State {
|
||||
$state = @{}
|
||||
if (Test-Path -LiteralPath $StatePath) {
|
||||
foreach ($line in (Get-Content -LiteralPath $StatePath)) {
|
||||
if ($line -match "^([^=]+)=(.*)$") {
|
||||
$state[$matches[1]] = $matches[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
return $state
|
||||
}
|
||||
|
||||
function Remove-SslBinding {
|
||||
param([int]$Port)
|
||||
|
||||
try {
|
||||
$ipPort = "0.0.0.0:{0}" -f $Port
|
||||
$output = & netsh http delete sslcert ipport=$ipPort 2>&1
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Step ("Removed SSL binding {0}." -f $ipPort)
|
||||
}
|
||||
else {
|
||||
Write-Step ("No removable SSL binding at {0} (netsh: {1})." -f $ipPort, (($output | Out-String).Trim()))
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Step ("WARN: Could not remove SSL binding for port {0}: {1}" -f $Port, $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
function Add-SslBinding {
|
||||
param(
|
||||
[int]$Port,
|
||||
[string]$Thumbprint
|
||||
)
|
||||
|
||||
Remove-SslBinding -Port $Port
|
||||
$ipPort = "0.0.0.0:{0}" -f $Port
|
||||
$output = & netsh http add sslcert ipport=$ipPort certhash=$Thumbprint appid=$SslAppId certstorename=MY 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw ("netsh http add sslcert failed for {0}: {1}" -f $ipPort, (($output | Out-String).Trim()))
|
||||
}
|
||||
Write-Step ("Added SSL binding {0} -> {1}." -f $ipPort, $Thumbprint)
|
||||
}
|
||||
|
||||
function Stop-ReproJobs {
|
||||
try {
|
||||
$jobs = Get-Job -Name "OpenWorkTlsRepro-*" -ErrorAction SilentlyContinue
|
||||
foreach ($job in $jobs) {
|
||||
Write-Step ("Stopping PowerShell job {0} (Id {1})." -f $job.Name, $job.Id)
|
||||
Stop-Job -Job $job -ErrorAction SilentlyContinue
|
||||
Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Step ("WARN: Could not stop repro jobs in this session: {0}" -f $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-HostsEntries {
|
||||
param([string[]]$Names)
|
||||
|
||||
$hostsPath = Join-Path $env:SystemRoot "System32\drivers\etc\hosts"
|
||||
if (-not (Test-Path -LiteralPath $hostsPath)) {
|
||||
Write-Step ("WARN: Hosts file not found at {0}." -f $hostsPath)
|
||||
return
|
||||
}
|
||||
|
||||
$lines = Get-Content -LiteralPath $hostsPath
|
||||
$newLines = @()
|
||||
$changed = $false
|
||||
foreach ($line in $lines) {
|
||||
$removeLine = $false
|
||||
if ($line -match [regex]::Escape($HostsMarker)) {
|
||||
$removeLine = $true
|
||||
}
|
||||
else {
|
||||
foreach ($name in $Names) {
|
||||
if (-not [string]::IsNullOrWhiteSpace($name)) {
|
||||
$escapedName = [regex]::Escape($name)
|
||||
if ($line -match ("^\s*127\.0\.0\.1\s+{0}(\s|$)" -f $escapedName)) {
|
||||
$removeLine = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($removeLine) {
|
||||
$changed = $true
|
||||
}
|
||||
else {
|
||||
$newLines += $line
|
||||
}
|
||||
}
|
||||
|
||||
if ($changed) {
|
||||
Set-Content -LiteralPath $hostsPath -Value $newLines -Encoding ASCII
|
||||
Write-Step "Removed repro hosts-file entries."
|
||||
}
|
||||
else {
|
||||
Write-Step "No repro hosts-file entries found."
|
||||
}
|
||||
}
|
||||
|
||||
function Add-HostsEntry {
|
||||
param([string]$Name)
|
||||
|
||||
$hostsPath = Join-Path $env:SystemRoot "System32\drivers\etc\hosts"
|
||||
$escapedName = [regex]::Escape($Name)
|
||||
$existingLines = @()
|
||||
if (Test-Path -LiteralPath $hostsPath) {
|
||||
$existingLines = Get-Content -LiteralPath $hostsPath
|
||||
}
|
||||
|
||||
foreach ($line in $existingLines) {
|
||||
if ($line -match ("^\s*127\.0\.0\.1\s+{0}(\s|$)" -f $escapedName)) {
|
||||
Write-Step ("Hosts file already maps 127.0.0.1 to {0}." -f $Name)
|
||||
return
|
||||
}
|
||||
if ($line -match ("^\s*\S+\s+{0}(\s|$)" -f $escapedName)) {
|
||||
Write-Step ("WARN: Hosts file already mentions {0}: {1}" -f $Name, $line)
|
||||
}
|
||||
}
|
||||
|
||||
Add-Content -LiteralPath $hostsPath -Value ("127.0.0.1 {0} {1}" -f $Name, $HostsMarker) -Encoding ASCII
|
||||
Write-Step ("Added hosts-file entry: 127.0.0.1 {0}." -f $Name)
|
||||
}
|
||||
|
||||
function Remove-CertificatesByThumbprint {
|
||||
param([string[]]$Thumbprints)
|
||||
|
||||
$stores = @("Cert:\LocalMachine\My", "Cert:\LocalMachine\Root", "Cert:\LocalMachine\CA", "Cert:\CurrentUser\My")
|
||||
foreach ($thumbprint in ($Thumbprints | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | Select-Object -Unique)) {
|
||||
foreach ($store in $stores) {
|
||||
try {
|
||||
$matches = Get-ChildItem -LiteralPath $store -ErrorAction SilentlyContinue | Where-Object { $_.Thumbprint -eq $thumbprint }
|
||||
foreach ($cert in $matches) {
|
||||
Write-Step ("Removing certificate thumbprint {0} from {1}." -f $thumbprint, $store)
|
||||
Remove-Item -LiteralPath $cert.PSPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Step ("WARN: Could not scan {0}: {1}" -f $store, $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Remove-CertificatesByMarker {
|
||||
$stores = @("Cert:\LocalMachine\My", "Cert:\LocalMachine\Root", "Cert:\LocalMachine\CA", "Cert:\CurrentUser\My")
|
||||
foreach ($store in $stores) {
|
||||
try {
|
||||
$matches = Get-ChildItem -LiteralPath $store -ErrorAction SilentlyContinue | Where-Object { $_.Subject -like ("*{0}*" -f $Marker) }
|
||||
foreach ($cert in $matches) {
|
||||
Write-Step ("Removing marked certificate {0} from {1}." -f $cert.Thumbprint, $store)
|
||||
Remove-Item -LiteralPath $cert.PSPath -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Step ("WARN: Could not remove marked certs from {0}: {1}" -f $store, $_.Exception.Message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-Cleanup {
|
||||
param([switch]$Quiet)
|
||||
|
||||
if (-not $Quiet) {
|
||||
Write-Step "Cleaning OpenWork TLS repro artifacts..."
|
||||
}
|
||||
|
||||
$state = Read-State
|
||||
$ports = @($HealthyPort, $BrokenPort)
|
||||
if ($state.ContainsKey("HealthyPort")) { $ports += [int]$state["HealthyPort"] }
|
||||
if ($state.ContainsKey("BrokenPort")) { $ports += [int]$state["BrokenPort"] }
|
||||
foreach ($port in ($ports | Select-Object -Unique)) {
|
||||
Remove-SslBinding -Port $port
|
||||
}
|
||||
|
||||
Stop-ReproJobs
|
||||
|
||||
$names = @($Hostname)
|
||||
if ($state.ContainsKey("Hostname")) { $names += $state["Hostname"] }
|
||||
Remove-HostsEntries -Names ($names | Select-Object -Unique)
|
||||
|
||||
$thumbprints = @()
|
||||
foreach ($key in $state.Keys) {
|
||||
if ($key -match "Thumbprint$") {
|
||||
$thumbprints += $state[$key]
|
||||
}
|
||||
}
|
||||
Remove-CertificatesByThumbprint -Thumbprints $thumbprints
|
||||
Remove-CertificatesByMarker
|
||||
|
||||
if (Test-Path -LiteralPath $ReproDir) {
|
||||
Remove-Item -LiteralPath $ReproDir -Recurse -Force
|
||||
Write-Step ("Removed {0}." -f $ReproDir)
|
||||
}
|
||||
}
|
||||
|
||||
function Start-ReproListenerJob {
|
||||
param(
|
||||
[int]$Port,
|
||||
[string]$Label
|
||||
)
|
||||
|
||||
$jobName = "OpenWorkTlsRepro-{0}" -f $Port
|
||||
$existing = Get-Job -Name $jobName -ErrorAction SilentlyContinue
|
||||
foreach ($job in $existing) {
|
||||
Stop-Job -Job $job -ErrorAction SilentlyContinue
|
||||
Remove-Job -Job $job -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# Windows-only runtime path. This section is parse-checked from macOS but must be exercised in a Daytona Windows sandbox.
|
||||
# HttpListener delegates TLS to HTTP.sys/Schannel. The healthy port can send the intermediate because it is installed
|
||||
# in LocalMachine\CA; the broken port binds a leaf whose intermediate is intentionally removed from local stores.
|
||||
$job = Start-Job -Name $jobName -ArgumentList $Port, $Label -ScriptBlock {
|
||||
param([int]$Port, [string]$Label)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$listener = New-Object System.Net.HttpListener
|
||||
$listener.Prefixes.Add(("https://+:{0}/" -f $Port))
|
||||
$listener.Start()
|
||||
try {
|
||||
while ($true) {
|
||||
$context = $listener.GetContext()
|
||||
$payload = '{"workspaces":[]}'
|
||||
$bytes = [System.Text.Encoding]::UTF8.GetBytes($payload)
|
||||
$context.Response.StatusCode = 200
|
||||
$context.Response.ContentType = "application/json"
|
||||
$context.Response.Headers.Add("X-OpenWork-TLS-Repro", $Label)
|
||||
$context.Response.ContentLength64 = $bytes.Length
|
||||
$context.Response.OutputStream.Write($bytes, 0, $bytes.Length)
|
||||
$context.Response.OutputStream.Close()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$listener.Stop()
|
||||
$listener.Close()
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Milliseconds 1000
|
||||
$job = Get-Job -Id $job.Id
|
||||
if ($job.State -ne "Running") {
|
||||
$details = Receive-Job -Job $job -Keep -ErrorAction SilentlyContinue | Out-String
|
||||
throw ("Listener job {0} did not stay running. State={1}. Output={2}" -f $jobName, $job.State, $details)
|
||||
}
|
||||
|
||||
Write-Step ("Started HTTPS listener job {0} (Id {1})." -f $job.Name, $job.Id)
|
||||
}
|
||||
|
||||
Assert-WindowsAdmin
|
||||
|
||||
if ($Cleanup) {
|
||||
Invoke-Cleanup
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Step "OpenWork TLS repro setup"
|
||||
Write-Step "Strategy: HTTP.sys/HttpListener with netsh sslcert bindings. Healthy uses root + installed intermediate; broken uses a different intermediate that is removed before serving."
|
||||
Write-Step "Risk: Windows chain caching can occasionally make the broken case validate until cache/session state is cleared; rerun -Cleanup or use a fresh VM if that happens."
|
||||
Write-Step ""
|
||||
|
||||
Invoke-Cleanup -Quiet
|
||||
New-Item -ItemType Directory -Path $ReproDir -Force | Out-Null
|
||||
|
||||
$notAfter = (Get-Date).AddYears(1)
|
||||
$rootSubject = "CN=OpenWork TLS Repro Root CA, O=$Marker"
|
||||
$healthyIntermediateSubject = "CN=OpenWork TLS Repro Healthy Intermediate CA, O=$Marker"
|
||||
$brokenIntermediateSubject = "CN=OpenWork TLS Repro Broken Intermediate CA, O=$Marker"
|
||||
$healthyLeafSubject = "CN=$Hostname, O=$Marker"
|
||||
$brokenLeafSubject = "CN=$Hostname, O=$Marker"
|
||||
$leafExtensions = @(
|
||||
"2.5.29.17={text}DNS=$Hostname",
|
||||
"2.5.29.19={critical}{text}ca=0",
|
||||
"2.5.29.37={text}1.3.6.1.5.5.7.3.1"
|
||||
)
|
||||
|
||||
Write-Step "Generating root, intermediates, and leaf certificates with New-SelfSignedCertificate..."
|
||||
$root = New-SelfSignedCertificate -Type Custom -Subject $rootSubject -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm SHA256 -KeyExportPolicy Exportable -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={critical}{text}ca=1&pathlength=2") -CertStoreLocation "Cert:\LocalMachine\My" -NotAfter $notAfter
|
||||
$healthyIntermediate = New-SelfSignedCertificate -Type Custom -Subject $healthyIntermediateSubject -Signer $root -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm SHA256 -KeyExportPolicy Exportable -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={critical}{text}ca=1&pathlength=0") -CertStoreLocation "Cert:\LocalMachine\My" -NotAfter $notAfter
|
||||
$brokenIntermediate = New-SelfSignedCertificate -Type Custom -Subject $brokenIntermediateSubject -Signer $root -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm SHA256 -KeyExportPolicy Exportable -KeyUsage CertSign, CRLSign, DigitalSignature -TextExtension @("2.5.29.19={critical}{text}ca=1&pathlength=0") -CertStoreLocation "Cert:\LocalMachine\My" -NotAfter $notAfter
|
||||
$healthyLeaf = New-SelfSignedCertificate -Type Custom -Subject $healthyLeafSubject -Signer $healthyIntermediate -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm SHA256 -KeyExportPolicy Exportable -KeySpec KeyExchange -KeyUsage DigitalSignature, KeyEncipherment -TextExtension $leafExtensions -CertStoreLocation "Cert:\LocalMachine\My" -NotAfter $notAfter
|
||||
$brokenLeaf = New-SelfSignedCertificate -Type Custom -Subject $brokenLeafSubject -Signer $brokenIntermediate -KeyAlgorithm RSA -KeyLength 2048 -HashAlgorithm SHA256 -KeyExportPolicy Exportable -KeySpec KeyExchange -KeyUsage DigitalSignature, KeyEncipherment -TextExtension $leafExtensions -CertStoreLocation "Cert:\LocalMachine\My" -NotAfter $notAfter
|
||||
|
||||
$rootPath = Join-Path $ReproDir "root.cer"
|
||||
$healthyIntermediatePath = Join-Path $ReproDir "healthy-intermediate.cer"
|
||||
Export-Certificate -Cert $root -FilePath $rootPath | Out-Null
|
||||
Export-Certificate -Cert $healthyIntermediate -FilePath $healthyIntermediatePath | Out-Null
|
||||
Import-Certificate -FilePath $rootPath -CertStoreLocation "Cert:\LocalMachine\Root" | Out-Null
|
||||
Import-Certificate -FilePath $healthyIntermediatePath -CertStoreLocation "Cert:\LocalMachine\CA" | Out-Null
|
||||
Write-Step ("Installed ONLY the repro root into Cert:\LocalMachine\Root: {0}" -f $root.Thumbprint)
|
||||
Write-Step ("Installed healthy intermediate into Cert:\LocalMachine\CA so Schannel can build/send that chain: {0}" -f $healthyIntermediate.Thumbprint)
|
||||
|
||||
# The broken leaf was signed by this intermediate, then the intermediate is removed from local stores.
|
||||
# If a Windows build caches it anyway, the broken endpoint may validate until cache/session state is reset.
|
||||
Remove-CertificatesByThumbprint -Thumbprints @($brokenIntermediate.Thumbprint)
|
||||
Write-Step ("Removed broken intermediate from local stores: {0}" -f $brokenIntermediate.Thumbprint)
|
||||
|
||||
$stateLines = @(
|
||||
"Hostname=$Hostname",
|
||||
"HealthyPort=$HealthyPort",
|
||||
"BrokenPort=$BrokenPort",
|
||||
"RootThumbprint=$($root.Thumbprint)",
|
||||
"HealthyIntermediateThumbprint=$($healthyIntermediate.Thumbprint)",
|
||||
"BrokenIntermediateThumbprint=$($brokenIntermediate.Thumbprint)",
|
||||
"HealthyLeafThumbprint=$($healthyLeaf.Thumbprint)",
|
||||
"BrokenLeafThumbprint=$($brokenLeaf.Thumbprint)"
|
||||
)
|
||||
Set-Content -LiteralPath $StatePath -Value $stateLines -Encoding ASCII
|
||||
|
||||
Add-HostsEntry -Name $Hostname
|
||||
Add-SslBinding -Port $HealthyPort -Thumbprint $healthyLeaf.Thumbprint
|
||||
Add-SslBinding -Port $BrokenPort -Thumbprint $brokenLeaf.Thumbprint
|
||||
Start-ReproListenerJob -Port $HealthyPort -Label "healthy-chain"
|
||||
Start-ReproListenerJob -Port $BrokenPort -Label "broken-missing-intermediate"
|
||||
|
||||
$healthyUrl = "https://{0}:{1}/" -f $Hostname, $HealthyPort
|
||||
$brokenUrl = "https://{0}:{1}/" -f $Hostname, $BrokenPort
|
||||
$rootPathForNode = (Resolve-Path -LiteralPath $rootPath).Path
|
||||
|
||||
Write-Step ""
|
||||
Write-Step "Setup complete. Keep this PowerShell window open so the background jobs keep serving."
|
||||
Write-Step ""
|
||||
Write-Step "Test matrix (paste each result under the command that produced it):"
|
||||
Write-Step ("1. curl healthy (expect HTTP 200 with {0}):" -f '{"workspaces":[]}')
|
||||
Write-Step (" curl.exe -v {0}" -f $healthyUrl)
|
||||
Write-Step "2. curl broken (expect certificate/chain failure on a fresh VM; if it succeeds, Windows found a cached intermediate):"
|
||||
Write-Step (" curl.exe -v {0}" -f $brokenUrl)
|
||||
Write-Step "3. Doctor against both local endpoints:"
|
||||
Write-Step (" powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\support\openwork-doctor.ps1 -WebUrl {0} -ApiUrl {1} -ExpectedIssuerMatch `"OpenWork TLS Repro`"" -f $healthyUrl, $brokenUrl)
|
||||
|
||||
$nodeCommand = Get-Command node -ErrorAction SilentlyContinue
|
||||
if ($nodeCommand -ne $null) {
|
||||
$nodeHealthy = "node -e `"fetch('$healthyUrl').then(r=>r.text()).then(console.log).catch(e=>{ console.error(e); process.exit(1) })`""
|
||||
$nodeBroken = "node -e `"fetch('$brokenUrl').then(r=>r.text()).then(console.log).catch(e=>{ console.error(e); process.exit(1) })`""
|
||||
Write-Step "4. Node fetch without extra CA (often fails because stock Node may not use Windows LocalMachine Root):"
|
||||
Write-Step (" {0}" -f $nodeHealthy)
|
||||
Write-Step (" {0}" -f $nodeBroken)
|
||||
Write-Step "5. Node fetch with the repro root trusted through NODE_EXTRA_CA_CERTS (healthy should OK; broken should fail):"
|
||||
Write-Step (" `$env:NODE_EXTRA_CA_CERTS = `"{0}`"" -f $rootPathForNode)
|
||||
Write-Step (" {0}" -f $nodeHealthy)
|
||||
Write-Step (" {0}" -f $nodeBroken)
|
||||
}
|
||||
else {
|
||||
Write-Step "4. Node fetch: node.exe was not found on PATH. Install Node or skip this part."
|
||||
Write-Step " Expected when Node is available: healthy OK only with NODE_EXTRA_CA_CERTS/system CA integration; broken FAIL."
|
||||
}
|
||||
|
||||
Write-Step ""
|
||||
Write-Step "Cleanup when finished:"
|
||||
Write-Step " powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\support\setup-openwork-tls-repro.ps1 -Cleanup"
|
||||
Reference in New Issue
Block a user