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