fix(agent): trust public CA certs when downloading from Manage Server (v1.3.7) #1

Merged
amiryahaya merged 2 commits from fix/agent-download-tls-public-ca into main 2026-07-02 20:35:42 +02:00
Owner

Summary

Both agent/get.ps1 and agent/get.sh pinned the binary-download connection (manage_server_url, typically HTTPS on 443) exclusively to the enrollment bundle's private CA. That's correct for the gateway connection (always self-signed by design — agents connect directly, no reverse proxy), but wrong for the admin/download connection: on a VPS deployment fronted by a reverse proxy with a real cert (e.g. Caddy + Let's Encrypt), that cert's chain never matches the bundle CA's thumbprint, so the download was rejected as untrusted even though it's a completely valid public certificate — blocking every VPS-with-a-domain install at the "downloading triton-agent from Manage Server" step.

  • get.ps1: the SslStream validation callback now falls back to Windows' own default trust verdict ($errors -eq [SslPolicyErrors]::None) when the bundle-CA pin doesn't match, instead of unconditionally returning $false.
  • get.sh: curl --cacert replaces curl's default CA bundle rather than adding to it, so there's no single-invocation equivalent of "trust this CA too." Added download_from_manage_server(), which tries normal system trust first and falls back to --cacert with the bundle CA — used at all 4 call sites (install, upgrade, --scanner-only, post-install scanner fetch).

Self-signed/on-prem deployments are unaffected — the bundle-CA pin still matches first in that case; the new fallback only engages when the pin doesn't match.

Test plan

  • pwsh syntax parse of get.ps1 clean, PSScriptAnalyzer zero Error-severity findings (pre-existing Write-Host/unused-delegate-param warnings only)
  • bash -n syntax check of get.sh clean, shellcheck — zero new findings (same 14 pre-existing info-level SC2059 notes before/after)
  • Extracted both fixed functions verbatim into isolated test harnesses and exercised 3 cases against each:
    • real forgejo.primatekun.tech connection with an unrelated bundle CA → succeeds via public-trust fallback (the fix)
    • synthetic bundle-CA-signed leaf cert, pinned against the matching bundle CA → succeeds via the pin (on-prem case, regression check)
    • synthetic self-signed leaf cert against an unrelated/wrong CA → correctly rejected (confirms no security regression — doesn't trust arbitrary certs)
  • Live end-to-end verification against the reporter's VPS (csi.mcert.tech) pending — this PR unblocks that

🤖 Generated with Claude Code

## Summary Both `agent/get.ps1` and `agent/get.sh` pinned the binary-download connection (`manage_server_url`, typically HTTPS on 443) exclusively to the enrollment bundle's private CA. That's correct for the gateway connection (always self-signed by design — agents connect directly, no reverse proxy), but wrong for the admin/download connection: on a VPS deployment fronted by a reverse proxy with a real cert (e.g. Caddy + Let's Encrypt), that cert's chain never matches the bundle CA's thumbprint, so the download was rejected as untrusted even though it's a completely valid public certificate — blocking every VPS-with-a-domain install at the "downloading triton-agent from Manage Server" step. - `get.ps1`: the `SslStream` validation callback now falls back to Windows' own default trust verdict (`$errors -eq [SslPolicyErrors]::None`) when the bundle-CA pin doesn't match, instead of unconditionally returning `$false`. - `get.sh`: `curl --cacert` replaces curl's default CA bundle rather than adding to it, so there's no single-invocation equivalent of "trust this CA too." Added `download_from_manage_server()`, which tries normal system trust first and falls back to `--cacert` with the bundle CA — used at all 4 call sites (install, upgrade, `--scanner-only`, post-install scanner fetch). Self-signed/on-prem deployments are unaffected — the bundle-CA pin still matches first in that case; the new fallback only engages when the pin doesn't match. ## Test plan - [x] `pwsh` syntax parse of `get.ps1` clean, `PSScriptAnalyzer` zero Error-severity findings (pre-existing Write-Host/unused-delegate-param warnings only) - [x] `bash -n` syntax check of `get.sh` clean, `shellcheck` — zero new findings (same 14 pre-existing info-level SC2059 notes before/after) - [x] Extracted both fixed functions verbatim into isolated test harnesses and exercised 3 cases against each: - real `forgejo.primatekun.tech` connection with an unrelated bundle CA → succeeds via public-trust fallback (the fix) - synthetic bundle-CA-signed leaf cert, pinned against the matching bundle CA → succeeds via the pin (on-prem case, regression check) - synthetic self-signed leaf cert against an unrelated/wrong CA → correctly rejected (confirms no security regression — doesn't trust arbitrary certs) - [ ] Live end-to-end verification against the reporter's VPS (csi.mcert.tech) pending — this PR unblocks that 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Both get.ps1 and get.sh pinned the binary-download connection to the
bundle's private CA exclusively — correct for the gateway connection
(always self-signed) but wrong for the manage_server_url download, which
on a VPS deployment behind a reverse proxy (e.g. Caddy + Let's Encrypt)
presents a real publicly-trusted cert. That cert's chain never matches
the bundle CA's thumbprint, so the download was rejected as untrusted
even though it's valid — blocking every VPS-with-a-domain install.

get.ps1: the SslStream validation callback now falls back to Windows'
own default trust verdict ($errors -eq [SslPolicyErrors]::None) when
the bundle-CA pin doesn't match, instead of returning $false outright.

get.sh: curl's --cacert replaces its default CA bundle rather than
adding to it, so there's no single-invocation equivalent. Added
download_from_manage_server(), which tries normal system trust first
and falls back to --cacert with the bundle CA — used at all 4 call
sites (install, upgrade, scanner-only, post-install scanner fetch).

Self-signed/on-prem deployments are unaffected (bundle-CA pin still
matches first); the new fallback only ever engages when the pin fails.

Verified against a real forgejo.primatekun.tech connection (public-CA
fallback), a synthetic bundle-CA-signed cert (pin match), and a
synthetic unrelated-CA cert (must still be rejected) for both scripts.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Per code review of the previous commit: extracts the real production
logic (via AST for get.ps1, via sed for get.sh) rather than a re-typed
copy, so these actually guard the fixed code, not an approximation of it.

get.ps1/Test-Download.ps1: 4 cases against the SslStream validation
callback — on-prem bundle-CA pin match, VPS public-cert fallback (using
a real forgejo.primatekun.tech connection), an untrusted+unpinned cert
correctly rejected, and the pre-existing no-CaCert-configured behavior.

get.sh/test-download.sh: 5 cases against download_from_manage_server()
via a mock curl — public-trust success, pinned-fallback success, both-
fail closes, missing ca_file closes, nonexistent ca_file path closes.

Run manually: bash agent/test-download.sh / pwsh -File agent/Test-Download.ps1
(repo has no existing test infra/CI to wire these into yet.)

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
primatekuntech/triton-install!1
No description provided.