github.com/ncdc/docker@v0.10.1-0.20160129113957-6c6729ef5b74/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 "github.com/docker/docker/pkg/authorization" 15 "github.com/docker/docker/pkg/integration/checker" 16 "github.com/docker/docker/pkg/plugins" 17 "github.com/go-check/check" 18 ) 19 20 const ( 21 testAuthZPlugin = "authzplugin" 22 unauthorizedMessage = "User unauthorized authz plugin" 23 errorMessage = "something went wrong..." 24 containerListAPI = "/containers/json" 25 ) 26 27 func init() { 28 check.Suite(&DockerAuthzSuite{ 29 ds: &DockerSuite{}, 30 }) 31 } 32 33 type DockerAuthzSuite struct { 34 server *httptest.Server 35 ds *DockerSuite 36 d *Daemon 37 ctrl *authorizationController 38 } 39 40 type authorizationController struct { 41 reqRes authorization.Response // reqRes holds the plugin response to the initial client request 42 resRes authorization.Response // resRes holds the plugin response to the daemon response 43 psRequestCnt int // psRequestCnt counts the number of calls to list container request api 44 psResponseCnt int // psResponseCnt counts the number of calls to list containers response API 45 requestsURIs []string // requestsURIs stores all request URIs that are sent to the authorization controller 46 } 47 48 func (s *DockerAuthzSuite) SetUpTest(c *check.C) { 49 s.d = NewDaemon(c) 50 s.ctrl = &authorizationController{} 51 } 52 53 func (s *DockerAuthzSuite) TearDownTest(c *check.C) { 54 s.d.Stop() 55 s.ds.TearDownTest(c) 56 s.ctrl = nil 57 } 58 59 func (s *DockerAuthzSuite) SetUpSuite(c *check.C) { 60 mux := http.NewServeMux() 61 s.server = httptest.NewServer(mux) 62 c.Assert(s.server, check.NotNil, check.Commentf("Failed to start a HTTP Server")) 63 64 mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) { 65 b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}}) 66 c.Assert(err, check.IsNil) 67 w.Write(b) 68 }) 69 70 mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) { 71 if s.ctrl.reqRes.Err != "" { 72 w.WriteHeader(http.StatusInternalServerError) 73 } 74 b, err := json.Marshal(s.ctrl.reqRes) 75 c.Assert(err, check.IsNil) 76 w.Write(b) 77 defer r.Body.Close() 78 body, err := ioutil.ReadAll(r.Body) 79 c.Assert(err, check.IsNil) 80 authReq := authorization.Request{} 81 err = json.Unmarshal(body, &authReq) 82 c.Assert(err, check.IsNil) 83 84 assertBody(c, authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody) 85 assertAuthHeaders(c, authReq.RequestHeaders) 86 87 // Count only container list api 88 if strings.HasSuffix(authReq.RequestURI, containerListAPI) { 89 s.ctrl.psRequestCnt++ 90 } 91 92 s.ctrl.requestsURIs = append(s.ctrl.requestsURIs, authReq.RequestURI) 93 }) 94 95 mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) { 96 if s.ctrl.resRes.Err != "" { 97 w.WriteHeader(http.StatusInternalServerError) 98 } 99 b, err := json.Marshal(s.ctrl.resRes) 100 c.Assert(err, check.IsNil) 101 w.Write(b) 102 103 defer r.Body.Close() 104 body, err := ioutil.ReadAll(r.Body) 105 c.Assert(err, check.IsNil) 106 authReq := authorization.Request{} 107 err = json.Unmarshal(body, &authReq) 108 c.Assert(err, check.IsNil) 109 110 assertBody(c, authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody) 111 assertAuthHeaders(c, authReq.ResponseHeaders) 112 113 // Count only container list api 114 if strings.HasSuffix(authReq.RequestURI, containerListAPI) { 115 s.ctrl.psResponseCnt++ 116 } 117 }) 118 119 err := os.MkdirAll("/etc/docker/plugins", 0755) 120 c.Assert(err, checker.IsNil) 121 122 fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin) 123 err = ioutil.WriteFile(fileName, []byte(s.server.URL), 0644) 124 c.Assert(err, checker.IsNil) 125 } 126 127 // assertAuthHeaders validates authentication headers are removed 128 func assertAuthHeaders(c *check.C, headers map[string]string) error { 129 for k := range headers { 130 if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") { 131 c.Errorf("Found authentication headers in request '%v'", headers) 132 } 133 } 134 return nil 135 } 136 137 // assertBody asserts that body is removed for non text/json requests 138 func assertBody(c *check.C, requestURI string, headers map[string]string, body []byte) { 139 if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 { 140 //return fmt.Errorf("Body included for authentication endpoint %s", string(body)) 141 c.Errorf("Body included for authentication endpoint %s", string(body)) 142 } 143 144 for k, v := range headers { 145 if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" { 146 return 147 } 148 } 149 if len(body) > 0 { 150 c.Errorf("Body included while it should not (Headers: '%v')", headers) 151 } 152 } 153 154 func (s *DockerAuthzSuite) TearDownSuite(c *check.C) { 155 if s.server == nil { 156 return 157 } 158 159 s.server.Close() 160 161 err := os.RemoveAll("/etc/docker/plugins") 162 c.Assert(err, checker.IsNil) 163 } 164 165 func (s *DockerAuthzSuite) TestAuthZPluginAllowRequest(c *check.C) { 166 // start the daemon and load busybox, --net=none build fails otherwise 167 // cause it needs to pull busybox 168 c.Assert(s.d.StartWithBusybox(), check.IsNil) 169 // restart the daemon and enable the plugin, otherwise busybox loading 170 // is blocked by the plugin itself 171 c.Assert(s.d.Restart("--authorization-plugin="+testAuthZPlugin), check.IsNil) 172 173 s.ctrl.reqRes.Allow = true 174 s.ctrl.resRes.Allow = true 175 176 // Ensure command successful 177 out, err := s.d.Cmd("run", "-d", "busybox", "top") 178 c.Assert(err, check.IsNil) 179 180 id := strings.TrimSpace(out) 181 assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create") 182 assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id)) 183 184 out, err = s.d.Cmd("ps") 185 c.Assert(err, check.IsNil) 186 c.Assert(assertContainerList(out, []string{id}), check.Equals, true) 187 c.Assert(s.ctrl.psRequestCnt, check.Equals, 1) 188 c.Assert(s.ctrl.psResponseCnt, check.Equals, 1) 189 } 190 191 func (s *DockerAuthzSuite) TestAuthZPluginDenyRequest(c *check.C) { 192 err := s.d.Start("--authorization-plugin=" + testAuthZPlugin) 193 c.Assert(err, check.IsNil) 194 s.ctrl.reqRes.Allow = false 195 s.ctrl.reqRes.Msg = unauthorizedMessage 196 197 // Ensure command is blocked 198 res, err := s.d.Cmd("ps") 199 c.Assert(err, check.NotNil) 200 c.Assert(s.ctrl.psRequestCnt, check.Equals, 1) 201 c.Assert(s.ctrl.psResponseCnt, check.Equals, 0) 202 203 // Ensure unauthorized message appears in response 204 c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage)) 205 } 206 207 func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) { 208 err := s.d.Start("--authorization-plugin=" + testAuthZPlugin) 209 c.Assert(err, check.IsNil) 210 s.ctrl.reqRes.Allow = true 211 s.ctrl.resRes.Allow = false 212 s.ctrl.resRes.Msg = unauthorizedMessage 213 214 // Ensure command is blocked 215 res, err := s.d.Cmd("ps") 216 c.Assert(err, check.NotNil) 217 c.Assert(s.ctrl.psRequestCnt, check.Equals, 1) 218 c.Assert(s.ctrl.psResponseCnt, check.Equals, 1) 219 220 // Ensure unauthorized message appears in response 221 c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage)) 222 } 223 224 func (s *DockerAuthzSuite) TestAuthZPluginErrorResponse(c *check.C) { 225 err := s.d.Start("--authorization-plugin=" + testAuthZPlugin) 226 c.Assert(err, check.IsNil) 227 s.ctrl.reqRes.Allow = true 228 s.ctrl.resRes.Err = errorMessage 229 230 // Ensure command is blocked 231 res, err := s.d.Cmd("ps") 232 c.Assert(err, check.NotNil) 233 234 c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage)) 235 } 236 237 func (s *DockerAuthzSuite) TestAuthZPluginErrorRequest(c *check.C) { 238 err := s.d.Start("--authorization-plugin=" + testAuthZPlugin) 239 c.Assert(err, check.IsNil) 240 s.ctrl.reqRes.Err = errorMessage 241 242 // Ensure command is blocked 243 res, err := s.d.Cmd("ps") 244 c.Assert(err, check.NotNil) 245 246 c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage)) 247 } 248 249 func (s *DockerAuthzSuite) TestAuthZPluginEnsureNoDuplicatePluginRegistration(c *check.C) { 250 c.Assert(s.d.Start("--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin), check.IsNil) 251 252 s.ctrl.reqRes.Allow = true 253 s.ctrl.resRes.Allow = true 254 255 out, err := s.d.Cmd("ps") 256 c.Assert(err, check.IsNil, check.Commentf(out)) 257 258 // assert plugin is only called once.. 259 c.Assert(s.ctrl.psRequestCnt, check.Equals, 1) 260 c.Assert(s.ctrl.psResponseCnt, check.Equals, 1) 261 } 262 263 // assertURIRecorded verifies that the given URI was sent and recorded in the authz plugin 264 func assertURIRecorded(c *check.C, uris []string, uri string) { 265 var found bool 266 for _, u := range uris { 267 if strings.Contains(u, uri) { 268 found = true 269 break 270 } 271 } 272 if !found { 273 c.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ",")) 274 } 275 }