123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339 |
- package daemon // import "github.com/docker/docker/integration/daemon"
- import (
- "context"
- "fmt"
- "net/http"
- "net/http/httptest"
- "os"
- "os/exec"
- "path/filepath"
- "runtime"
- "strings"
- "syscall"
- "testing"
- "github.com/docker/docker/api/types"
- "github.com/docker/docker/daemon/config"
- "github.com/docker/docker/testutil/daemon"
- "gotest.tools/v3/assert"
- is "gotest.tools/v3/assert/cmp"
- "gotest.tools/v3/env"
- "gotest.tools/v3/skip"
- )
- func TestConfigDaemonLibtrustID(t *testing.T) {
- skip.If(t, runtime.GOOS == "windows")
- d := daemon.New(t)
- defer d.Stop(t)
- trustKey := filepath.Join(d.RootDir(), "key.json")
- err := os.WriteFile(trustKey, []byte(`{"crv":"P-256","d":"dm28PH4Z4EbyUN8L0bPonAciAQa1QJmmyYd876mnypY","kid":"WTJ3:YSIP:CE2E:G6KJ:PSBD:YX2Y:WEYD:M64G:NU2V:XPZV:H2CR:VLUB","kty":"EC","x":"Mh5-JINSjaa_EZdXDttri255Z5fbCEOTQIZjAcScFTk","y":"eUyuAjfxevb07hCCpvi4Zi334Dy4GDWQvEToGEX4exQ"}`), 0644)
- assert.NilError(t, err)
- config := filepath.Join(d.RootDir(), "daemon.json")
- err = os.WriteFile(config, []byte(`{"deprecated-key-path": "`+trustKey+`"}`), 0644)
- assert.NilError(t, err)
- d.Start(t, "--config-file", config)
- info := d.Info(t)
- assert.Equal(t, info.ID, "WTJ3:YSIP:CE2E:G6KJ:PSBD:YX2Y:WEYD:M64G:NU2V:XPZV:H2CR:VLUB")
- }
- func TestDaemonConfigValidation(t *testing.T) {
- skip.If(t, runtime.GOOS == "windows")
- d := daemon.New(t)
- dockerBinary, err := d.BinaryPath()
- assert.NilError(t, err)
- params := []string{"--validate", "--config-file"}
- dest := os.Getenv("DOCKER_INTEGRATION_DAEMON_DEST")
- if dest == "" {
- dest = os.Getenv("DEST")
- }
- testdata := filepath.Join(dest, "..", "..", "integration", "daemon", "testdata")
- const (
- validOut = "configuration OK"
- failedOut = "unable to configure the Docker daemon with file"
- )
- tests := []struct {
- name string
- args []string
- expectedOut string
- }{
- {
- name: "config with no content",
- args: append(params, filepath.Join(testdata, "empty-config-1.json")),
- expectedOut: validOut,
- },
- {
- name: "config with {}",
- args: append(params, filepath.Join(testdata, "empty-config-2.json")),
- expectedOut: validOut,
- },
- {
- name: "invalid config",
- args: append(params, filepath.Join(testdata, "invalid-config-1.json")),
- expectedOut: failedOut,
- },
- {
- name: "malformed config",
- args: append(params, filepath.Join(testdata, "malformed-config.json")),
- expectedOut: failedOut,
- },
- {
- name: "valid config",
- args: append(params, filepath.Join(testdata, "valid-config-1.json")),
- expectedOut: validOut,
- },
- }
- for _, tc := range tests {
- tc := tc
- t.Run(tc.name, func(t *testing.T) {
- t.Parallel()
- cmd := exec.Command(dockerBinary, tc.args...)
- out, err := cmd.CombinedOutput()
- assert.Check(t, is.Contains(string(out), tc.expectedOut))
- if tc.expectedOut == failedOut {
- assert.ErrorContains(t, err, "", "expected an error, but got none")
- } else {
- assert.NilError(t, err)
- }
- })
- }
- }
- func TestConfigDaemonSeccompProfiles(t *testing.T) {
- skip.If(t, runtime.GOOS == "windows")
- d := daemon.New(t)
- defer d.Stop(t)
- tests := []struct {
- doc string
- profile string
- expectedProfile string
- }{
- {
- doc: "empty profile set",
- profile: "",
- expectedProfile: config.SeccompProfileDefault,
- },
- {
- doc: "default profile",
- profile: config.SeccompProfileDefault,
- expectedProfile: config.SeccompProfileDefault,
- },
- {
- doc: "unconfined profile",
- profile: config.SeccompProfileUnconfined,
- expectedProfile: config.SeccompProfileUnconfined,
- },
- }
- for _, tc := range tests {
- tc := tc
- t.Run(tc.doc, func(t *testing.T) {
- d.Start(t, "--seccomp-profile="+tc.profile)
- info := d.Info(t)
- assert.Assert(t, is.Contains(info.SecurityOptions, "name=seccomp,profile="+tc.expectedProfile))
- d.Stop(t)
- cfg := filepath.Join(d.RootDir(), "daemon.json")
- err := os.WriteFile(cfg, []byte(`{"seccomp-profile": "`+tc.profile+`"}`), 0644)
- assert.NilError(t, err)
- d.Start(t, "--config-file", cfg)
- info = d.Info(t)
- assert.Assert(t, is.Contains(info.SecurityOptions, "name=seccomp,profile="+tc.expectedProfile))
- d.Stop(t)
- })
- }
- }
- func TestDaemonProxy(t *testing.T) {
- skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows")
- skip.If(t, os.Getenv("DOCKER_ROOTLESS") != "", "cannot connect to localhost proxy in rootless environment")
- var received string
- proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- received = r.Host
- w.Header().Set("Content-Type", "application/json")
- _, _ = w.Write([]byte("OK"))
- }))
- defer proxyServer.Close()
- const userPass = "myuser:mypassword@"
- // Configure proxy through env-vars
- t.Run("environment variables", func(t *testing.T) {
- defer env.Patch(t, "HTTP_PROXY", proxyServer.URL)()
- defer env.Patch(t, "HTTPS_PROXY", proxyServer.URL)()
- defer env.Patch(t, "NO_PROXY", "example.com")()
- d := daemon.New(t)
- c := d.NewClientT(t)
- defer func() { _ = c.Close() }()
- ctx := context.Background()
- d.Start(t)
- _, err := c.ImagePull(ctx, "example.org:5000/some/image:latest", types.ImagePullOptions{})
- assert.ErrorContains(t, err, "", "pulling should have failed")
- assert.Equal(t, received, "example.org:5000")
- // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
- _, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
- assert.ErrorContains(t, err, "", "pulling should have failed")
- assert.Equal(t, received, "example.org:5000", "should not have used proxy")
- info := d.Info(t)
- assert.Equal(t, info.HTTPProxy, proxyServer.URL)
- assert.Equal(t, info.HTTPSProxy, proxyServer.URL)
- assert.Equal(t, info.NoProxy, "example.com")
- d.Stop(t)
- })
- // Configure proxy through command-line flags
- t.Run("command-line options", func(t *testing.T) {
- defer env.Patch(t, "HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")()
- defer env.Patch(t, "http_proxy", "http://"+userPass+"from-env-http.invalid")()
- defer env.Patch(t, "HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")()
- defer env.Patch(t, "https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")()
- defer env.Patch(t, "NO_PROXY", "ignore.invalid")()
- defer env.Patch(t, "no_proxy", "ignore.invalid")()
- d := daemon.New(t)
- d.Start(t, "--http-proxy", proxyServer.URL, "--https-proxy", proxyServer.URL, "--no-proxy", "example.com")
- logs, err := d.ReadLogFile()
- assert.NilError(t, err)
- assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration"))
- for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} {
- assert.Assert(t, is.Contains(string(logs), "name="+v))
- assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
- }
- c := d.NewClientT(t)
- defer func() { _ = c.Close() }()
- ctx := context.Background()
- _, err = c.ImagePull(ctx, "example.org:5001/some/image:latest", types.ImagePullOptions{})
- assert.ErrorContains(t, err, "", "pulling should have failed")
- assert.Equal(t, received, "example.org:5001")
- // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
- _, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
- assert.ErrorContains(t, err, "", "pulling should have failed")
- assert.Equal(t, received, "example.org:5001", "should not have used proxy")
- info := d.Info(t)
- assert.Equal(t, info.HTTPProxy, proxyServer.URL)
- assert.Equal(t, info.HTTPSProxy, proxyServer.URL)
- assert.Equal(t, info.NoProxy, "example.com")
- d.Stop(t)
- })
- // Configure proxy through configuration file
- t.Run("configuration file", func(t *testing.T) {
- defer env.Patch(t, "HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")()
- defer env.Patch(t, "http_proxy", "http://"+userPass+"from-env-http.invalid")()
- defer env.Patch(t, "HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")()
- defer env.Patch(t, "https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")()
- defer env.Patch(t, "NO_PROXY", "ignore.invalid")()
- defer env.Patch(t, "no_proxy", "ignore.invalid")()
- d := daemon.New(t)
- c := d.NewClientT(t)
- defer func() { _ = c.Close() }()
- ctx := context.Background()
- configFile := filepath.Join(d.RootDir(), "daemon.json")
- configJSON := fmt.Sprintf(`{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}`, proxyServer.URL)
- assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0644))
- d.Start(t, "--config-file", configFile)
- logs, err := d.ReadLogFile()
- assert.NilError(t, err)
- assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration"))
- for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} {
- assert.Assert(t, is.Contains(string(logs), "name="+v))
- assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
- }
- _, err = c.ImagePull(ctx, "example.org:5002/some/image:latest", types.ImagePullOptions{})
- assert.ErrorContains(t, err, "", "pulling should have failed")
- assert.Equal(t, received, "example.org:5002")
- // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
- _, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
- assert.ErrorContains(t, err, "", "pulling should have failed")
- assert.Equal(t, received, "example.org:5002", "should not have used proxy")
- info := d.Info(t)
- assert.Equal(t, info.HTTPProxy, proxyServer.URL)
- assert.Equal(t, info.HTTPSProxy, proxyServer.URL)
- assert.Equal(t, info.NoProxy, "example.com")
- d.Stop(t)
- })
- // Conflicting options (passed both through command-line options and config file)
- t.Run("conflicting options", func(t *testing.T) {
- const (
- proxyRawURL = "https://" + userPass + "example.org"
- proxyURL = "https://xxxxx:xxxxx@example.org"
- )
- d := daemon.New(t)
- configFile := filepath.Join(d.RootDir(), "daemon.json")
- configJSON := fmt.Sprintf(`{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}`, proxyRawURL)
- assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0644))
- err := d.StartWithError("--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com", "--config-file", configFile, "--validate")
- assert.ErrorContains(t, err, "daemon exited during startup")
- logs, err := d.ReadLogFile()
- assert.NilError(t, err)
- expected := fmt.Sprintf(
- `the following directives are specified both as a flag and in the configuration file: http-proxy: (from flag: %[1]s, from file: %[1]s), https-proxy: (from flag: %[1]s, from file: %[1]s), no-proxy: (from flag: example.com, from file: example.com)`,
- proxyURL,
- )
- assert.Assert(t, is.Contains(string(logs), expected))
- })
- // Make sure values are sanitized when reloading the daemon-config
- t.Run("reload sanitized", func(t *testing.T) {
- const (
- proxyRawURL = "https://" + userPass + "example.org"
- proxyURL = "https://xxxxx:xxxxx@example.org"
- )
- d := daemon.New(t)
- d.Start(t, "--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com")
- defer d.Stop(t)
- err := d.Signal(syscall.SIGHUP)
- assert.NilError(t, err)
- logs, err := d.ReadLogFile()
- assert.NilError(t, err)
- // FIXME: there appears to ba a race condition, which causes ReadLogFile
- // to not contain the full logs after signaling the daemon to reload,
- // causing the test to fail here. As a workaround, check if we
- // received the "reloaded" message after signaling, and only then
- // check that it's sanitized properly. For more details on this
- // issue, see https://github.com/moby/moby/pull/42835/files#r713120315
- if !strings.Contains(string(logs), "Reloaded configuration:") {
- t.Skip("Skipping test, because we did not find 'Reloaded configuration' in the logs")
- }
- assert.Assert(t, is.Contains(string(logs), proxyURL))
- assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
- })
- }
|