Skip to content

fix: trust the certificates named by SSL_CERT_FILE on Windows and macOS - #141

Merged
bojanz merged 3 commits into
mainfrom
fix/go-ssl-cert-file
Aug 7, 2026
Merged

fix: trust the certificates named by SSL_CERT_FILE on Windows and macOS#141
bojanz merged 3 commits into
mainfrom
fix/go-ssl-cert-file

Conversation

@pjcdawkins

Copy link
Copy Markdown
Contributor

Related to #110. Independent of #139 and #140, which deal with the Windows certificate store.

The problem

The legacy CLI trusts the certificates named by SSL_CERT_FILE on every platform, because Composer\CaBundle checks that variable first when it resolves a bundle. The Go part of the CLI only trusts it on Unix: Go reads SSL_CERT_FILE in crypto/x509/root_unix.go, and on Windows and macOS it verifies through the operating system instead.

So for someone whose organization gives them a CA bundle as a file, upsun environment:list works and upsun init does not, on the same machine, for the same reason — with no hint that the two halves of the CLI resolve certificates differently.

The change

certs.UseEnvCertFile() points the default HTTP transport at the file, on the platforms where Go does not read the variable itself. Every request the Go part makes goes through http.DefaultTransport — the API client, the update check, the config fetch, and init — so one call at startup covers all of them.

The file replaces the system certificates rather than adding to them. That matches Go's own behavior on Unix and Composer\CaBundle's everywhere, so the CLI resolves certificates the same way whichever part serves the command.

An unusable file is a warning on stderr rather than a fatal error, and the system certificates are used, which is what both Go and Composer\CaBundle do with one.

Tests

internal/certs serves HTTPS with a certificate the system does not trust, and checks it is rejected with no file set and accepted with the file set. The error cases cover an unreadable path and a file holding no certificates.

Written by Claude Code.

Go reads that variable only on Unix; elsewhere it verifies through the
operating system. The legacy CLI reads it on every platform, through
Composer\CaBundle, so the two parts of the CLI disagreed about which
certificates to trust, and a command implemented in Go failed where the
same command implemented in PHP worked.

An unusable file is reported as a warning rather than being fatal, and
the system certificates are used, as before.

Written by Claude Code.
Copilot AI review requested due to automatic review settings July 30, 2026 00:45
@upsun-dispatch

upsun-dispatch Bot commented Jul 30, 2026

Copy link
Copy Markdown

📋 PR Summary

Adds an internal/certs package whose UseEnvCertFile() points http.DefaultTransport at the certificate bundle named by SSL_CERT_FILE on Windows and macOS, where Go's own verification (unlike Unix) ignores that variable. cmd/platform/main.go calls it once at startup, treating an unusable file as a non-fatal stderr warning that falls back to the system certificates. This aligns the Go half of the CLI with the legacy PHP half, which already trusts the file everywhere via Composer\CaBundle.

Changes
Layer / File(s) Summary
cert resolution
internal/certs/certs.go New package that replaces the default transport's root CAs with the certificates in SSL_CERT_FILE, only on platforms where Go does not read the variable itself.
internal/certs/certs_test.go Tests that a server signed by a cert in the file is trusted, an empty path falls back to system certs, and unreadable/empty files error.
startup wiring
cmd/platform/main.go Calls certs.UseEnvCertFile() after config load, warning on stderr rather than aborting when it fails.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aligns TLS certificate trust behavior between the Go and legacy PHP halves of the Upsun CLI by honoring SSL_CERT_FILE on Windows and macOS (where Go does not read it automatically), so commands implemented in Go (e.g., init) behave consistently with legacy commands.

Changes:

  • Add internal/certs with UseEnvCertFile() to configure the default HTTP transport to trust the PEM bundle referenced by SSL_CERT_FILE on Windows/macOS.
  • Invoke certs.UseEnvCertFile() early in cmd/platform/main.go, warning (non-fatal) if the file is unreadable or invalid.
  • Add tests in internal/certs covering trust/no-trust behavior and error cases for invalid/missing PEM input.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
internal/certs/certs.go Introduces transport configuration to use SSL_CERT_FILE roots on platforms where Go doesn’t read it.
internal/certs/certs_test.go Adds unit tests validating trust behavior and error handling for the cert-file logic.
cmd/platform/main.go Wires the cert-file logic into CLI startup so Go HTTP requests follow the same trust model as legacy PHP.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/certs/certs.go
@bojanz
bojanz self-requested a review August 7, 2026 16:45

@upsun-dispatch upsun-dispatch Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Reviewed — No new blocking findings · 1 minor point

🔍 Full review · 3 files reviewed

🔵 Minor points

Not blocking, and no threads opened for these.

  • internal/certs/certs.go:35 — UseEnvCertFile honours only SSL_CERT_FILE. Composer\CaBundle (the legacy CLI's resolver) and Go on Unix both also consult SSL_CERT_DIR. On Windows/macOS a user whose organization sets only SSL_CERT_DIR still gets the legacy-vs-Go divergence that #110 describes: the legacy half trusts the directory bundle while the Go half falls back to the system store, so environment:list works and init fails on the same machine.
Review details
  • Commit: 5c0219e
  • Model: claude-opus-4-8
  • Panel: security · correctness · robustness · design

@bojanz
bojanz merged commit 8ec6914 into main Aug 7, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants