github.com/uppal0016/docker_new@v0.0.0-20240123060250-1c98be13ac2c/integration-cli/docker_cli_authz_unix_test.go (about) 1 // +build !windows 2 3 package main 4 5 import ( 6 "encoding/json" 7 "fmt" 8 "io/ioutil" 9 "net/http" 10 "net/http/httptest" 11 "os" 12 "strings" 13 14 "bufio" 15 "bytes" 16 "os/exec" 17 "strconv" 18 "time" 19 20 "github.com/docker/docker/pkg/authorization" 21 "github.com/docker/docker/pkg/integration/checker" 22 "github.com/docker/docker/pkg/plugins" 23 "github.com/go-check/check" 24 ) 25 26 const ( 27 testAuthZPlugin = "authzplugin" 28 unauthorizedMessage = "User unauthorized authz plugin" 29 errorMessage = "something went wrong..." 30 containerListAPI = "/containers/json" 31 ) 32 33 var ( 34 alwaysAllowed = []string{"/_ping", "/info"} 35 ) 36 37 func init() { 38 check.Suite(&DockerAuthzSuite{ 39 ds: &DockerSuite{}, 40 }) 41 } 42 43 type DockerAuthzSuite struct { 44 server *httptest.Server 45 ds *DockerSuite 46 d *Daemon 47 ctrl *authorizationController 48 } 49 50 type authorizationController struct { 51 reqRes authorization.Response // reqRes holds the plugin response to the initial client request 52 resRes authorization.Response // resRes holds the plugin response to the daemon response 53 psRequestCnt int // psRequestCnt counts the number of calls to list container request api 54 psResponseCnt int // psResponseCnt counts the number of calls to list containers response API 55 requestsURIs []string // requestsURIs stores all request URIs that are sent to the authorization controller 56 reqUser string 57 resUser string 58 } 59 60 func (s *DockerAuthzSuite) SetUpTest(c *check.C) { 61 s.d = NewDaemon(c) 62 s.ctrl = &authorizationController{} 63 } 64 65 func (s *DockerAuthzSuite) TearDownTest(c *check.C) { 66 s.d.Stop() 67 s.ds.TearDownTest(c) 68 s.ctrl = nil 69 } 70 71 func (s *DockerAuthzSuite) SetUpSuite(c *check.C) { 72 mux := http.NewServeMux() 73 s.server = httptest.NewServer(mux) 74 c.Assert(s.server, check.NotNil, check.Commentf("Failed to start a HTTP Server")) 75 76 mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { 77 b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}}) 78 c.Assert(err, check.IsNil) 79 w.Write(b) 80 }) 81 82 mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) { 83 defer r.Body.Close() 84 body, err := ioutil.ReadAll(r.Body) 85 c.Assert(err, check.IsNil) 86 authReq := authorization.Request{} 87 err = json.Unmarshal(body, &authReq) 88 c.Assert(err, check.IsNil) 89 90 assertBody(c, authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody) 91 assertAuthHeaders(c, authReq.RequestHeaders) 92 93 // Count only container list api 94 if strings.HasSuffix(authReq.RequestURI, containerListAPI) { 95 s.ctrl.psRequestCnt++ 96 } 97 98 s.ctrl.requestsURIs = append(s.ctrl.requestsURIs, authReq.RequestURI) 99 100 reqRes := s.ctrl.reqRes 101 if isAllowed(authReq.RequestURI) { 102 reqRes = authorization.Response{Allow: true} 103 } 104 if reqRes.Err != "" { 105 w.WriteHeader(http.StatusInternalServerError) 106 } 107 b, err := json.Marshal(reqRes) 108 c.Assert(err, check.IsNil) 109 s.ctrl.reqUser = authReq.User 110 w.Write(b) 111 }) 112 113 mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) { 114 defer r.Body.Close() 115 body, err := ioutil.ReadAll(r.Body) 116 c.Assert(err, check.IsNil) 117 authReq := authorization.Request{} 118 err = json.Unmarshal(body, &authReq) 119 c.Assert(err, check.IsNil) 120 121 assertBody(c, authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody) 122 assertAuthHeaders(c, authReq.ResponseHeaders) 123 124 // Count only container list api 125 if strings.HasSuffix(authReq.RequestURI, containerListAPI) { 126 s.ctrl.psResponseCnt++ 127 } 128 resRes := s.ctrl.resRes 129 if isAllowed(authReq.RequestURI) { 130 resRes = authorization.Response{Allow: true} 131 } 132 if resRes.Err != "" { 133 w.WriteHeader(http.StatusInternalServerError) 134 } 135 b, err := json.Marshal(resRes) 136 c.Assert(err, check.IsNil) 137 s.ctrl.resUser = authReq.User 138 w.Write(b) 139 }) 140 141 err := os.MkdirAll("/etc/docker/plugins", 0755) 142 c.Assert(err, checker.IsNil) 143 144 fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin) 145 err = ioutil.WriteFile(fileName, []byte(s.server.URL), 0644) 146 c.Assert(err, checker.IsNil) 147 } 148 149 // check for always allowed endpoints to not inhibit test framework functions 150 func isAllowed(reqURI string) bool { 151 for _, endpoint := range alwaysAllowed { 152 if strings.HasSuffix(reqURI, endpoint) { 153 return true 154 } 155 } 156 return false 157 } 158 159 // assertAuthHeaders validates authentication headers are removed 160 func assertAuthHeaders(c *check.C, headers map[string]string) error { 161 for k := range headers { 162 if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") { 163 c.Errorf("Found authentication headers in request '%v'", headers) 164 } 165 } 166 return nil 167 } 168 169 // assertBody asserts that body is removed for non text/json requests 170 func assertBody(c *check.C, requestURI string, headers map[string]string, body []byte) { 171 if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 { 172 //return fmt.Errorf("Body included for authentication endpoint %s", string(body)) 173 c.Errorf("Body included for authentication endpoint %s", string(body)) 174 } 175 176 for k, v := range headers { 177 if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" { 178 return 179 } 180 } 181 if len(body) > 0 { 182 c.Errorf("Body included while it should not (Headers: '%v')", headers) 183 } 184 } 185 186 func (s *DockerAuthzSuite) TearDownSuite(c *check.C) { 187 if s.server == nil { 188 return 189 } 190 191 s.server.Close() 192 193 err := os.RemoveAll("/etc/docker/plugins") 194 c.Assert(err, checker.IsNil) 195 } 196 197 func (s *DockerAuthzSuite) TestAuthZPluginAllowRequest(c *check.C) { 198 // start the daemon and load busybox, --net=none build fails otherwise 199 // cause it needs to pull busybox 200 c.Assert(s.d.Start("--authorization-plugin="+testAuthZPlugin), check.IsNil) 201 s.ctrl.reqRes.Allow = true 202 s.ctrl.resRes.Allow = true 203 c.Assert(s.d.LoadBusybox(), check.IsNil) 204 205 // Ensure command successful 206 out, err := s.d.Cmd("run", "-d", "busybox", "top") 207 c.Assert(err, check.IsNil) 208 209 id := strings.TrimSpace(out) 210 assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create") 211 assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id)) 212 213 out, err = s.d.Cmd("ps") 214 c.Assert(err, check.IsNil) 215 c.Assert(assertContainerList(out, []string{id}), check.Equals, true) 216 c.Assert(s.ctrl.psRequestCnt, check.Equals, 1) 217 c.Assert(s.ctrl.psResponseCnt, check.Equals, 1) 218 } 219 220 func (s *DockerAuthzSuite) TestAuthZPluginTls(c *check.C) { 221 222 const testDaemonHTTPSAddr = "tcp://localhost:4271" 223 // start the daemon and load busybox, --net=none build fails otherwise 224 // cause it needs to pull busybox 225 if err := s.d.Start( 226 "--authorization-plugin="+testAuthZPlugin, 227 "--tlsverify", 228 "--tlscacert", 229 "fixtures/https/ca.pem", 230 "--tlscert", 231 "fixtures/https/server-cert.pem", 232 "--tlskey", 233 "fixtures/https/server-key.pem", 234 "-H", testDaemonHTTPSAddr); err != nil { 235 c.Fatalf("Could not start daemon with busybox: %v", err) 236 } 237 238 s.ctrl.reqRes.Allow = true 239 s.ctrl.resRes.Allow = true 240 241 out, _ := dockerCmd( 242 c, 243 "--tlsverify", 244 "--tlscacert", "fixtures/https/ca.pem", 245 "--tlscert", "fixtures/https/client-cert.pem", 246 "--tlskey", "fixtures/https/client-key.pem", 247 "-H", 248 testDaemonHTTPSAddr, 249 "version", 250 ) 251 if !strings.Contains(out, "Server") { 252 c.Fatalf("docker version should return information of server side") 253 } 254 255 c.Assert(s.ctrl.reqUser, check.Equals, "client") 256 c.Assert(s.ctrl.resUser, check.Equals, "client") 257 } 258 259 func (s *DockerAuthzSuite) TestAuthZPluginDenyRequest(c *check.C) { 260 err := s.d.Start("--authorization-plugin=" + testAuthZPlugin) 261 c.Assert(err, check.IsNil) 262 s.ctrl.reqRes.Allow = false 263 s.ctrl.reqRes.Msg = unauthorizedMessage 264 265 // Ensure command is blocked 266 res, err := s.d.Cmd("ps") 267 c.Assert(err, check.NotNil) 268 c.Assert(s.ctrl.psRequestCnt, check.Equals, 1) 269 c.Assert(s.ctrl.psResponseCnt, check.Equals, 0) 270 271 // Ensure unauthorized message appears in response 272 c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage)) 273 } 274 275 func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) { 276 err := s.d.Start("--authorization-plugin=" + testAuthZPlugin) 277 c.Assert(err, check.IsNil) 278 s.ctrl.reqRes.Allow = true 279 s.ctrl.resRes.Allow = false 280 s.ctrl.resRes.Msg = unauthorizedMessage 281 282 // Ensure command is blocked 283 res, err := s.d.Cmd("ps") 284 c.Assert(err, check.NotNil) 285 c.Assert(s.ctrl.psRequestCnt, check.Equals, 1) 286 c.Assert(s.ctrl.psResponseCnt, check.Equals, 1) 287 288 // Ensure unauthorized message appears in response 289 c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage)) 290 } 291 292 // TestAuthZPluginAllowEventStream verifies event stream propagates correctly after request pass through by the authorization plugin 293 func (s *DockerAuthzSuite) TestAuthZPluginAllowEventStream(c *check.C) { 294 testRequires(c, DaemonIsLinux) 295 296 // start the daemon and load busybox to avoid pulling busybox from Docker Hub 297 c.Assert(s.d.Start("--authorization-plugin="+testAuthZPlugin), check.IsNil) 298 s.ctrl.reqRes.Allow = true 299 s.ctrl.resRes.Allow = true 300 c.Assert(s.d.LoadBusybox(), check.IsNil) 301 302 startTime := strconv.FormatInt(daemonTime(c).Unix(), 10) 303 // Add another command to to enable event pipelining 304 eventsCmd := exec.Command(s.d.cmd.Path, "--host", s.d.sock(), "events", "--since", startTime) 305 stdout, err := eventsCmd.StdoutPipe() 306 if err != nil { 307 c.Assert(err, check.IsNil) 308 } 309 310 observer := eventObserver{ 311 buffer: new(bytes.Buffer), 312 command: eventsCmd, 313 scanner: bufio.NewScanner(stdout), 314 startTime: startTime, 315 } 316 317 err = observer.Start() 318 c.Assert(err, checker.IsNil) 319 defer observer.Stop() 320 321 // Create a container and wait for the creation events 322 out, err := s.d.Cmd("run", "-d", "busybox", "top") 323 c.Assert(err, check.IsNil, check.Commentf(out)) 324 containerID := strings.TrimSpace(out) 325 c.Assert(s.d.waitRun(containerID), checker.IsNil) 326 327 events := map[string]chan bool{ 328 "create": make(chan bool, 1), 329 "start": make(chan bool, 1), 330 } 331 332 matcher := matchEventLine(containerID, "container", events) 333 processor := processEventMatch(events) 334 go observer.Match(matcher, processor) 335 336 // Ensure all events are received 337 for event, eventChannel := range events { 338 339 select { 340 case <-time.After(30 * time.Second): 341 // Fail the test 342 observer.CheckEventError(c, containerID, event, matcher) 343 c.FailNow() 344 case <-eventChannel: 345 // Ignore, event received 346 } 347 } 348 349 // Ensure both events and container endpoints are passed to the authorization plugin 350 assertURIRecorded(c, s.ctrl.requestsURIs, "/events") 351 assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create") 352 assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", containerID)) 353 } 354 355 func (s *DockerAuthzSuite) TestAuthZPluginErrorResponse(c *check.C) { 356 err := s.d.Start("--authorization-plugin=" + testAuthZPlugin) 357 c.Assert(err, check.IsNil) 358 s.ctrl.reqRes.Allow = true 359 s.ctrl.resRes.Err = errorMessage 360 361 // Ensure command is blocked 362 res, err := s.d.Cmd("ps") 363 c.Assert(err, check.NotNil) 364 365 c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage)) 366 } 367 368 func (s *DockerAuthzSuite) TestAuthZPluginErrorRequest(c *check.C) { 369 err := s.d.Start("--authorization-plugin=" + testAuthZPlugin) 370 c.Assert(err, check.IsNil) 371 s.ctrl.reqRes.Err = errorMessage 372 373 // Ensure command is blocked 374 res, err := s.d.Cmd("ps") 375 c.Assert(err, check.NotNil) 376 377 c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage)) 378 } 379 380 func (s *DockerAuthzSuite) TestAuthZPluginEnsureNoDuplicatePluginRegistration(c *check.C) { 381 c.Assert(s.d.Start("--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin), check.IsNil) 382 383 s.ctrl.reqRes.Allow = true 384 s.ctrl.resRes.Allow = true 385 386 out, err := s.d.Cmd("ps") 387 c.Assert(err, check.IsNil, check.Commentf(out)) 388 389 // assert plugin is only called once.. 390 c.Assert(s.ctrl.psRequestCnt, check.Equals, 1) 391 c.Assert(s.ctrl.psResponseCnt, check.Equals, 1) 392 } 393 394 // assertURIRecorded verifies that the given URI was sent and recorded in the authz plugin 395 func assertURIRecorded(c *check.C, uris []string, uri string) { 396 var found bool 397 for _, u := range uris { 398 if strings.Contains(u, uri) { 399 found = true 400 break 401 } 402 } 403 if !found { 404 c.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ",")) 405 } 406 }