2018-02-05 21:05:59 +00:00
|
|
|
package client // import "github.com/docker/docker/client"
|
2016-09-06 18:46:37 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
2018-04-19 22:30:59 +00:00
|
|
|
"context"
|
2020-12-10 23:13:37 +00:00
|
|
|
"errors"
|
2016-09-06 18:46:37 +00:00
|
|
|
"fmt"
|
2021-08-24 10:10:50 +00:00
|
|
|
"io"
|
2018-10-10 16:37:39 +00:00
|
|
|
"math/rand"
|
2016-09-06 18:46:37 +00:00
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
"testing"
|
2020-12-10 23:13:37 +00:00
|
|
|
"time"
|
2016-09-06 18:46:37 +00:00
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
2018-12-31 17:22:43 +00:00
|
|
|
"github.com/docker/docker/errdefs"
|
2020-02-07 13:39:24 +00:00
|
|
|
"gotest.tools/v3/assert"
|
|
|
|
is "gotest.tools/v3/assert/cmp"
|
2016-09-06 18:46:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// TestSetHostHeader should set fake host for local communications, set real host
|
|
|
|
// for normal communications.
|
|
|
|
func TestSetHostHeader(t *testing.T) {
|
2023-07-13 09:40:00 +00:00
|
|
|
const testEndpoint = "/test"
|
2016-09-06 18:46:37 +00:00
|
|
|
testCases := []struct {
|
|
|
|
host string
|
|
|
|
expectedHost string
|
|
|
|
expectedURLHost string
|
|
|
|
}{
|
|
|
|
{
|
2023-07-14 16:56:47 +00:00
|
|
|
host: "unix:///var/run/docker.sock",
|
client: define a "dummy" hostname to use for local connections
For local communications (npipe://, unix://), the hostname is not used,
but we need valid and meaningful hostname.
The current code used the client's `addr` as hostname in some cases, which
could contain the path for the unix-socket (`/var/run/docker.sock`), which
gets rejected by go1.20.6 and go1.19.11 because of a security fix for
[CVE-2023-29406 ][1], which was implemented in https://go.dev/issue/60374.
Prior versions go Go would clean the host header, and strip slashes in the
process, but go1.20.6 and go1.19.11 no longer do, and reject the host
header.
This patch introduces a `DummyHost` const, and uses this dummy host for
cases where we don't need an actual hostname.
Before this patch (using go1.20.6):
make GO_VERSION=1.20.6 TEST_FILTER=TestAttach test-integration
=== RUN TestAttachWithTTY
attach_test.go:46: assertion failed: error is not nil: http: invalid Host header
--- FAIL: TestAttachWithTTY (0.11s)
=== RUN TestAttachWithoutTTy
attach_test.go:46: assertion failed: error is not nil: http: invalid Host header
--- FAIL: TestAttachWithoutTTy (0.02s)
FAIL
With this patch applied:
make GO_VERSION=1.20.6 TEST_FILTER=TestAttach test-integration
INFO: Testing against a local daemon
=== RUN TestAttachWithTTY
--- PASS: TestAttachWithTTY (0.12s)
=== RUN TestAttachWithoutTTy
--- PASS: TestAttachWithoutTTy (0.02s)
PASS
[1]: https://github.com/advisories/GHSA-f8f7-69v5-w4vx
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-07-12 12:15:38 +00:00
|
|
|
expectedHost: DummyHost,
|
2023-07-14 16:56:47 +00:00
|
|
|
expectedURLHost: "/var/run/docker.sock",
|
2016-09-06 18:46:37 +00:00
|
|
|
},
|
|
|
|
{
|
2023-07-14 16:56:47 +00:00
|
|
|
host: "npipe:////./pipe/docker_engine",
|
client: define a "dummy" hostname to use for local connections
For local communications (npipe://, unix://), the hostname is not used,
but we need valid and meaningful hostname.
The current code used the client's `addr` as hostname in some cases, which
could contain the path for the unix-socket (`/var/run/docker.sock`), which
gets rejected by go1.20.6 and go1.19.11 because of a security fix for
[CVE-2023-29406 ][1], which was implemented in https://go.dev/issue/60374.
Prior versions go Go would clean the host header, and strip slashes in the
process, but go1.20.6 and go1.19.11 no longer do, and reject the host
header.
This patch introduces a `DummyHost` const, and uses this dummy host for
cases where we don't need an actual hostname.
Before this patch (using go1.20.6):
make GO_VERSION=1.20.6 TEST_FILTER=TestAttach test-integration
=== RUN TestAttachWithTTY
attach_test.go:46: assertion failed: error is not nil: http: invalid Host header
--- FAIL: TestAttachWithTTY (0.11s)
=== RUN TestAttachWithoutTTy
attach_test.go:46: assertion failed: error is not nil: http: invalid Host header
--- FAIL: TestAttachWithoutTTy (0.02s)
FAIL
With this patch applied:
make GO_VERSION=1.20.6 TEST_FILTER=TestAttach test-integration
INFO: Testing against a local daemon
=== RUN TestAttachWithTTY
--- PASS: TestAttachWithTTY (0.12s)
=== RUN TestAttachWithoutTTy
--- PASS: TestAttachWithoutTTy (0.02s)
PASS
[1]: https://github.com/advisories/GHSA-f8f7-69v5-w4vx
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-07-12 12:15:38 +00:00
|
|
|
expectedHost: DummyHost,
|
2023-07-14 16:56:47 +00:00
|
|
|
expectedURLHost: "//./pipe/docker_engine",
|
2016-09-06 18:46:37 +00:00
|
|
|
},
|
|
|
|
{
|
2023-07-14 16:56:47 +00:00
|
|
|
host: "tcp://0.0.0.0:4243",
|
|
|
|
expectedHost: "",
|
|
|
|
expectedURLHost: "0.0.0.0:4243",
|
2016-09-06 18:46:37 +00:00
|
|
|
},
|
|
|
|
{
|
2023-07-14 16:56:47 +00:00
|
|
|
host: "tcp://localhost:4243",
|
|
|
|
expectedHost: "",
|
|
|
|
expectedURLHost: "localhost:4243",
|
2016-09-06 18:46:37 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2023-07-13 09:28:13 +00:00
|
|
|
for _, tc := range testCases {
|
|
|
|
tc := tc
|
|
|
|
t.Run(tc.host, func(t *testing.T) {
|
|
|
|
hostURL, err := ParseHostURL(tc.host)
|
|
|
|
assert.Check(t, err)
|
2016-09-06 18:46:37 +00:00
|
|
|
|
2023-07-13 09:28:13 +00:00
|
|
|
client := &Client{
|
|
|
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
2023-07-13 09:40:00 +00:00
|
|
|
if !strings.HasPrefix(req.URL.Path, testEndpoint) {
|
|
|
|
return nil, fmt.Errorf("expected URL %q, got %q", testEndpoint, req.URL)
|
2023-07-13 09:28:13 +00:00
|
|
|
}
|
|
|
|
if req.Host != tc.expectedHost {
|
|
|
|
return nil, fmt.Errorf("wxpected host %q, got %q", tc.expectedHost, req.Host)
|
|
|
|
}
|
|
|
|
if req.URL.Host != tc.expectedURLHost {
|
|
|
|
return nil, fmt.Errorf("expected URL host %q, got %q", tc.expectedURLHost, req.URL.Host)
|
|
|
|
}
|
|
|
|
return &http.Response{
|
|
|
|
StatusCode: http.StatusOK,
|
|
|
|
Body: io.NopCloser(bytes.NewReader([]byte(""))),
|
|
|
|
}, nil
|
|
|
|
}),
|
2016-09-09 03:44:25 +00:00
|
|
|
|
2023-07-13 09:28:13 +00:00
|
|
|
proto: hostURL.Scheme,
|
|
|
|
addr: hostURL.Host,
|
|
|
|
basePath: hostURL.Path,
|
|
|
|
}
|
2016-09-06 18:46:37 +00:00
|
|
|
|
2023-07-13 09:40:00 +00:00
|
|
|
_, err = client.sendRequest(context.Background(), http.MethodGet, testEndpoint, nil, nil, nil)
|
2023-07-13 09:28:13 +00:00
|
|
|
assert.Check(t, err)
|
|
|
|
})
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestPlainTextError tests the server returning an error in plain text for
|
|
|
|
// backwards compatibility with API versions <1.24. All other tests use
|
|
|
|
// errors returned as JSON
|
|
|
|
func TestPlainTextError(t *testing.T) {
|
|
|
|
client := &Client{
|
2016-09-09 03:44:25 +00:00
|
|
|
client: newMockClient(plainTextErrorMock(http.StatusInternalServerError, "Server error")),
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
|
|
|
_, err := client.ContainerList(context.Background(), types.ContainerListOptions{})
|
2023-05-10 11:17:40 +00:00
|
|
|
assert.Check(t, is.ErrorType(err, errdefs.IsSystem))
|
2016-09-06 18:46:37 +00:00
|
|
|
}
|
2018-10-10 16:37:39 +00:00
|
|
|
|
|
|
|
func TestInfiniteError(t *testing.T) {
|
|
|
|
infinitR := rand.New(rand.NewSource(42))
|
|
|
|
client := &Client{
|
|
|
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
2023-07-13 09:40:00 +00:00
|
|
|
resp := &http.Response{
|
|
|
|
StatusCode: http.StatusInternalServerError,
|
|
|
|
Header: http.Header{},
|
|
|
|
Body: io.NopCloser(infinitR),
|
|
|
|
}
|
2018-10-10 16:37:39 +00:00
|
|
|
return resp, nil
|
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err := client.Ping(context.Background())
|
|
|
|
assert.Check(t, is.ErrorContains(err, "request returned Internal Server Error"))
|
|
|
|
}
|
2020-12-10 23:13:37 +00:00
|
|
|
|
|
|
|
func TestCanceledContext(t *testing.T) {
|
2023-07-13 09:40:00 +00:00
|
|
|
const testEndpoint = "/test"
|
2020-12-10 23:13:37 +00:00
|
|
|
|
|
|
|
client := &Client{
|
|
|
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
2023-07-13 09:40:00 +00:00
|
|
|
assert.Check(t, is.ErrorType(req.Context().Err(), context.Canceled))
|
|
|
|
return nil, context.Canceled
|
2020-12-10 23:13:37 +00:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
cancel()
|
|
|
|
|
2023-07-13 09:40:00 +00:00
|
|
|
_, err := client.sendRequest(ctx, http.MethodGet, testEndpoint, nil, nil, nil)
|
|
|
|
assert.Check(t, is.ErrorType(err, errdefs.IsCancelled))
|
|
|
|
assert.Check(t, errors.Is(err, context.Canceled))
|
2020-12-10 23:13:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func TestDeadlineExceededContext(t *testing.T) {
|
2023-07-13 09:40:00 +00:00
|
|
|
const testEndpoint = "/test"
|
2020-12-10 23:13:37 +00:00
|
|
|
|
|
|
|
client := &Client{
|
|
|
|
client: newMockClient(func(req *http.Request) (*http.Response, error) {
|
2023-07-13 09:40:00 +00:00
|
|
|
assert.Check(t, is.ErrorType(req.Context().Err(), context.DeadlineExceeded))
|
|
|
|
return nil, context.DeadlineExceeded
|
2020-12-10 23:13:37 +00:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
<-ctx.Done()
|
|
|
|
|
2023-07-13 09:40:00 +00:00
|
|
|
_, err := client.sendRequest(ctx, http.MethodGet, testEndpoint, nil, nil, nil)
|
|
|
|
assert.Check(t, is.ErrorType(err, errdefs.IsDeadline))
|
|
|
|
assert.Check(t, errors.Is(err, context.DeadlineExceeded))
|
2020-12-10 23:13:37 +00:00
|
|
|
}
|