docker_cli_authz_unix_test.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // +build !windows
  2. package main
  3. import (
  4. "encoding/json"
  5. "fmt"
  6. "github.com/docker/docker/pkg/authorization"
  7. "github.com/docker/docker/pkg/integration/checker"
  8. "github.com/docker/docker/pkg/plugins"
  9. "github.com/go-check/check"
  10. "io/ioutil"
  11. "net/http"
  12. "net/http/httptest"
  13. "os"
  14. "strings"
  15. )
  16. const testAuthZPlugin = "authzplugin"
  17. const unauthorizedMessage = "User unauthorized authz plugin"
  18. const containerListAPI = "/containers/json"
  19. func init() {
  20. check.Suite(&DockerAuthzSuite{
  21. ds: &DockerSuite{},
  22. })
  23. }
  24. type DockerAuthzSuite struct {
  25. server *httptest.Server
  26. ds *DockerSuite
  27. d *Daemon
  28. ctrl *authorizationController
  29. }
  30. type authorizationController struct {
  31. reqRes authorization.Response // reqRes holds the plugin response to the initial client request
  32. resRes authorization.Response // resRes holds the plugin response to the daemon response
  33. psRequestCnt int // psRequestCnt counts the number of calls to list container request api
  34. psResponseCnt int // psResponseCnt counts the number of calls to list containers response API
  35. requestsURIs []string // requestsURIs stores all request URIs that are sent to the authorization controller
  36. }
  37. func (s *DockerAuthzSuite) SetUpTest(c *check.C) {
  38. s.d = NewDaemon(c)
  39. s.ctrl = &authorizationController{}
  40. }
  41. func (s *DockerAuthzSuite) TearDownTest(c *check.C) {
  42. s.d.Stop()
  43. s.ds.TearDownTest(c)
  44. s.ctrl = nil
  45. }
  46. func (s *DockerAuthzSuite) SetUpSuite(c *check.C) {
  47. mux := http.NewServeMux()
  48. s.server = httptest.NewServer(mux)
  49. c.Assert(s.server, check.NotNil, check.Commentf("Failed to start a HTTP Server"))
  50. mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
  51. b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}})
  52. c.Assert(err, check.IsNil)
  53. w.Write(b)
  54. })
  55. mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) {
  56. b, err := json.Marshal(s.ctrl.reqRes)
  57. w.Write(b)
  58. c.Assert(err, check.IsNil)
  59. defer r.Body.Close()
  60. body, err := ioutil.ReadAll(r.Body)
  61. c.Assert(err, check.IsNil)
  62. authReq := authorization.Request{}
  63. err = json.Unmarshal(body, &authReq)
  64. c.Assert(err, check.IsNil)
  65. assertBody(c, authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody)
  66. assertAuthHeaders(c, authReq.RequestHeaders)
  67. // Count only container list api
  68. if strings.HasSuffix(authReq.RequestURI, containerListAPI) {
  69. s.ctrl.psRequestCnt++
  70. }
  71. s.ctrl.requestsURIs = append(s.ctrl.requestsURIs, authReq.RequestURI)
  72. })
  73. mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) {
  74. b, err := json.Marshal(s.ctrl.resRes)
  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. assertBody(c, authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody)
  84. assertAuthHeaders(c, authReq.ResponseHeaders)
  85. // Count only container list api
  86. if strings.HasSuffix(authReq.RequestURI, containerListAPI) {
  87. s.ctrl.psResponseCnt++
  88. }
  89. })
  90. err := os.MkdirAll("/etc/docker/plugins", 0755)
  91. c.Assert(err, checker.IsNil)
  92. fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin)
  93. err = ioutil.WriteFile(fileName, []byte(s.server.URL), 0644)
  94. c.Assert(err, checker.IsNil)
  95. }
  96. // assertAuthHeaders validates authentication headers are removed
  97. func assertAuthHeaders(c *check.C, headers map[string]string) error {
  98. for k := range headers {
  99. if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") {
  100. c.Errorf("Found authentication headers in request '%v'", headers)
  101. }
  102. }
  103. return nil
  104. }
  105. // assertBody asserts that body is removed for non text/json requests
  106. func assertBody(c *check.C, requestURI string, headers map[string]string, body []byte) {
  107. if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 {
  108. //return fmt.Errorf("Body included for authentication endpoint %s", string(body))
  109. c.Errorf("Body included for authentication endpoint %s", string(body))
  110. }
  111. for k, v := range headers {
  112. if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" {
  113. return
  114. }
  115. }
  116. if len(body) > 0 {
  117. c.Errorf("Body included while it should not (Headers: '%v')", headers)
  118. }
  119. }
  120. func (s *DockerAuthzSuite) TearDownSuite(c *check.C) {
  121. if s.server == nil {
  122. return
  123. }
  124. s.server.Close()
  125. err := os.RemoveAll("/etc/docker/plugins")
  126. c.Assert(err, checker.IsNil)
  127. }
  128. func (s *DockerAuthzSuite) TestAuthZPluginAllowRequest(c *check.C) {
  129. err := s.d.Start("--authz-plugins=" + testAuthZPlugin)
  130. c.Assert(err, check.IsNil)
  131. s.ctrl.reqRes.Allow = true
  132. s.ctrl.resRes.Allow = true
  133. // Ensure command successful
  134. out, err := s.d.Cmd("run", "-d", "--name", "container1", "busybox:latest", "top")
  135. c.Assert(err, check.IsNil)
  136. // Extract the id of the created container
  137. res := strings.Split(strings.TrimSpace(out), "\n")
  138. id := res[len(res)-1]
  139. assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create")
  140. assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id))
  141. out, err = s.d.Cmd("ps")
  142. c.Assert(err, check.IsNil)
  143. c.Assert(assertContainerList(out, []string{id}), check.Equals, true)
  144. c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
  145. c.Assert(s.ctrl.psResponseCnt, check.Equals, 1)
  146. }
  147. func (s *DockerAuthzSuite) TestAuthZPluginDenyRequest(c *check.C) {
  148. err := s.d.Start("--authz-plugins=" + testAuthZPlugin)
  149. c.Assert(err, check.IsNil)
  150. s.ctrl.reqRes.Allow = false
  151. s.ctrl.reqRes.Msg = unauthorizedMessage
  152. // Ensure command is blocked
  153. res, err := s.d.Cmd("ps")
  154. c.Assert(err, check.NotNil)
  155. c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
  156. c.Assert(s.ctrl.psResponseCnt, check.Equals, 0)
  157. // Ensure unauthorized message appears in response
  158. c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: %s\n", unauthorizedMessage))
  159. }
  160. func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) {
  161. err := s.d.Start("--authz-plugins=" + testAuthZPlugin)
  162. c.Assert(err, check.IsNil)
  163. s.ctrl.reqRes.Allow = true
  164. s.ctrl.resRes.Allow = false
  165. s.ctrl.resRes.Msg = unauthorizedMessage
  166. // Ensure command is blocked
  167. res, err := s.d.Cmd("ps")
  168. c.Assert(err, check.NotNil)
  169. c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
  170. c.Assert(s.ctrl.psResponseCnt, check.Equals, 1)
  171. // Ensure unauthorized message appears in response
  172. c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: %s\n", unauthorizedMessage))
  173. }
  174. // assertURIRecorded verifies that the given URI was sent and recorded in the authz plugin
  175. func assertURIRecorded(c *check.C, uris []string, uri string) {
  176. found := false
  177. for _, u := range uris {
  178. if strings.Contains(u, uri) {
  179. found = true
  180. }
  181. }
  182. if !found {
  183. c.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ","))
  184. }
  185. }