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 }