fix(terraform): stop provider Configure mutating the global http.DefaultClient - #21
byron-lambda wants to merge 5 commits into
Conversation
…ultClient Configure aliased http.DefaultClient (a pointer, where a copy was clearly intended) and set its Transport, so every Configure rewrote the process-global client. In a normal provider process this is invisible, but when several provider instances share a process (in-process test frameworks such as terraform-plugin-testing) concurrent Configures race on the global under the race detector, and the last configured provider's transport leaks to any other http.DefaultClient user in the process. Build a fresh http.Client instead.
|
All contributors have signed the CLA ✍️ ✅ |
|
I have read the CLA Document and I hereby sign the CLA |
There was a problem hiding this comment.
All reported issues were addressed across 2 files
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
The tlsSkipVerify block mutated the TLS configuration of the transport it was handed, which is http.DefaultTransport, leaking InsecureSkipVerify to every other user of the global in the process. Clone it, mutate the clone, and hand the clone to the provider transport. Same class of bug as the http.DefaultClient aliasing this PR fixes; flagged by review.
There was a problem hiding this comment.
1 issue found across 2 files (changes from recent commits).
Confidence score: 4/5
- The committed review-SDK fixtures in
zSDKs/sdk-terraform/internal/provider/provider.goandzSDKs/terraform-provider-testing/internal/provider/provider.goremain stale and still mutatehttp.DefaultTransport, so the fixtures may not reflect the updated provider behavior—refresh them to matchtemplates/templates/terraform/includes/provider.ts.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="templates/templates/terraform/includes/provider.ts">
<violation number="1" location="templates/templates/terraform/includes/provider.ts:776">
P2: The committed review-SDK fixtures for this template change are stale. zSDKs/sdk-terraform/internal/provider/provider.go and zSDKs/terraform-provider-testing/internal/provider/provider.go still mutate http.DefaultTransport directly (no Clone) and still rewire http.DefaultClient, so they keep the exact global-mutation/data-race behavior this PR eliminates and don't demonstrate the intended generated-output change. Per CONTRIBUTING.md, the relevant zSDKs should be regenerated (e.g. TARGET=review make build-terraform) and included in this PR.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Fix all with cubic | Re-trigger cubic
| // Clone before mutating: the transport here is http.DefaultTransport, and | ||
| // writing its TLS configuration would leak InsecureSkipVerify to every | ||
| // other user of the global in the process. | ||
| result.push(`transport = transport.Clone()`); |
There was a problem hiding this comment.
P2: The committed review-SDK fixtures for this template change are stale. zSDKs/sdk-terraform/internal/provider/provider.go and zSDKs/terraform-provider-testing/internal/provider/provider.go still mutate http.DefaultTransport directly (no Clone) and still rewire http.DefaultClient, so they keep the exact global-mutation/data-race behavior this PR eliminates and don't demonstrate the intended generated-output change. Per CONTRIBUTING.md, the relevant zSDKs should be regenerated (e.g. TARGET=review make build-terraform) and included in this PR.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At templates/templates/terraform/includes/provider.ts, line 776:
<comment>The committed review-SDK fixtures for this template change are stale. zSDKs/sdk-terraform/internal/provider/provider.go and zSDKs/terraform-provider-testing/internal/provider/provider.go still mutate http.DefaultTransport directly (no Clone) and still rewire http.DefaultClient, so they keep the exact global-mutation/data-race behavior this PR eliminates and don't demonstrate the intended generated-output change. Per CONTRIBUTING.md, the relevant zSDKs should be regenerated (e.g. TARGET=review make build-terraform) and included in this PR.</comment>
<file context>
@@ -770,12 +770,17 @@ function templateProviderHTTPTransportTlsSkipVerify(
+ // Clone before mutating: the transport here is http.DefaultTransport, and
+ // writing its TLS configuration would leak InsecureSkipVerify to every
+ // other user of the global in the process.
+ result.push(`transport = transport.Clone()`);
result.push(`if transport.TLSClientConfig == nil {`);
result.push(`transport.TLSClientConfig = &tls.Config{}`);
</file context>
Rather than cloning inside the tlsSkipVerify block, give Configure ownership of its transport at the point http.DefaultTransport enters: the downstream helpers (headers, TLS skip verification) can then mutate the transport they are handed without touching process globals.
|
A possible follow-up worth a maintainer opinion: the transport clone could instead be |
TARGET=review output for terraform-provider-testing, regenerated so the fixture demonstrates the Configure change: a cloned transport wired into the provider transport opts and a fresh http.Client, with the process globals untouched.
Why
The generated terraform provider's
Configuredoes:http.DefaultClientis a*http.Client, so the first line aliases the package-global rather than copying it, and the second line rewrites the global'sTransporton every Configure. A normal provider process configures once, so this goes unnoticed, but it has two real consequences: any other user ofhttp.DefaultClientin the process silently inherits the provider's transport, and when several provider instances share one process (in-process testing with terraform-plugin-testing'sProtoV6ProviderFactories) concurrent Configures are a data race on the global, which fails any test run under-race.What changed
templates/templates/terraform/provider/provider.go.stmplbuilds a dedicated client instead:Review flagged the sibling bug in the same wiring: when a generator config opts into a
tlsSkipVerifyprovider attribute, the generated block mutates the TLS configuration of the transport it is handed, which washttp.DefaultTransport, leakingInsecureSkipVerifyto every other user of that global in the process. Rather than cloning at the mutation site, Configure now takes ownership of its transport at the single point the default enters: it cloneshttp.DefaultTransport(guarded, so a process that replaced the global with a custom RoundTripper keeps today's behavior) and wires the clone into the transport opts, so every downstream helper mutates state Configure owns. Each provider instance gets its own connection pool, the correct trade for never writing configuration into process globals.Generated output changes by a few lines in every terraform provider's
Configure. Behavior is identical for a single provider instance; the globalshttp.DefaultClientandhttp.DefaultTransportare simply no longer touched. Changeset added viamake changelog core terraform.Testing
Applied the same one-line change to the generated
provider.goof an existing Speakeasy-generated terraform provider and ran its terraform-plugin-testing e2e suite (17 resource lifecycle tests,t.Parallel, against a local test server) undergo test -race: the suite fails with a data race report onhttp.DefaultClient.Transportbefore the change and passes after it. For the transport clone, verified the emitted Go by reading the template output (the terraform target has no per-change test harness for this block). Did not runTARGET=review make build-terraform.Public-safety check
.claude/skills/public-repo-communication/SKILL.md).git diff --check.