mirror of
https://github.com/moby/moby.git
synced 2026-01-11 10:41:43 +00:00
client: do not modify user-provided HTTP client
The http.Client passed into client.WithHTTPClient() is modified by the constructor in-place: the value of its Transport field is mutated and wrapped in an OpenTelemetry decorator. This can lead to very surprising behaviour when a second client is constructed reusing the same http.Client value. If the http.Client is configured for TLS, the second client will fail to detect that and will incorrectly dial the Engine API socket as cleartext HTTP. Copy the provided http.Client so our modifications don't leak out to unexpected places. Signed-off-by: Cory Snider <csnider@mirantis.com>
This commit is contained in:
@@ -293,6 +293,11 @@ linters:
|
|||||||
linters:
|
linters:
|
||||||
- gosec
|
- gosec
|
||||||
|
|
||||||
|
- text: "^G402: " # Look for bad TLS connection settings
|
||||||
|
source: "cmpopts\\.Ignore"
|
||||||
|
linters:
|
||||||
|
- gosec
|
||||||
|
|
||||||
# FIXME: ignoring unused assigns to ctx for now; too many hits in libnetwork/xxx functions that setup traces
|
# FIXME: ignoring unused assigns to ctx for now; too many hits in libnetwork/xxx functions that setup traces
|
||||||
- text: "assigned to ctx, but never used afterwards"
|
- text: "assigned to ctx, but never used afterwards"
|
||||||
linters:
|
linters:
|
||||||
|
|||||||
@@ -149,7 +149,16 @@ func WithHostFromEnv() Opt {
|
|||||||
func WithHTTPClient(client *http.Client) Opt {
|
func WithHTTPClient(client *http.Client) Opt {
|
||||||
return func(c *clientConfig) error {
|
return func(c *clientConfig) error {
|
||||||
if client != nil {
|
if client != nil {
|
||||||
c.client = client
|
// Make a clone of client so modifications do not affect
|
||||||
|
// the caller's client. Clone here instead of in New()
|
||||||
|
// as other options (WithHost) also mutate c.client.
|
||||||
|
// Cloned clients share the same CookieJar as the
|
||||||
|
// original.
|
||||||
|
hc := *client
|
||||||
|
if ht, ok := hc.Transport.(*http.Transport); ok {
|
||||||
|
hc.Transport = ht.Clone()
|
||||||
|
}
|
||||||
|
c.client = &hc
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/tls"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/cookiejar"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
)
|
)
|
||||||
@@ -367,3 +370,23 @@ func TestWithUserAgent(t *testing.T) {
|
|||||||
assert.NilError(t, c.Close())
|
assert.NilError(t, c.Close())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWithHTTPClient(t *testing.T) {
|
||||||
|
cookieJar, err := cookiejar.New(nil)
|
||||||
|
assert.NilError(t, err)
|
||||||
|
pristineHTTPClient := func() *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Timeout: 42 * time.Second,
|
||||||
|
Jar: cookieJar,
|
||||||
|
Transport: &http.Transport{
|
||||||
|
TLSClientConfig: &tls.Config{ServerName: "example.com", MinVersion: tls.VersionTLS12},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hc := pristineHTTPClient()
|
||||||
|
_, err = New(WithHTTPClient(hc), WithHost("tcp://example.com:443"))
|
||||||
|
assert.NilError(t, err)
|
||||||
|
assert.DeepEqual(t, hc, pristineHTTPClient(),
|
||||||
|
cmpopts.IgnoreUnexported(http.Transport{}, tls.Config{}),
|
||||||
|
cmpopts.EquateComparable(&cookiejar.Jar{}))
|
||||||
|
}
|
||||||
|
|||||||
11
vendor/github.com/moby/moby/client/client_options.go
generated
vendored
11
vendor/github.com/moby/moby/client/client_options.go
generated
vendored
@@ -149,7 +149,16 @@ func WithHostFromEnv() Opt {
|
|||||||
func WithHTTPClient(client *http.Client) Opt {
|
func WithHTTPClient(client *http.Client) Opt {
|
||||||
return func(c *clientConfig) error {
|
return func(c *clientConfig) error {
|
||||||
if client != nil {
|
if client != nil {
|
||||||
c.client = client
|
// Make a clone of client so modifications do not affect
|
||||||
|
// the caller's client. Clone here instead of in New()
|
||||||
|
// as other options (WithHost) also mutate c.client.
|
||||||
|
// Cloned clients share the same CookieJar as the
|
||||||
|
// original.
|
||||||
|
hc := *client
|
||||||
|
if ht, ok := hc.Transport.(*http.Transport); ok {
|
||||||
|
hc.Transport = ht.Clone()
|
||||||
|
}
|
||||||
|
c.client = &hc
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user