From 84c43ad3e1934d3e9a8ecdcaf85606e259f2fecc Mon Sep 17 00:00:00 2001 From: rcourtman Date: Tue, 26 May 2026 10:19:56 +0100 Subject: [PATCH] Fix Windows agent installer readiness path --- cmd/pulse-agent/main_test.go | 21 ++++ cmd/pulse-agent/service_windows.go | 17 +++ .../v6/internal/subsystems/agent-lifecycle.md | 6 +- .../v6/internal/subsystems/api-contracts.md | 6 +- .../subsystems/deployment-installability.md | 4 + .../__tests__/agentInstallCommand.test.ts | 35 ++++-- .../src/utils/agentInstallCommand.ts | 16 ++- scripts/install.ps1 | 114 ++++++++++++++---- scripts/installtests/install_ps1_test.go | 13 +- .../release_control/subsystem_lookup_test.py | 2 +- 10 files changed, 190 insertions(+), 44 deletions(-) diff --git a/cmd/pulse-agent/main_test.go b/cmd/pulse-agent/main_test.go index 3ad99e8f0..a4d7086b5 100644 --- a/cmd/pulse-agent/main_test.go +++ b/cmd/pulse-agent/main_test.go @@ -1935,6 +1935,27 @@ func TestRunAsWindowsServiceStub(t *testing.T) { } } +func TestWindowsServiceRuntimeStartsHealthServer(t *testing.T) { + source, err := os.ReadFile("service_windows.go") + if err != nil { + t.Fatalf("read pulse-agent service_windows.go: %v", err) + } + text := string(source) + + required := []string{ + `var ready atomic.Bool`, + `startHealthServer(ctx, ws.cfg.HealthAddr, &ready, &ws.logger)`, + `ready.Store(true)`, + `agentUp.Set(1)`, + `defer agentUp.Set(0)`, + } + for _, want := range required { + if !strings.Contains(text, want) { + t.Fatalf("expected Windows service runtime to include %q", want) + } + } +} + func TestRun_KubeRetry(t *testing.T) { origKube := newKubeAgent defer func() { newKubeAgent = origKube }() diff --git a/cmd/pulse-agent/service_windows.go b/cmd/pulse-agent/service_windows.go index 076903760..0f4fb9515 100644 --- a/cmd/pulse-agent/service_windows.go +++ b/cmd/pulse-agent/service_windows.go @@ -6,6 +6,7 @@ import ( "context" "fmt" "os" + "sync/atomic" "time" "github.com/rcourtman/pulse-go-rewrite/internal/agentupdate" @@ -39,6 +40,20 @@ func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, cha g, ctx := errgroup.WithContext(ctx) + agentInfo.WithLabelValues( + Version, + fmt.Sprintf("%t", ws.cfg.EnableHost), + fmt.Sprintf("%t", ws.cfg.EnableDocker), + fmt.Sprintf("%t", ws.cfg.EnableKubernetes), + ).Set(1) + agentUp.Set(1) + defer agentUp.Set(0) + + var ready atomic.Bool + if ws.cfg.HealthAddr != "" { + startHealthServer(ctx, ws.cfg.HealthAddr, &ready, &ws.logger) + } + // Start Auto-Updater updater := newUpdater(agentupdate.Config{ PulseURL: ws.cfg.PulseURL, @@ -131,6 +146,8 @@ func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, cha }) } + ready.Store(true) + changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} ws.logger.Info(). Str("version", Version). diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index fc67e3c8d..343b66a85 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -141,7 +141,11 @@ reverse. The Windows installer must support a non-mutating download preflight that can run before Administrator-only install work, must accept token-file enrollment input, and must persist plain-HTTP/insecure runtime continuity - consistently with the Unix installer. + consistently with the Unix installer. The installed Windows service must + also expose the same local health/readiness server as foreground + `pulse-agent` runs so installer "healthy" verification and post-install + smoke checks prove a live agent runtime, not merely a running service + wrapper. 23. `scripts/install.sh` shared with `deployment-installability`: the shell installer is both a deployment installability entry point and a canonical agent lifecycle runtime continuity boundary. Agent lifecycle and fleet-operation surfaces may consume diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index bd97bcbda..c917eea27 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -266,7 +266,11 @@ platform page needs source-native backup columns. behavior, and `/download/pulse-agent?arch=...` availability proof into the installer invocation. Windows, macOS, and Linux commands must therefore preflight the exact platform artifact and avoid raw token process - arguments. + arguments. Windows generated commands must pass boolean installer intent + through environment variables and invoke `install.ps1` through child + PowerShell `-File` calls so Windows PowerShell argument binding cannot + reinterpret copied `$true` values as strings or let preflight-only returns + abort the parent wrapper before install. 29. `frontend-modern/src/utils/apiTokenPresentation.ts` shared with `security-privacy`: the API token presentation helper is both a security/privacy control surface and a canonical API token management boundary. It owns the operator-facing Docker / Podman token vocabulary used by API Access, token presets, usage summaries, and revoke warnings. diff --git a/docs/release-control/v6/internal/subsystems/deployment-installability.md b/docs/release-control/v6/internal/subsystems/deployment-installability.md index 68a2ec256..7d7117585 100644 --- a/docs/release-control/v6/internal/subsystems/deployment-installability.md +++ b/docs/release-control/v6/internal/subsystems/deployment-installability.md @@ -1353,6 +1353,10 @@ when `PULSE_INSECURE_SKIP_VERIFY` is enabled, `scripts/install.ps1` must use that relaxed certificate policy for its own agent download and uninstall deregistration requests so self-signed deployments do not fail before the persisted Windows service ever starts. +Windows installability proof must also verify the installed service's local +readiness endpoint, not just SCM `Running` state: the Windows service runtime +must start the shared Pulse Agent health/readiness server so `/readyz` can prove +the agent modules initialized after install. Copied PowerShell uninstall commands must preserve that same `PULSE_INSECURE_SKIP_VERIFY` setting so the governed deregistration request can still reach self-signed Pulse deployments during removal. diff --git a/frontend-modern/src/utils/__tests__/agentInstallCommand.test.ts b/frontend-modern/src/utils/__tests__/agentInstallCommand.test.ts index 12ef420fa..4387a795e 100644 --- a/frontend-modern/src/utils/__tests__/agentInstallCommand.test.ts +++ b/frontend-modern/src/utils/__tests__/agentInstallCommand.test.ts @@ -134,12 +134,22 @@ describe('agentInstallCommand', () => { '[System.IO.File]::WriteAllText($pulseTokenFile, "token-123", [System.Text.Encoding]::ASCII)', ); expect(command).toContain('-TokenFile $pulseTokenFile'); - expect(command).toContain('-PreflightOnly $true'); - expect(command).toContain('-Output "json"'); - expect(command).toContain('-NonInteractive $true'); - expect(command).toContain('-Insecure $true'); - expect(command).toContain('-CACertPath "C:\\Pulse\\custom-ca.cer"'); + expect(command).toContain('$env:PULSE_PREFLIGHT_ONLY="true"'); + expect(command).toContain('$env:PULSE_OUTPUT="json"'); + expect(command).toContain('$env:PULSE_NON_INTERACTIVE="true"'); + expect(command).toContain('$env:PULSE_INSECURE_SKIP_VERIFY="true"'); + expect(command).toContain('$env:PULSE_CACERT="C:\\Pulse\\custom-ca.cer"'); expect(command).toContain('Invoke-WebRequest -Uri $pulseScriptUrl -UseBasicParsing -OutFile $pulseInstallScript'); + expect(command).toContain( + '& $pulsePowerShell -NoProfile -ExecutionPolicy Bypass -File $pulseInstallScript -Url', + ); + expect(command).toContain('if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }'); + expect(command).toContain( + 'Remove-Item Env:PULSE_PREFLIGHT_ONLY -ErrorAction SilentlyContinue', + ); + expect(command).not.toContain('-PreflightOnly $true'); + expect(command).not.toContain('-NonInteractive $true'); + expect(command).not.toContain('-Insecure $true'); expect(command).not.toContain('$env:PULSE_TOKEN='); }); @@ -152,8 +162,10 @@ describe('agentInstallCommand', () => { expect(command).toContain('$pulseScriptUrl="https://pulse.example/install.ps1"'); expect(command).not.toContain('$env:PULSE_TOKEN='); expect(command).not.toContain('-TokenFile $pulseTokenFile'); - expect(command).toContain('-PreflightOnly $true'); - expect(command).toContain('-NonInteractive $true'); + expect(command).toContain('$env:PULSE_PREFLIGHT_ONLY="true"'); + expect(command).toContain('$env:PULSE_NON_INTERACTIVE="true"'); + expect(command).not.toContain('-PreflightOnly $true'); + expect(command).not.toContain('-NonInteractive $true'); }); it('fails closed when the install endpoint URL is blank', () => { @@ -199,6 +211,9 @@ describe('agentInstallCommand', () => { expect(command).toContain('$env:PULSE_ENABLE_PROXMOX="true"'); expect(command).toContain('$env:PULSE_PROXMOX_TYPE="pbs"'); expect(command).toContain('$env:PULSE_ENABLE_COMMANDS="true"'); + expect(command).toContain( + '& $pulsePowerShell -NoProfile -ExecutionPolicy Bypass -File $pulseInstallScript -Url', + ); }); it('passes insecure runtime continuity for plain-http Windows installs', () => { @@ -208,8 +223,10 @@ describe('agentInstallCommand', () => { }); expect(command).toContain('-Url "http://pulse.example:7655"'); - expect(command).toContain('-Insecure $true'); - expect(command).toContain('-PreflightOnly $true'); + expect(command).toContain('$env:PULSE_INSECURE_SKIP_VERIFY="true"'); + expect(command).toContain('$env:PULSE_PREFLIGHT_ONLY="true"'); + expect(command).not.toContain('-Insecure $true'); + expect(command).not.toContain('-PreflightOnly $true'); expect(command).not.toContain('$env:PULSE_TOKEN='); }); }); diff --git a/frontend-modern/src/utils/agentInstallCommand.ts b/frontend-modern/src/utils/agentInstallCommand.ts index 1c1c7c6e2..7d5ed5e24 100644 --- a/frontend-modern/src/utils/agentInstallCommand.ts +++ b/frontend-modern/src/utils/agentInstallCommand.ts @@ -148,11 +148,7 @@ export const buildWindowsAgentInstallCommand = ({ const installArgs = [ `-Url "${powerShellQuote(normalizedBaseUrl)}"`, ...(normalizedToken ? ['-TokenFile $pulseTokenFile'] : []), - ...(installRequiresInsecure ? ['-Insecure $true'] : []), - ...(normalizedCaCertPath ? [`-CACertPath "${powerShellQuote(normalizedCaCertPath)}"`] : []), - '-NonInteractive $true', ]; - const preflightArgs = [...installArgs, '-PreflightOnly $true', '-Output "json"']; const customTrustFetch = installerFetchRequiresCustomTrust ? `$pulseCustomCa=$null; if (-not [string]::IsNullOrWhiteSpace($pulseCaCertPath)) { $pulseCustomCaBytes=[System.IO.File]::ReadAllBytes($pulseCaCertPath); $pulseCustomCaText=[System.Text.Encoding]::ASCII.GetString($pulseCustomCaBytes); if ($pulseCustomCaText.Contains("-----BEGIN CERTIFICATE-----")) { $pulseCustomCa=[System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPem($pulseCustomCaText) } else { $pulseCustomCa=[System.Security.Cryptography.X509Certificates.X509Certificate2]::new($pulseCustomCaBytes) } }; $pulsePrev=[System.Net.ServicePointManager]::ServerCertificateValidationCallback; try { [System.Net.ServicePointManager]::ServerCertificateValidationCallback={ param($sender,$certificate,$chain,$sslPolicyErrors) if ($pulseAllowInsecure) { return $true }; if ($null -eq $pulseCustomCa) { return $sslPolicyErrors -eq [System.Net.Security.SslPolicyErrors]::None }; if ($null -eq $certificate) { return $false }; $pulseChain=[System.Security.Cryptography.X509Certificates.X509Chain]::new(); $pulseChain.ChainPolicy.RevocationMode=[System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck; $null=$pulseChain.ChainPolicy.ExtraStore.Add($pulseCustomCa); $null=$pulseChain.Build($certificate); foreach ($pulseElement in $pulseChain.ChainElements) { if ($pulseElement.Certificate.Thumbprint -eq $pulseCustomCa.Thumbprint) { return $true } }; return $false }; Invoke-WebRequest -Uri $pulseScriptUrl -UseBasicParsing -OutFile $pulseInstallScript } finally { [System.Net.ServicePointManager]::ServerCertificateValidationCallback=$pulsePrev }` : `Invoke-WebRequest -Uri $pulseScriptUrl -UseBasicParsing -OutFile $pulseInstallScript`; @@ -163,6 +159,11 @@ export const buildWindowsAgentInstallCommand = ({ const extraEnvBootstrap = normalizedExtraEnvAssignments.length ? `${normalizedExtraEnvAssignments.join('; ')}; ` : ''; + const runtimeEnvBootstrap = + `${installRequiresInsecure ? '$env:PULSE_INSECURE_SKIP_VERIFY="true"; ' : ''}` + + `${normalizedCaCertPath ? `$env:PULSE_CACERT="${powerShellQuote(normalizedCaCertPath)}"; ` : ''}` + + '$env:PULSE_NON_INTERACTIVE="true"; '; + const installCommand = `& $pulsePowerShell -NoProfile -ExecutionPolicy Bypass -File $pulseInstallScript ${installArgs.join(' ')}`; return ( `& { $ErrorActionPreference="Stop"; ` + @@ -176,10 +177,13 @@ export const buildWindowsAgentInstallCommand = ({ `${customTrustFetch}; ` + `${tokenBootstrap}` + `${extraEnvBootstrap}` + + `${runtimeEnvBootstrap}` + `$pulsePowerShell=(Get-Process -Id $PID).Path; ` + - `& $pulsePowerShell -NoProfile -ExecutionPolicy Bypass -File $pulseInstallScript ${preflightArgs.join(' ')}; ` + + `$env:PULSE_PREFLIGHT_ONLY="true"; $env:PULSE_OUTPUT="json"; ` + + `${installCommand}; ` + `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }; ` + - `& $pulsePowerShell -NoProfile -ExecutionPolicy Bypass -File $pulseInstallScript ${installArgs.join(' ')}; ` + + `Remove-Item Env:PULSE_PREFLIGHT_ONLY -ErrorAction SilentlyContinue; Remove-Item Env:PULSE_OUTPUT -ErrorAction SilentlyContinue; ` + + `${installCommand}; ` + `if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } ` + `} finally { Remove-Item -LiteralPath $pulseTmp -Recurse -Force -ErrorAction SilentlyContinue } }` ); diff --git a/scripts/install.ps1 b/scripts/install.ps1 index fa6998959..e4eafc55f 100644 --- a/scripts/install.ps1 +++ b/scripts/install.ps1 @@ -152,6 +152,45 @@ function Write-InstallerEvent { Write-Host $Message } +function Get-ResponseHeaderValue { + param( + $Headers, + [string]$Name + ) + + if ($null -eq $Headers -or [string]::IsNullOrWhiteSpace($Name)) { + return "" + } + + try { + $value = $Headers[$Name] + if ($null -ne $value) { + if ($value -is [array]) { + return [string]$value[0] + } + return [string]$value + } + } catch { + # Fall back to case-insensitive enumeration below. + } + + try { + foreach ($key in $Headers.Keys) { + if ([string]::Equals([string]$key, $Name, [System.StringComparison]::OrdinalIgnoreCase)) { + $value = $Headers[$key] + if ($value -is [array]) { + return [string]$value[0] + } + return [string]$value + } + } + } catch { + return "" + } + + return "" +} + function Test-ValidUrl { param([string]$TestUrl) if ([string]::IsNullOrWhiteSpace($TestUrl)) { return $false } @@ -482,9 +521,8 @@ if ($Uninstall) { $lookupArgs.Headers = @{ "X-API-Token" = $Token } } - $lookupResult = $null - Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $customCaCertificate -Action { - $lookupResult = Invoke-RestMethod @lookupArgs + $lookupResult = Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $customCaCertificate -Action { + Invoke-RestMethod @lookupArgs } if ($lookupResult -and $lookupResult.agent -and -not [string]::IsNullOrWhiteSpace($lookupResult.agent.id)) { $detectedAgentId = $lookupResult.agent.id.Trim() @@ -622,12 +660,11 @@ function Invoke-AgentDownloadPreflight { param([string]$Uri) try { - $preflightResponse = $null - Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $CustomCaCertificate -Action { - $preflightResponse = Invoke-WebRequest -Uri $Uri -Method Head -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop + $preflightResponse = Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $CustomCaCertificate -Action { + Invoke-WebRequest -Uri $Uri -Method Head -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop } - $checksum = $preflightResponse.Headers["X-Checksum-Sha256"] + $checksum = Get-ResponseHeaderValue -Headers $preflightResponse.Headers -Name "X-Checksum-Sha256" if ([string]::IsNullOrWhiteSpace($checksum)) { Write-InstallerEvent -Phase "preflight" -Code "agent_download_checksum_missing" -Message "Agent download exists but did not include a checksum header: $Uri" -ExitCode 12 Exit 12 @@ -662,24 +699,57 @@ try { # Configure TLS 1.2 minimum [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 -bor [Net.SecurityProtocolType]::Tls13 - Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $CustomCaCertificate -Action { + $downloadPreflightResponse = Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $CustomCaCertificate -Action { + Invoke-WebRequest -Uri $DownloadUrl -Method Head -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop + } + $serverChecksum = Get-ResponseHeaderValue -Headers $downloadPreflightResponse.Headers -Name "X-Checksum-Sha256" + $serverSshSignature = Get-ResponseHeaderValue -Headers $downloadPreflightResponse.Headers -Name $InstallerSignatureHeaderName + if ([string]::IsNullOrWhiteSpace($serverChecksum)) { + Cleanup + Show-Error "Server did not provide checksum header; refusing install." + Exit 1 + } + + $downloadMetadata = Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $CustomCaCertificate -Action { # Download with timeout $webClient = New-Object System.Net.WebClient - $webClient.Headers.Add("User-Agent", "PulseInstaller/1.0") + try { + $webClient.Headers.Add("User-Agent", "PulseInstaller/1.0") - # Set up async download with timeout - $downloadTask = $webClient.DownloadFileTaskAsync($DownloadUrl, $TempPath) - if (-not $downloadTask.Wait($DownloadTimeoutSec * 1000)) { - $webClient.CancelAsync() - throw "Download timed out after $DownloadTimeoutSec seconds" - } - if ($downloadTask.IsFaulted) { - throw $downloadTask.Exception.InnerException - } + # Set up async download with timeout + $downloadTask = $webClient.DownloadFileTaskAsync($DownloadUrl, $TempPath) + if (-not $downloadTask.Wait($DownloadTimeoutSec * 1000)) { + $webClient.CancelAsync() + throw "Download timed out after $DownloadTimeoutSec seconds" + } + if ($downloadTask.IsFaulted) { + throw $downloadTask.Exception.InnerException + } - # Get checksum from server response headers if available - $serverChecksum = $webClient.ResponseHeaders["X-Checksum-Sha256"] - $serverSshSignature = $webClient.ResponseHeaders[$InstallerSignatureHeaderName] + # Prefer the same-download headers if Windows exposes them; otherwise + # retain the HEAD metadata captured immediately before download. + $downloadChecksum = Get-ResponseHeaderValue -Headers $webClient.ResponseHeaders -Name "X-Checksum-Sha256" + $downloadSshSignature = Get-ResponseHeaderValue -Headers $webClient.ResponseHeaders -Name $InstallerSignatureHeaderName + if (-not [string]::IsNullOrWhiteSpace($downloadChecksum)) { + $serverChecksum = $downloadChecksum + } + if (-not [string]::IsNullOrWhiteSpace($downloadSshSignature)) { + $serverSshSignature = $downloadSshSignature + } + + @{ + Checksum = $serverChecksum + SshSignature = $serverSshSignature + } + } finally { + $webClient.Dispose() + } + } + if ($downloadMetadata -and -not [string]::IsNullOrWhiteSpace($downloadMetadata.Checksum)) { + $serverChecksum = $downloadMetadata.Checksum + } + if ($downloadMetadata -and -not [string]::IsNullOrWhiteSpace($downloadMetadata.SshSignature)) { + $serverSshSignature = $downloadMetadata.SshSignature } } catch { @@ -691,8 +761,6 @@ try { Read-Host } Exit 1 -} finally { - if ($webClient) { $webClient.Dispose() } } # --- Binary Verification --- diff --git a/scripts/installtests/install_ps1_test.go b/scripts/installtests/install_ps1_test.go index 7f1a939ae..5095d99b7 100644 --- a/scripts/installtests/install_ps1_test.go +++ b/scripts/installtests/install_ps1_test.go @@ -171,9 +171,15 @@ func TestInstallPS1SupportsDownloadPreflightBeforeAdministratorInstall(t *testin `[bool]$NonInteractive = $false`, `if (-not $isAdmin -and -not $PreflightOnly) {`, `function Write-InstallerEvent {`, + `function Get-ResponseHeaderValue {`, `function Invoke-AgentDownloadPreflight {`, `Invoke-WebRequest -Uri $Uri -Method Head -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop`, - `$checksum = $preflightResponse.Headers["X-Checksum-Sha256"]`, + `$checksum = Get-ResponseHeaderValue -Headers $preflightResponse.Headers -Name "X-Checksum-Sha256"`, + `$downloadPreflightResponse = Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $CustomCaCertificate -Action {`, + `Invoke-WebRequest -Uri $DownloadUrl -Method Head -UseBasicParsing -TimeoutSec 10 -ErrorAction Stop`, + `$serverChecksum = Get-ResponseHeaderValue -Headers $downloadPreflightResponse.Headers -Name "X-Checksum-Sha256"`, + `$downloadMetadata = Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $CustomCaCertificate -Action {`, + `$downloadChecksum = Get-ResponseHeaderValue -Headers $webClient.ResponseHeaders -Name "X-Checksum-Sha256"`, `agent_download_checksum_missing`, `agent_download_available`, `agent_download_unavailable`, @@ -217,7 +223,8 @@ func TestInstallPS1UsesHostnameLookupForUninstallFallback(t *testing.T) { `$lookupHostname = $Hostname`, `$lookupHostname = $env:COMPUTERNAME`, `Uri = "$Url/api/agents/agent/lookup?hostname=$([System.Uri]::EscapeDataString($lookupHostname))"`, - `$lookupResult = Invoke-RestMethod @lookupArgs`, + `$lookupResult = Invoke-WithOptionalInsecureTls -AllowInsecure $Insecure -CustomCaCertificate $customCaCertificate -Action {`, + `Invoke-RestMethod @lookupArgs`, } for _, needle := range required { if !strings.Contains(script, needle) { @@ -313,7 +320,7 @@ func TestInstallPS1RequiresPinnedSignatureVerificationForReleaseDownloads(t *tes `function Test-HasPinnedInstallerSignatureKey {`, `function Invoke-InstallerSignatureVerification {`, `Get-Command ssh-keygen.exe -ErrorAction SilentlyContinue`, - `$serverSshSignature = $webClient.ResponseHeaders[$InstallerSignatureHeaderName]`, + `$serverSshSignature = Get-ResponseHeaderValue -Headers $downloadPreflightResponse.Headers -Name $InstallerSignatureHeaderName`, `Show-Error "Server did not provide checksum header; refusing signed install."`, `Show-Error "Server did not provide SSH signature header; refusing signed install."`, `Invoke-InstallerSignatureVerification -FilePath $TempPath -SignatureHeader $serverSshSignature`, diff --git a/scripts/release_control/subsystem_lookup_test.py b/scripts/release_control/subsystem_lookup_test.py index 526761334..e8592626a 100644 --- a/scripts/release_control/subsystem_lookup_test.py +++ b/scripts/release_control/subsystem_lookup_test.py @@ -3529,7 +3529,7 @@ class SubsystemLookupTest(unittest.TestCase): { "heading": "## Shared Boundaries", "path": "internal/api/access_control_handlers.go", - "line": 274, + "line": 278, "heading_line": 112, } ],