github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/integration/plugin/authz/authz_plugin_test.go (about) 1 // +build !windows 2 3 package authz // import "github.com/docker/docker/integration/plugin/authz" 4 5 import ( 6 "context" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "net" 11 "net/http" 12 "net/http/httputil" 13 "net/url" 14 "os" 15 "path/filepath" 16 "strconv" 17 "strings" 18 "testing" 19 "time" 20 21 "github.com/docker/docker/api/types" 22 eventtypes "github.com/docker/docker/api/types/events" 23 "github.com/docker/docker/client" 24 "github.com/docker/docker/integration/internal/container" 25 "github.com/docker/docker/internal/test/environment" 26 "github.com/docker/docker/pkg/archive" 27 "github.com/docker/docker/pkg/authorization" 28 "github.com/gotestyourself/gotestyourself/assert" 29 "github.com/gotestyourself/gotestyourself/skip" 30 ) 31 32 const ( 33 testAuthZPlugin = "authzplugin" 34 unauthorizedMessage = "User unauthorized authz plugin" 35 errorMessage = "something went wrong..." 36 serverVersionAPI = "/version" 37 ) 38 39 var ( 40 alwaysAllowed = []string{"/_ping", "/info"} 41 ctrl *authorizationController 42 ) 43 44 type authorizationController struct { 45 reqRes authorization.Response // reqRes holds the plugin response to the initial client request 46 resRes authorization.Response // resRes holds the plugin response to the daemon response 47 versionReqCount int // versionReqCount counts the number of requests to the server version API endpoint 48 versionResCount int // versionResCount counts the number of responses from the server version API endpoint 49 requestsURIs []string // requestsURIs stores all request URIs that are sent to the authorization controller 50 reqUser string 51 resUser string 52 } 53 54 func setupTestV1(t *testing.T) func() { 55 ctrl = &authorizationController{} 56 teardown := setupTest(t) 57 58 err := os.MkdirAll("/etc/docker/plugins", 0755) 59 assert.NilError(t, err) 60 61 fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin) 62 err = ioutil.WriteFile(fileName, []byte(server.URL), 0644) 63 assert.NilError(t, err) 64 65 return func() { 66 err := os.RemoveAll("/etc/docker/plugins") 67 assert.NilError(t, err) 68 69 teardown() 70 ctrl = nil 71 } 72 } 73 74 // check for always allowed endpoints to not inhibit test framework functions 75 func isAllowed(reqURI string) bool { 76 for _, endpoint := range alwaysAllowed { 77 if strings.HasSuffix(reqURI, endpoint) { 78 return true 79 } 80 } 81 return false 82 } 83 84 func TestAuthZPluginAllowRequest(t *testing.T) { 85 defer setupTestV1(t)() 86 ctrl.reqRes.Allow = true 87 ctrl.resRes.Allow = true 88 d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin) 89 90 client, err := d.NewClient() 91 assert.NilError(t, err) 92 93 ctx := context.Background() 94 95 // Ensure command successful 96 cID := container.Run(t, ctx, client) 97 98 assertURIRecorded(t, ctrl.requestsURIs, "/containers/create") 99 assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", cID)) 100 101 _, err = client.ServerVersion(ctx) 102 assert.NilError(t, err) 103 assert.Equal(t, 1, ctrl.versionReqCount) 104 assert.Equal(t, 1, ctrl.versionResCount) 105 } 106 107 func TestAuthZPluginTLS(t *testing.T) { 108 defer setupTestV1(t)() 109 const ( 110 testDaemonHTTPSAddr = "tcp://localhost:4271" 111 cacertPath = "../../testdata/https/ca.pem" 112 serverCertPath = "../../testdata/https/server-cert.pem" 113 serverKeyPath = "../../testdata/https/server-key.pem" 114 clientCertPath = "../../testdata/https/client-cert.pem" 115 clientKeyPath = "../../testdata/https/client-key.pem" 116 ) 117 118 d.Start(t, 119 "--authorization-plugin="+testAuthZPlugin, 120 "--tlsverify", 121 "--tlscacert", cacertPath, 122 "--tlscert", serverCertPath, 123 "--tlskey", serverKeyPath, 124 "-H", testDaemonHTTPSAddr) 125 126 ctrl.reqRes.Allow = true 127 ctrl.resRes.Allow = true 128 129 client, err := newTLSAPIClient(testDaemonHTTPSAddr, cacertPath, clientCertPath, clientKeyPath) 130 assert.NilError(t, err) 131 132 _, err = client.ServerVersion(context.Background()) 133 assert.NilError(t, err) 134 135 assert.Equal(t, "client", ctrl.reqUser) 136 assert.Equal(t, "client", ctrl.resUser) 137 } 138 139 func newTLSAPIClient(host, cacertPath, certPath, keyPath string) (client.APIClient, error) { 140 dialer := &net.Dialer{ 141 KeepAlive: 30 * time.Second, 142 Timeout: 30 * time.Second, 143 } 144 return client.NewClientWithOpts( 145 client.WithTLSClientConfig(cacertPath, certPath, keyPath), 146 client.WithDialer(dialer), 147 client.WithHost(host)) 148 } 149 150 func TestAuthZPluginDenyRequest(t *testing.T) { 151 defer setupTestV1(t)() 152 d.Start(t, "--authorization-plugin="+testAuthZPlugin) 153 ctrl.reqRes.Allow = false 154 ctrl.reqRes.Msg = unauthorizedMessage 155 156 client, err := d.NewClient() 157 assert.NilError(t, err) 158 159 // Ensure command is blocked 160 _, err = client.ServerVersion(context.Background()) 161 assert.Assert(t, err != nil) 162 assert.Equal(t, 1, ctrl.versionReqCount) 163 assert.Equal(t, 0, ctrl.versionResCount) 164 165 // Ensure unauthorized message appears in response 166 assert.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error()) 167 } 168 169 // TestAuthZPluginAPIDenyResponse validates that when authorization 170 // plugin deny the request, the status code is forbidden 171 func TestAuthZPluginAPIDenyResponse(t *testing.T) { 172 defer setupTestV1(t)() 173 d.Start(t, "--authorization-plugin="+testAuthZPlugin) 174 ctrl.reqRes.Allow = false 175 ctrl.resRes.Msg = unauthorizedMessage 176 177 daemonURL, err := url.Parse(d.Sock()) 178 assert.NilError(t, err) 179 180 conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10) 181 assert.NilError(t, err) 182 client := httputil.NewClientConn(conn, nil) 183 req, err := http.NewRequest("GET", "/version", nil) 184 assert.NilError(t, err) 185 resp, err := client.Do(req) 186 187 assert.NilError(t, err) 188 assert.DeepEqual(t, http.StatusForbidden, resp.StatusCode) 189 } 190 191 func TestAuthZPluginDenyResponse(t *testing.T) { 192 defer setupTestV1(t)() 193 d.Start(t, "--authorization-plugin="+testAuthZPlugin) 194 ctrl.reqRes.Allow = true 195 ctrl.resRes.Allow = false 196 ctrl.resRes.Msg = unauthorizedMessage 197 198 client, err := d.NewClient() 199 assert.NilError(t, err) 200 201 // Ensure command is blocked 202 _, err = client.ServerVersion(context.Background()) 203 assert.Assert(t, err != nil) 204 assert.Equal(t, 1, ctrl.versionReqCount) 205 assert.Equal(t, 1, ctrl.versionResCount) 206 207 // Ensure unauthorized message appears in response 208 assert.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error()) 209 } 210 211 // TestAuthZPluginAllowEventStream verifies event stream propagates 212 // correctly after request pass through by the authorization plugin 213 func TestAuthZPluginAllowEventStream(t *testing.T) { 214 skip.IfCondition(t, testEnv.DaemonInfo.OSType != "linux") 215 216 defer setupTestV1(t)() 217 ctrl.reqRes.Allow = true 218 ctrl.resRes.Allow = true 219 d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin) 220 221 client, err := d.NewClient() 222 assert.NilError(t, err) 223 224 ctx := context.Background() 225 226 startTime := strconv.FormatInt(systemTime(t, client, testEnv).Unix(), 10) 227 events, errs, cancel := systemEventsSince(client, startTime) 228 defer cancel() 229 230 // Create a container and wait for the creation events 231 cID := container.Run(t, ctx, client) 232 233 for i := 0; i < 100; i++ { 234 c, err := client.ContainerInspect(ctx, cID) 235 assert.NilError(t, err) 236 if c.State.Running { 237 break 238 } 239 if i == 99 { 240 t.Fatal("Container didn't run within 10s") 241 } 242 time.Sleep(100 * time.Millisecond) 243 } 244 245 created := false 246 started := false 247 for !created && !started { 248 select { 249 case event := <-events: 250 if event.Type == eventtypes.ContainerEventType && event.Actor.ID == cID { 251 if event.Action == "create" { 252 created = true 253 } 254 if event.Action == "start" { 255 started = true 256 } 257 } 258 case err := <-errs: 259 if err == io.EOF { 260 t.Fatal("premature end of event stream") 261 } 262 assert.NilError(t, err) 263 case <-time.After(30 * time.Second): 264 // Fail the test 265 t.Fatal("event stream timeout") 266 } 267 } 268 269 // Ensure both events and container endpoints are passed to the 270 // authorization plugin 271 assertURIRecorded(t, ctrl.requestsURIs, "/events") 272 assertURIRecorded(t, ctrl.requestsURIs, "/containers/create") 273 assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", cID)) 274 } 275 276 func systemTime(t *testing.T, client client.APIClient, testEnv *environment.Execution) time.Time { 277 if testEnv.IsLocalDaemon() { 278 return time.Now() 279 } 280 281 ctx := context.Background() 282 info, err := client.Info(ctx) 283 assert.NilError(t, err) 284 285 dt, err := time.Parse(time.RFC3339Nano, info.SystemTime) 286 assert.NilError(t, err, "invalid time format in GET /info response") 287 return dt 288 } 289 290 func systemEventsSince(client client.APIClient, since string) (<-chan eventtypes.Message, <-chan error, func()) { 291 eventOptions := types.EventsOptions{ 292 Since: since, 293 } 294 ctx, cancel := context.WithCancel(context.Background()) 295 events, errs := client.Events(ctx, eventOptions) 296 297 return events, errs, cancel 298 } 299 300 func TestAuthZPluginErrorResponse(t *testing.T) { 301 defer setupTestV1(t)() 302 d.Start(t, "--authorization-plugin="+testAuthZPlugin) 303 ctrl.reqRes.Allow = true 304 ctrl.resRes.Err = errorMessage 305 306 client, err := d.NewClient() 307 assert.NilError(t, err) 308 309 // Ensure command is blocked 310 _, err = client.ServerVersion(context.Background()) 311 assert.Assert(t, err != nil) 312 assert.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage), err.Error()) 313 } 314 315 func TestAuthZPluginErrorRequest(t *testing.T) { 316 defer setupTestV1(t)() 317 d.Start(t, "--authorization-plugin="+testAuthZPlugin) 318 ctrl.reqRes.Err = errorMessage 319 320 client, err := d.NewClient() 321 assert.NilError(t, err) 322 323 // Ensure command is blocked 324 _, err = client.ServerVersion(context.Background()) 325 assert.Assert(t, err != nil) 326 assert.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage), err.Error()) 327 } 328 329 func TestAuthZPluginEnsureNoDuplicatePluginRegistration(t *testing.T) { 330 defer setupTestV1(t)() 331 d.Start(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin) 332 333 ctrl.reqRes.Allow = true 334 ctrl.resRes.Allow = true 335 336 client, err := d.NewClient() 337 assert.NilError(t, err) 338 339 _, err = client.ServerVersion(context.Background()) 340 assert.NilError(t, err) 341 342 // assert plugin is only called once.. 343 assert.Equal(t, 1, ctrl.versionReqCount) 344 assert.Equal(t, 1, ctrl.versionResCount) 345 } 346 347 func TestAuthZPluginEnsureLoadImportWorking(t *testing.T) { 348 defer setupTestV1(t)() 349 ctrl.reqRes.Allow = true 350 ctrl.resRes.Allow = true 351 d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin) 352 353 client, err := d.NewClient() 354 assert.NilError(t, err) 355 356 ctx := context.Background() 357 358 tmp, err := ioutil.TempDir("", "test-authz-load-import") 359 assert.NilError(t, err) 360 defer os.RemoveAll(tmp) 361 362 savedImagePath := filepath.Join(tmp, "save.tar") 363 364 err = imageSave(client, savedImagePath, "busybox") 365 assert.NilError(t, err) 366 err = imageLoad(client, savedImagePath) 367 assert.NilError(t, err) 368 369 exportedImagePath := filepath.Join(tmp, "export.tar") 370 371 cID := container.Run(t, ctx, client) 372 373 responseReader, err := client.ContainerExport(context.Background(), cID) 374 assert.NilError(t, err) 375 defer responseReader.Close() 376 file, err := os.Create(exportedImagePath) 377 assert.NilError(t, err) 378 defer file.Close() 379 _, err = io.Copy(file, responseReader) 380 assert.NilError(t, err) 381 382 err = imageImport(client, exportedImagePath) 383 assert.NilError(t, err) 384 } 385 386 func TestAuthzPluginEnsureContainerCopyToFrom(t *testing.T) { 387 defer setupTestV1(t)() 388 ctrl.reqRes.Allow = true 389 ctrl.resRes.Allow = true 390 d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin) 391 392 dir, err := ioutil.TempDir("", t.Name()) 393 assert.Assert(t, err) 394 defer os.RemoveAll(dir) 395 396 f, err := ioutil.TempFile(dir, "send") 397 assert.Assert(t, err) 398 defer f.Close() 399 400 buf := make([]byte, 1024) 401 fileSize := len(buf) * 1024 * 10 402 for written := 0; written < fileSize; { 403 n, err := f.Write(buf) 404 assert.Assert(t, err) 405 written += n 406 } 407 408 ctx := context.Background() 409 client, err := d.NewClient() 410 assert.Assert(t, err) 411 412 cID := container.Run(t, ctx, client) 413 defer client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true}) 414 415 _, err = f.Seek(0, io.SeekStart) 416 assert.Assert(t, err) 417 418 srcInfo, err := archive.CopyInfoSourcePath(f.Name(), false) 419 assert.Assert(t, err) 420 srcArchive, err := archive.TarResource(srcInfo) 421 assert.Assert(t, err) 422 defer srcArchive.Close() 423 424 dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, archive.CopyInfo{Path: "/test"}) 425 assert.Assert(t, err) 426 427 err = client.CopyToContainer(ctx, cID, dstDir, preparedArchive, types.CopyToContainerOptions{}) 428 assert.Assert(t, err) 429 430 rdr, _, err := client.CopyFromContainer(ctx, cID, "/test") 431 assert.Assert(t, err) 432 _, err = io.Copy(ioutil.Discard, rdr) 433 assert.Assert(t, err) 434 } 435 436 func imageSave(client client.APIClient, path, image string) error { 437 ctx := context.Background() 438 responseReader, err := client.ImageSave(ctx, []string{image}) 439 if err != nil { 440 return err 441 } 442 defer responseReader.Close() 443 file, err := os.Create(path) 444 if err != nil { 445 return err 446 } 447 defer file.Close() 448 _, err = io.Copy(file, responseReader) 449 return err 450 } 451 452 func imageLoad(client client.APIClient, path string) error { 453 file, err := os.Open(path) 454 if err != nil { 455 return err 456 } 457 defer file.Close() 458 quiet := true 459 ctx := context.Background() 460 response, err := client.ImageLoad(ctx, file, quiet) 461 if err != nil { 462 return err 463 } 464 defer response.Body.Close() 465 return nil 466 } 467 468 func imageImport(client client.APIClient, path string) error { 469 file, err := os.Open(path) 470 if err != nil { 471 return err 472 } 473 defer file.Close() 474 options := types.ImageImportOptions{} 475 ref := "" 476 source := types.ImageImportSource{ 477 Source: file, 478 SourceName: "-", 479 } 480 ctx := context.Background() 481 responseReader, err := client.ImageImport(ctx, source, ref, options) 482 if err != nil { 483 return err 484 } 485 defer responseReader.Close() 486 return nil 487 } 488 489 func TestAuthZPluginHeader(t *testing.T) { 490 defer setupTestV1(t)() 491 ctrl.reqRes.Allow = true 492 ctrl.resRes.Allow = true 493 d.StartWithBusybox(t, "--debug", "--authorization-plugin="+testAuthZPlugin) 494 495 daemonURL, err := url.Parse(d.Sock()) 496 assert.NilError(t, err) 497 498 conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10) 499 assert.NilError(t, err) 500 client := httputil.NewClientConn(conn, nil) 501 req, err := http.NewRequest("GET", "/version", nil) 502 assert.NilError(t, err) 503 resp, err := client.Do(req) 504 assert.NilError(t, err) 505 assert.Equal(t, "application/json", resp.Header["Content-Type"][0]) 506 } 507 508 // assertURIRecorded verifies that the given URI was sent and recorded 509 // in the authz plugin 510 func assertURIRecorded(t *testing.T, uris []string, uri string) { 511 var found bool 512 for _, u := range uris { 513 if strings.Contains(u, uri) { 514 found = true 515 break 516 } 517 } 518 if !found { 519 t.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ",")) 520 } 521 }