github.com/adityamillind98/moby@v23.0.0-rc.4+incompatible/integration/daemon/daemon_test.go (about)

     1  package daemon // import "github.com/docker/docker/integration/daemon"
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  	"syscall"
    14  	"testing"
    15  
    16  	"github.com/docker/docker/api/types"
    17  	"github.com/docker/docker/api/types/mount"
    18  	"github.com/docker/docker/api/types/volume"
    19  	"github.com/docker/docker/daemon/config"
    20  	"github.com/docker/docker/integration/internal/container"
    21  	"github.com/docker/docker/testutil/daemon"
    22  	"gotest.tools/v3/assert"
    23  	is "gotest.tools/v3/assert/cmp"
    24  	"gotest.tools/v3/skip"
    25  )
    26  
    27  const (
    28  	libtrustKey   = `{"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"}`
    29  	libtrustKeyID = "WTJ3:YSIP:CE2E:G6KJ:PSBD:YX2Y:WEYD:M64G:NU2V:XPZV:H2CR:VLUB"
    30  )
    31  
    32  func TestConfigDaemonID(t *testing.T) {
    33  	skip.If(t, runtime.GOOS == "windows")
    34  
    35  	d := daemon.New(t)
    36  	defer d.Stop(t)
    37  
    38  	trustKey := filepath.Join(d.RootDir(), "key.json")
    39  	err := os.WriteFile(trustKey, []byte(libtrustKey), 0644)
    40  	assert.NilError(t, err)
    41  
    42  	cfg := filepath.Join(d.RootDir(), "daemon.json")
    43  	err = os.WriteFile(cfg, []byte(`{"deprecated-key-path": "`+trustKey+`"}`), 0644)
    44  	assert.NilError(t, err)
    45  
    46  	// Verify that on an installation with a trust-key present, the ID matches
    47  	// the trust-key ID, and that the ID has been migrated to the engine-id file.
    48  	d.Start(t, "--config-file", cfg, "--iptables=false")
    49  	info := d.Info(t)
    50  	assert.Equal(t, info.ID, libtrustKeyID)
    51  
    52  	idFile := filepath.Join(d.RootDir(), "engine-id")
    53  	id, err := os.ReadFile(idFile)
    54  	assert.NilError(t, err)
    55  	assert.Equal(t, string(id), libtrustKeyID)
    56  	d.Stop(t)
    57  
    58  	// Verify that (if present) the engine-id file takes precedence
    59  	const engineID = "this-is-the-engine-id"
    60  	err = os.WriteFile(idFile, []byte(engineID), 0600)
    61  	assert.NilError(t, err)
    62  
    63  	d.Start(t, "--config-file", cfg, "--iptables=false")
    64  	info = d.Info(t)
    65  	assert.Equal(t, info.ID, engineID)
    66  	d.Stop(t)
    67  }
    68  
    69  func TestDaemonConfigValidation(t *testing.T) {
    70  	skip.If(t, runtime.GOOS == "windows")
    71  
    72  	d := daemon.New(t)
    73  	dockerBinary, err := d.BinaryPath()
    74  	assert.NilError(t, err)
    75  	params := []string{"--validate", "--config-file"}
    76  
    77  	dest := os.Getenv("DOCKER_INTEGRATION_DAEMON_DEST")
    78  	if dest == "" {
    79  		dest = os.Getenv("DEST")
    80  	}
    81  	testdata := filepath.Join(dest, "..", "..", "integration", "daemon", "testdata")
    82  
    83  	const (
    84  		validOut  = "configuration OK"
    85  		failedOut = "unable to configure the Docker daemon with file"
    86  	)
    87  
    88  	tests := []struct {
    89  		name        string
    90  		args        []string
    91  		expectedOut string
    92  	}{
    93  		{
    94  			name:        "config with no content",
    95  			args:        append(params, filepath.Join(testdata, "empty-config-1.json")),
    96  			expectedOut: validOut,
    97  		},
    98  		{
    99  			name:        "config with {}",
   100  			args:        append(params, filepath.Join(testdata, "empty-config-2.json")),
   101  			expectedOut: validOut,
   102  		},
   103  		{
   104  			name:        "invalid config",
   105  			args:        append(params, filepath.Join(testdata, "invalid-config-1.json")),
   106  			expectedOut: failedOut,
   107  		},
   108  		{
   109  			name:        "malformed config",
   110  			args:        append(params, filepath.Join(testdata, "malformed-config.json")),
   111  			expectedOut: failedOut,
   112  		},
   113  		{
   114  			name:        "valid config",
   115  			args:        append(params, filepath.Join(testdata, "valid-config-1.json")),
   116  			expectedOut: validOut,
   117  		},
   118  	}
   119  	for _, tc := range tests {
   120  		tc := tc
   121  		t.Run(tc.name, func(t *testing.T) {
   122  			t.Parallel()
   123  			cmd := exec.Command(dockerBinary, tc.args...)
   124  			out, err := cmd.CombinedOutput()
   125  			assert.Check(t, is.Contains(string(out), tc.expectedOut))
   126  			if tc.expectedOut == failedOut {
   127  				assert.ErrorContains(t, err, "", "expected an error, but got none")
   128  			} else {
   129  				assert.NilError(t, err)
   130  			}
   131  		})
   132  	}
   133  }
   134  
   135  func TestConfigDaemonSeccompProfiles(t *testing.T) {
   136  	skip.If(t, runtime.GOOS == "windows")
   137  
   138  	d := daemon.New(t)
   139  	defer d.Stop(t)
   140  
   141  	tests := []struct {
   142  		doc             string
   143  		profile         string
   144  		expectedProfile string
   145  	}{
   146  		{
   147  			doc:             "empty profile set",
   148  			profile:         "",
   149  			expectedProfile: config.SeccompProfileDefault,
   150  		},
   151  		{
   152  			doc:             "default profile",
   153  			profile:         config.SeccompProfileDefault,
   154  			expectedProfile: config.SeccompProfileDefault,
   155  		},
   156  		{
   157  			doc:             "unconfined profile",
   158  			profile:         config.SeccompProfileUnconfined,
   159  			expectedProfile: config.SeccompProfileUnconfined,
   160  		},
   161  	}
   162  
   163  	for _, tc := range tests {
   164  		tc := tc
   165  		t.Run(tc.doc, func(t *testing.T) {
   166  			d.Start(t, "--seccomp-profile="+tc.profile)
   167  			info := d.Info(t)
   168  			assert.Assert(t, is.Contains(info.SecurityOptions, "name=seccomp,profile="+tc.expectedProfile))
   169  			d.Stop(t)
   170  
   171  			cfg := filepath.Join(d.RootDir(), "daemon.json")
   172  			err := os.WriteFile(cfg, []byte(`{"seccomp-profile": "`+tc.profile+`"}`), 0644)
   173  			assert.NilError(t, err)
   174  
   175  			d.Start(t, "--config-file", cfg)
   176  			info = d.Info(t)
   177  			assert.Assert(t, is.Contains(info.SecurityOptions, "name=seccomp,profile="+tc.expectedProfile))
   178  			d.Stop(t)
   179  		})
   180  	}
   181  }
   182  
   183  func TestDaemonProxy(t *testing.T) {
   184  	skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows")
   185  	skip.If(t, os.Getenv("DOCKER_ROOTLESS") != "", "cannot connect to localhost proxy in rootless environment")
   186  
   187  	var received string
   188  	proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   189  		received = r.Host
   190  		w.Header().Set("Content-Type", "application/json")
   191  		_, _ = w.Write([]byte("OK"))
   192  	}))
   193  	defer proxyServer.Close()
   194  
   195  	const userPass = "myuser:mypassword@"
   196  
   197  	// Configure proxy through env-vars
   198  	t.Run("environment variables", func(t *testing.T) {
   199  		t.Setenv("HTTP_PROXY", proxyServer.URL)
   200  		t.Setenv("HTTPS_PROXY", proxyServer.URL)
   201  		t.Setenv("NO_PROXY", "example.com")
   202  
   203  		d := daemon.New(t)
   204  		c := d.NewClientT(t)
   205  		defer func() { _ = c.Close() }()
   206  		ctx := context.Background()
   207  		d.Start(t)
   208  
   209  		_, err := c.ImagePull(ctx, "example.org:5000/some/image:latest", types.ImagePullOptions{})
   210  		assert.ErrorContains(t, err, "", "pulling should have failed")
   211  		assert.Equal(t, received, "example.org:5000")
   212  
   213  		// Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
   214  		_, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
   215  		assert.ErrorContains(t, err, "", "pulling should have failed")
   216  		assert.Equal(t, received, "example.org:5000", "should not have used proxy")
   217  
   218  		info := d.Info(t)
   219  		assert.Equal(t, info.HTTPProxy, proxyServer.URL)
   220  		assert.Equal(t, info.HTTPSProxy, proxyServer.URL)
   221  		assert.Equal(t, info.NoProxy, "example.com")
   222  		d.Stop(t)
   223  	})
   224  
   225  	// Configure proxy through command-line flags
   226  	t.Run("command-line options", func(t *testing.T) {
   227  		t.Setenv("HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")
   228  		t.Setenv("http_proxy", "http://"+userPass+"from-env-http.invalid")
   229  		t.Setenv("HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")
   230  		t.Setenv("https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")
   231  		t.Setenv("NO_PROXY", "ignore.invalid")
   232  		t.Setenv("no_proxy", "ignore.invalid")
   233  
   234  		d := daemon.New(t)
   235  		d.Start(t, "--http-proxy", proxyServer.URL, "--https-proxy", proxyServer.URL, "--no-proxy", "example.com")
   236  
   237  		logs, err := d.ReadLogFile()
   238  		assert.NilError(t, err)
   239  		assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration"))
   240  		for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} {
   241  			assert.Assert(t, is.Contains(string(logs), "name="+v))
   242  			assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
   243  		}
   244  
   245  		c := d.NewClientT(t)
   246  		defer func() { _ = c.Close() }()
   247  		ctx := context.Background()
   248  
   249  		_, err = c.ImagePull(ctx, "example.org:5001/some/image:latest", types.ImagePullOptions{})
   250  		assert.ErrorContains(t, err, "", "pulling should have failed")
   251  		assert.Equal(t, received, "example.org:5001")
   252  
   253  		// Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
   254  		_, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
   255  		assert.ErrorContains(t, err, "", "pulling should have failed")
   256  		assert.Equal(t, received, "example.org:5001", "should not have used proxy")
   257  
   258  		info := d.Info(t)
   259  		assert.Equal(t, info.HTTPProxy, proxyServer.URL)
   260  		assert.Equal(t, info.HTTPSProxy, proxyServer.URL)
   261  		assert.Equal(t, info.NoProxy, "example.com")
   262  
   263  		d.Stop(t)
   264  	})
   265  
   266  	// Configure proxy through configuration file
   267  	t.Run("configuration file", func(t *testing.T) {
   268  		t.Setenv("HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")
   269  		t.Setenv("http_proxy", "http://"+userPass+"from-env-http.invalid")
   270  		t.Setenv("HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")
   271  		t.Setenv("https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")
   272  		t.Setenv("NO_PROXY", "ignore.invalid")
   273  		t.Setenv("no_proxy", "ignore.invalid")
   274  
   275  		d := daemon.New(t)
   276  		c := d.NewClientT(t)
   277  		defer func() { _ = c.Close() }()
   278  		ctx := context.Background()
   279  
   280  		configFile := filepath.Join(d.RootDir(), "daemon.json")
   281  		configJSON := fmt.Sprintf(`{"proxies":{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}}`, proxyServer.URL)
   282  		assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0644))
   283  
   284  		d.Start(t, "--config-file", configFile)
   285  
   286  		logs, err := d.ReadLogFile()
   287  		assert.NilError(t, err)
   288  		assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration"))
   289  		for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} {
   290  			assert.Assert(t, is.Contains(string(logs), "name="+v))
   291  			assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
   292  		}
   293  
   294  		_, err = c.ImagePull(ctx, "example.org:5002/some/image:latest", types.ImagePullOptions{})
   295  		assert.ErrorContains(t, err, "", "pulling should have failed")
   296  		assert.Equal(t, received, "example.org:5002")
   297  
   298  		// Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
   299  		_, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
   300  		assert.ErrorContains(t, err, "", "pulling should have failed")
   301  		assert.Equal(t, received, "example.org:5002", "should not have used proxy")
   302  
   303  		info := d.Info(t)
   304  		assert.Equal(t, info.HTTPProxy, proxyServer.URL)
   305  		assert.Equal(t, info.HTTPSProxy, proxyServer.URL)
   306  		assert.Equal(t, info.NoProxy, "example.com")
   307  
   308  		d.Stop(t)
   309  	})
   310  
   311  	// Conflicting options (passed both through command-line options and config file)
   312  	t.Run("conflicting options", func(t *testing.T) {
   313  		const (
   314  			proxyRawURL = "https://" + userPass + "example.org"
   315  			proxyURL    = "https://xxxxx:xxxxx@example.org"
   316  		)
   317  
   318  		d := daemon.New(t)
   319  
   320  		configFile := filepath.Join(d.RootDir(), "daemon.json")
   321  		configJSON := fmt.Sprintf(`{"proxies":{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}}`, proxyRawURL)
   322  		assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0644))
   323  
   324  		err := d.StartWithError("--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com", "--config-file", configFile, "--validate")
   325  		assert.ErrorContains(t, err, "daemon exited during startup")
   326  		logs, err := d.ReadLogFile()
   327  		assert.NilError(t, err)
   328  		expected := fmt.Sprintf(
   329  			`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)`,
   330  			proxyURL,
   331  		)
   332  		assert.Assert(t, is.Contains(string(logs), expected))
   333  	})
   334  
   335  	// Make sure values are sanitized when reloading the daemon-config
   336  	t.Run("reload sanitized", func(t *testing.T) {
   337  		const (
   338  			proxyRawURL = "https://" + userPass + "example.org"
   339  			proxyURL    = "https://xxxxx:xxxxx@example.org"
   340  		)
   341  
   342  		d := daemon.New(t)
   343  		d.Start(t, "--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com")
   344  		defer d.Stop(t)
   345  		err := d.Signal(syscall.SIGHUP)
   346  		assert.NilError(t, err)
   347  
   348  		logs, err := d.ReadLogFile()
   349  		assert.NilError(t, err)
   350  
   351  		// FIXME: there appears to ba a race condition, which causes ReadLogFile
   352  		//        to not contain the full logs after signaling the daemon to reload,
   353  		//        causing the test to fail here. As a workaround, check if we
   354  		//        received the "reloaded" message after signaling, and only then
   355  		//        check that it's sanitized properly. For more details on this
   356  		//        issue, see https://github.com/moby/moby/pull/42835/files#r713120315
   357  		if !strings.Contains(string(logs), "Reloaded configuration:") {
   358  			t.Skip("Skipping test, because we did not find 'Reloaded configuration' in the logs")
   359  		}
   360  
   361  		assert.Assert(t, is.Contains(string(logs), proxyURL))
   362  		assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs))
   363  	})
   364  }
   365  
   366  func TestLiveRestore(t *testing.T) {
   367  	skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows")
   368  
   369  	t.Run("volume references", testLiveRestoreVolumeReferences)
   370  }
   371  
   372  func testLiveRestoreVolumeReferences(t *testing.T) {
   373  	t.Parallel()
   374  
   375  	d := daemon.New(t)
   376  	d.StartWithBusybox(t, "--live-restore", "--iptables=false")
   377  	defer func() {
   378  		d.Stop(t)
   379  		d.Cleanup(t)
   380  	}()
   381  
   382  	c := d.NewClientT(t)
   383  	ctx := context.Background()
   384  
   385  	runTest := func(t *testing.T, policy string) {
   386  		t.Run(policy, func(t *testing.T) {
   387  			volName := "test-live-restore-volume-references-" + policy
   388  			_, err := c.VolumeCreate(ctx, volume.CreateOptions{Name: volName})
   389  			assert.NilError(t, err)
   390  
   391  			// Create a container that uses the volume
   392  			m := mount.Mount{
   393  				Type:   mount.TypeVolume,
   394  				Source: volName,
   395  				Target: "/foo",
   396  			}
   397  			cID := container.Run(ctx, t, c, container.WithMount(m), container.WithCmd("top"), container.WithRestartPolicy(policy))
   398  			defer c.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true})
   399  
   400  			// Stop the daemon
   401  			d.Restart(t, "--live-restore", "--iptables=false")
   402  
   403  			// Try to remove the volume
   404  			err = c.VolumeRemove(ctx, volName, false)
   405  			assert.ErrorContains(t, err, "volume is in use")
   406  
   407  			_, err = c.VolumeInspect(ctx, volName)
   408  			assert.NilError(t, err)
   409  		})
   410  	}
   411  
   412  	t.Run("restartPolicy", func(t *testing.T) {
   413  		runTest(t, "always")
   414  		runTest(t, "unless-stopped")
   415  		runTest(t, "on-failure")
   416  		runTest(t, "no")
   417  	})
   418  }