github.com/docker/engine@v22.0.0-20211208180946-d456264580cf+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/daemon/config" 18 "github.com/docker/docker/testutil/daemon" 19 "gotest.tools/v3/assert" 20 is "gotest.tools/v3/assert/cmp" 21 "gotest.tools/v3/env" 22 "gotest.tools/v3/skip" 23 ) 24 25 func TestConfigDaemonLibtrustID(t *testing.T) { 26 skip.If(t, runtime.GOOS == "windows") 27 28 d := daemon.New(t) 29 defer d.Stop(t) 30 31 trustKey := filepath.Join(d.RootDir(), "key.json") 32 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) 33 assert.NilError(t, err) 34 35 config := filepath.Join(d.RootDir(), "daemon.json") 36 err = os.WriteFile(config, []byte(`{"deprecated-key-path": "`+trustKey+`"}`), 0644) 37 assert.NilError(t, err) 38 39 d.Start(t, "--config-file", config) 40 info := d.Info(t) 41 assert.Equal(t, info.ID, "WTJ3:YSIP:CE2E:G6KJ:PSBD:YX2Y:WEYD:M64G:NU2V:XPZV:H2CR:VLUB") 42 } 43 44 func TestDaemonConfigValidation(t *testing.T) { 45 skip.If(t, runtime.GOOS == "windows") 46 47 d := daemon.New(t) 48 dockerBinary, err := d.BinaryPath() 49 assert.NilError(t, err) 50 params := []string{"--validate", "--config-file"} 51 52 dest := os.Getenv("DOCKER_INTEGRATION_DAEMON_DEST") 53 if dest == "" { 54 dest = os.Getenv("DEST") 55 } 56 testdata := filepath.Join(dest, "..", "..", "integration", "daemon", "testdata") 57 58 const ( 59 validOut = "configuration OK" 60 failedOut = "unable to configure the Docker daemon with file" 61 ) 62 63 tests := []struct { 64 name string 65 args []string 66 expectedOut string 67 }{ 68 { 69 name: "config with no content", 70 args: append(params, filepath.Join(testdata, "empty-config-1.json")), 71 expectedOut: validOut, 72 }, 73 { 74 name: "config with {}", 75 args: append(params, filepath.Join(testdata, "empty-config-2.json")), 76 expectedOut: validOut, 77 }, 78 { 79 name: "invalid config", 80 args: append(params, filepath.Join(testdata, "invalid-config-1.json")), 81 expectedOut: failedOut, 82 }, 83 { 84 name: "malformed config", 85 args: append(params, filepath.Join(testdata, "malformed-config.json")), 86 expectedOut: failedOut, 87 }, 88 { 89 name: "valid config", 90 args: append(params, filepath.Join(testdata, "valid-config-1.json")), 91 expectedOut: validOut, 92 }, 93 } 94 for _, tc := range tests { 95 tc := tc 96 t.Run(tc.name, func(t *testing.T) { 97 t.Parallel() 98 cmd := exec.Command(dockerBinary, tc.args...) 99 out, err := cmd.CombinedOutput() 100 assert.Check(t, is.Contains(string(out), tc.expectedOut)) 101 if tc.expectedOut == failedOut { 102 assert.ErrorContains(t, err, "", "expected an error, but got none") 103 } else { 104 assert.NilError(t, err) 105 } 106 }) 107 } 108 } 109 110 func TestConfigDaemonSeccompProfiles(t *testing.T) { 111 skip.If(t, runtime.GOOS == "windows") 112 113 d := daemon.New(t) 114 defer d.Stop(t) 115 116 tests := []struct { 117 doc string 118 profile string 119 expectedProfile string 120 }{ 121 { 122 doc: "empty profile set", 123 profile: "", 124 expectedProfile: config.SeccompProfileDefault, 125 }, 126 { 127 doc: "default profile", 128 profile: config.SeccompProfileDefault, 129 expectedProfile: config.SeccompProfileDefault, 130 }, 131 { 132 doc: "unconfined profile", 133 profile: config.SeccompProfileUnconfined, 134 expectedProfile: config.SeccompProfileUnconfined, 135 }, 136 } 137 138 for _, tc := range tests { 139 tc := tc 140 t.Run(tc.doc, func(t *testing.T) { 141 d.Start(t, "--seccomp-profile="+tc.profile) 142 info := d.Info(t) 143 assert.Assert(t, is.Contains(info.SecurityOptions, "name=seccomp,profile="+tc.expectedProfile)) 144 d.Stop(t) 145 146 cfg := filepath.Join(d.RootDir(), "daemon.json") 147 err := os.WriteFile(cfg, []byte(`{"seccomp-profile": "`+tc.profile+`"}`), 0644) 148 assert.NilError(t, err) 149 150 d.Start(t, "--config-file", cfg) 151 info = d.Info(t) 152 assert.Assert(t, is.Contains(info.SecurityOptions, "name=seccomp,profile="+tc.expectedProfile)) 153 d.Stop(t) 154 }) 155 } 156 } 157 158 func TestDaemonProxy(t *testing.T) { 159 skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows") 160 skip.If(t, os.Getenv("DOCKER_ROOTLESS") != "", "cannot connect to localhost proxy in rootless environment") 161 162 var received string 163 proxyServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 164 received = r.Host 165 w.Header().Set("Content-Type", "application/json") 166 _, _ = w.Write([]byte("OK")) 167 })) 168 defer proxyServer.Close() 169 170 const userPass = "myuser:mypassword@" 171 172 // Configure proxy through env-vars 173 t.Run("environment variables", func(t *testing.T) { 174 defer env.Patch(t, "HTTP_PROXY", proxyServer.URL)() 175 defer env.Patch(t, "HTTPS_PROXY", proxyServer.URL)() 176 defer env.Patch(t, "NO_PROXY", "example.com")() 177 178 d := daemon.New(t) 179 c := d.NewClientT(t) 180 defer func() { _ = c.Close() }() 181 ctx := context.Background() 182 d.Start(t) 183 184 _, err := c.ImagePull(ctx, "example.org:5000/some/image:latest", types.ImagePullOptions{}) 185 assert.ErrorContains(t, err, "", "pulling should have failed") 186 assert.Equal(t, received, "example.org:5000") 187 188 // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed. 189 _, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{}) 190 assert.ErrorContains(t, err, "", "pulling should have failed") 191 assert.Equal(t, received, "example.org:5000", "should not have used proxy") 192 193 info := d.Info(t) 194 assert.Equal(t, info.HTTPProxy, proxyServer.URL) 195 assert.Equal(t, info.HTTPSProxy, proxyServer.URL) 196 assert.Equal(t, info.NoProxy, "example.com") 197 d.Stop(t) 198 }) 199 200 // Configure proxy through command-line flags 201 t.Run("command-line options", func(t *testing.T) { 202 defer env.Patch(t, "HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")() 203 defer env.Patch(t, "http_proxy", "http://"+userPass+"from-env-http.invalid")() 204 defer env.Patch(t, "HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")() 205 defer env.Patch(t, "https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")() 206 defer env.Patch(t, "NO_PROXY", "ignore.invalid")() 207 defer env.Patch(t, "no_proxy", "ignore.invalid")() 208 209 d := daemon.New(t) 210 d.Start(t, "--http-proxy", proxyServer.URL, "--https-proxy", proxyServer.URL, "--no-proxy", "example.com") 211 212 logs, err := d.ReadLogFile() 213 assert.NilError(t, err) 214 assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration")) 215 for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} { 216 assert.Assert(t, is.Contains(string(logs), "name="+v)) 217 assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs)) 218 } 219 220 c := d.NewClientT(t) 221 defer func() { _ = c.Close() }() 222 ctx := context.Background() 223 224 _, err = c.ImagePull(ctx, "example.org:5001/some/image:latest", types.ImagePullOptions{}) 225 assert.ErrorContains(t, err, "", "pulling should have failed") 226 assert.Equal(t, received, "example.org:5001") 227 228 // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed. 229 _, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{}) 230 assert.ErrorContains(t, err, "", "pulling should have failed") 231 assert.Equal(t, received, "example.org:5001", "should not have used proxy") 232 233 info := d.Info(t) 234 assert.Equal(t, info.HTTPProxy, proxyServer.URL) 235 assert.Equal(t, info.HTTPSProxy, proxyServer.URL) 236 assert.Equal(t, info.NoProxy, "example.com") 237 238 d.Stop(t) 239 }) 240 241 // Configure proxy through configuration file 242 t.Run("configuration file", func(t *testing.T) { 243 defer env.Patch(t, "HTTP_PROXY", "http://"+userPass+"from-env-http.invalid")() 244 defer env.Patch(t, "http_proxy", "http://"+userPass+"from-env-http.invalid")() 245 defer env.Patch(t, "HTTPS_PROXY", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")() 246 defer env.Patch(t, "https_proxy", "https://"+userPass+"myuser:mypassword@from-env-https.invalid")() 247 defer env.Patch(t, "NO_PROXY", "ignore.invalid")() 248 defer env.Patch(t, "no_proxy", "ignore.invalid")() 249 250 d := daemon.New(t) 251 c := d.NewClientT(t) 252 defer func() { _ = c.Close() }() 253 ctx := context.Background() 254 255 configFile := filepath.Join(d.RootDir(), "daemon.json") 256 configJSON := fmt.Sprintf(`{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}`, proxyServer.URL) 257 assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0644)) 258 259 d.Start(t, "--config-file", configFile) 260 261 logs, err := d.ReadLogFile() 262 assert.NilError(t, err) 263 assert.Assert(t, is.Contains(string(logs), "overriding existing proxy variable with value from configuration")) 264 for _, v := range []string{"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "no_proxy", "NO_PROXY"} { 265 assert.Assert(t, is.Contains(string(logs), "name="+v)) 266 assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs)) 267 } 268 269 _, err = c.ImagePull(ctx, "example.org:5002/some/image:latest", types.ImagePullOptions{}) 270 assert.ErrorContains(t, err, "", "pulling should have failed") 271 assert.Equal(t, received, "example.org:5002") 272 273 // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed. 274 _, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{}) 275 assert.ErrorContains(t, err, "", "pulling should have failed") 276 assert.Equal(t, received, "example.org:5002", "should not have used proxy") 277 278 info := d.Info(t) 279 assert.Equal(t, info.HTTPProxy, proxyServer.URL) 280 assert.Equal(t, info.HTTPSProxy, proxyServer.URL) 281 assert.Equal(t, info.NoProxy, "example.com") 282 283 d.Stop(t) 284 }) 285 286 // Conflicting options (passed both through command-line options and config file) 287 t.Run("conflicting options", func(t *testing.T) { 288 const ( 289 proxyRawURL = "https://" + userPass + "example.org" 290 proxyURL = "https://xxxxx:xxxxx@example.org" 291 ) 292 293 d := daemon.New(t) 294 295 configFile := filepath.Join(d.RootDir(), "daemon.json") 296 configJSON := fmt.Sprintf(`{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}`, proxyRawURL) 297 assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0644)) 298 299 err := d.StartWithError("--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com", "--config-file", configFile, "--validate") 300 assert.ErrorContains(t, err, "daemon exited during startup") 301 logs, err := d.ReadLogFile() 302 assert.NilError(t, err) 303 expected := fmt.Sprintf( 304 `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)`, 305 proxyURL, 306 ) 307 assert.Assert(t, is.Contains(string(logs), expected)) 308 }) 309 310 // Make sure values are sanitized when reloading the daemon-config 311 t.Run("reload sanitized", func(t *testing.T) { 312 const ( 313 proxyRawURL = "https://" + userPass + "example.org" 314 proxyURL = "https://xxxxx:xxxxx@example.org" 315 ) 316 317 d := daemon.New(t) 318 d.Start(t, "--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com") 319 defer d.Stop(t) 320 err := d.Signal(syscall.SIGHUP) 321 assert.NilError(t, err) 322 323 logs, err := d.ReadLogFile() 324 assert.NilError(t, err) 325 326 // FIXME: there appears to ba a race condition, which causes ReadLogFile 327 // to not contain the full logs after signaling the daemon to reload, 328 // causing the test to fail here. As a workaround, check if we 329 // received the "reloaded" message after signaling, and only then 330 // check that it's sanitized properly. For more details on this 331 // issue, see https://github.com/moby/moby/pull/42835/files#r713120315 332 if !strings.Contains(string(logs), "Reloaded configuration:") { 333 t.Skip("Skipping test, because we did not find 'Reloaded configuration' in the logs") 334 } 335 336 assert.Assert(t, is.Contains(string(logs), proxyURL)) 337 assert.Assert(t, !strings.Contains(string(logs), userPass), "logs should not contain the non-sanitized proxy URL: %s", string(logs)) 338 }) 339 }