github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/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("--authz-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("--authz-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("--authz-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("--authz-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("--authz-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("--authz-plugin="+testAuthZPlugin, "--authz-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  }