github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/integration/plugin/authz/authz_plugin_test.go (about)

     1  // +build !windows
     2  
     3  package authz // import "github.com/docker/docker/integration/plugin/authz"
     4  
     5  import (
     6  	"context"
     7  	"fmt"
     8  	"io"
     9  	"io/ioutil"
    10  	"net"
    11  	"net/http"
    12  	"net/http/httputil"
    13  	"net/url"
    14  	"os"
    15  	"path/filepath"
    16  	"strconv"
    17  	"strings"
    18  	"testing"
    19  	"time"
    20  
    21  	"github.com/docker/docker/api/types"
    22  	eventtypes "github.com/docker/docker/api/types/events"
    23  	"github.com/docker/docker/client"
    24  	"github.com/docker/docker/integration/internal/container"
    25  	"github.com/docker/docker/internal/test/environment"
    26  	"github.com/docker/docker/pkg/archive"
    27  	"github.com/docker/docker/pkg/authorization"
    28  	"github.com/gotestyourself/gotestyourself/assert"
    29  	"github.com/gotestyourself/gotestyourself/skip"
    30  )
    31  
    32  const (
    33  	testAuthZPlugin     = "authzplugin"
    34  	unauthorizedMessage = "User unauthorized authz plugin"
    35  	errorMessage        = "something went wrong..."
    36  	serverVersionAPI    = "/version"
    37  )
    38  
    39  var (
    40  	alwaysAllowed = []string{"/_ping", "/info"}
    41  	ctrl          *authorizationController
    42  )
    43  
    44  type authorizationController struct {
    45  	reqRes          authorization.Response // reqRes holds the plugin response to the initial client request
    46  	resRes          authorization.Response // resRes holds the plugin response to the daemon response
    47  	versionReqCount int                    // versionReqCount counts the number of requests to the server version API endpoint
    48  	versionResCount int                    // versionResCount counts the number of responses from the server version API endpoint
    49  	requestsURIs    []string               // requestsURIs stores all request URIs that are sent to the authorization controller
    50  	reqUser         string
    51  	resUser         string
    52  }
    53  
    54  func setupTestV1(t *testing.T) func() {
    55  	ctrl = &authorizationController{}
    56  	teardown := setupTest(t)
    57  
    58  	err := os.MkdirAll("/etc/docker/plugins", 0755)
    59  	assert.NilError(t, err)
    60  
    61  	fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin)
    62  	err = ioutil.WriteFile(fileName, []byte(server.URL), 0644)
    63  	assert.NilError(t, err)
    64  
    65  	return func() {
    66  		err := os.RemoveAll("/etc/docker/plugins")
    67  		assert.NilError(t, err)
    68  
    69  		teardown()
    70  		ctrl = nil
    71  	}
    72  }
    73  
    74  // check for always allowed endpoints to not inhibit test framework functions
    75  func isAllowed(reqURI string) bool {
    76  	for _, endpoint := range alwaysAllowed {
    77  		if strings.HasSuffix(reqURI, endpoint) {
    78  			return true
    79  		}
    80  	}
    81  	return false
    82  }
    83  
    84  func TestAuthZPluginAllowRequest(t *testing.T) {
    85  	defer setupTestV1(t)()
    86  	ctrl.reqRes.Allow = true
    87  	ctrl.resRes.Allow = true
    88  	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
    89  
    90  	client, err := d.NewClient()
    91  	assert.NilError(t, err)
    92  
    93  	ctx := context.Background()
    94  
    95  	// Ensure command successful
    96  	cID := container.Run(t, ctx, client)
    97  
    98  	assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
    99  	assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", cID))
   100  
   101  	_, err = client.ServerVersion(ctx)
   102  	assert.NilError(t, err)
   103  	assert.Equal(t, 1, ctrl.versionReqCount)
   104  	assert.Equal(t, 1, ctrl.versionResCount)
   105  }
   106  
   107  func TestAuthZPluginTLS(t *testing.T) {
   108  	defer setupTestV1(t)()
   109  	const (
   110  		testDaemonHTTPSAddr = "tcp://localhost:4271"
   111  		cacertPath          = "../../testdata/https/ca.pem"
   112  		serverCertPath      = "../../testdata/https/server-cert.pem"
   113  		serverKeyPath       = "../../testdata/https/server-key.pem"
   114  		clientCertPath      = "../../testdata/https/client-cert.pem"
   115  		clientKeyPath       = "../../testdata/https/client-key.pem"
   116  	)
   117  
   118  	d.Start(t,
   119  		"--authorization-plugin="+testAuthZPlugin,
   120  		"--tlsverify",
   121  		"--tlscacert", cacertPath,
   122  		"--tlscert", serverCertPath,
   123  		"--tlskey", serverKeyPath,
   124  		"-H", testDaemonHTTPSAddr)
   125  
   126  	ctrl.reqRes.Allow = true
   127  	ctrl.resRes.Allow = true
   128  
   129  	client, err := newTLSAPIClient(testDaemonHTTPSAddr, cacertPath, clientCertPath, clientKeyPath)
   130  	assert.NilError(t, err)
   131  
   132  	_, err = client.ServerVersion(context.Background())
   133  	assert.NilError(t, err)
   134  
   135  	assert.Equal(t, "client", ctrl.reqUser)
   136  	assert.Equal(t, "client", ctrl.resUser)
   137  }
   138  
   139  func newTLSAPIClient(host, cacertPath, certPath, keyPath string) (client.APIClient, error) {
   140  	dialer := &net.Dialer{
   141  		KeepAlive: 30 * time.Second,
   142  		Timeout:   30 * time.Second,
   143  	}
   144  	return client.NewClientWithOpts(
   145  		client.WithTLSClientConfig(cacertPath, certPath, keyPath),
   146  		client.WithDialer(dialer),
   147  		client.WithHost(host))
   148  }
   149  
   150  func TestAuthZPluginDenyRequest(t *testing.T) {
   151  	defer setupTestV1(t)()
   152  	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
   153  	ctrl.reqRes.Allow = false
   154  	ctrl.reqRes.Msg = unauthorizedMessage
   155  
   156  	client, err := d.NewClient()
   157  	assert.NilError(t, err)
   158  
   159  	// Ensure command is blocked
   160  	_, err = client.ServerVersion(context.Background())
   161  	assert.Assert(t, err != nil)
   162  	assert.Equal(t, 1, ctrl.versionReqCount)
   163  	assert.Equal(t, 0, ctrl.versionResCount)
   164  
   165  	// Ensure unauthorized message appears in response
   166  	assert.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
   167  }
   168  
   169  // TestAuthZPluginAPIDenyResponse validates that when authorization
   170  // plugin deny the request, the status code is forbidden
   171  func TestAuthZPluginAPIDenyResponse(t *testing.T) {
   172  	defer setupTestV1(t)()
   173  	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
   174  	ctrl.reqRes.Allow = false
   175  	ctrl.resRes.Msg = unauthorizedMessage
   176  
   177  	daemonURL, err := url.Parse(d.Sock())
   178  	assert.NilError(t, err)
   179  
   180  	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
   181  	assert.NilError(t, err)
   182  	client := httputil.NewClientConn(conn, nil)
   183  	req, err := http.NewRequest("GET", "/version", nil)
   184  	assert.NilError(t, err)
   185  	resp, err := client.Do(req)
   186  
   187  	assert.NilError(t, err)
   188  	assert.DeepEqual(t, http.StatusForbidden, resp.StatusCode)
   189  }
   190  
   191  func TestAuthZPluginDenyResponse(t *testing.T) {
   192  	defer setupTestV1(t)()
   193  	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
   194  	ctrl.reqRes.Allow = true
   195  	ctrl.resRes.Allow = false
   196  	ctrl.resRes.Msg = unauthorizedMessage
   197  
   198  	client, err := d.NewClient()
   199  	assert.NilError(t, err)
   200  
   201  	// Ensure command is blocked
   202  	_, err = client.ServerVersion(context.Background())
   203  	assert.Assert(t, err != nil)
   204  	assert.Equal(t, 1, ctrl.versionReqCount)
   205  	assert.Equal(t, 1, ctrl.versionResCount)
   206  
   207  	// Ensure unauthorized message appears in response
   208  	assert.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
   209  }
   210  
   211  // TestAuthZPluginAllowEventStream verifies event stream propagates
   212  // correctly after request pass through by the authorization plugin
   213  func TestAuthZPluginAllowEventStream(t *testing.T) {
   214  	skip.IfCondition(t, testEnv.DaemonInfo.OSType != "linux")
   215  
   216  	defer setupTestV1(t)()
   217  	ctrl.reqRes.Allow = true
   218  	ctrl.resRes.Allow = true
   219  	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
   220  
   221  	client, err := d.NewClient()
   222  	assert.NilError(t, err)
   223  
   224  	ctx := context.Background()
   225  
   226  	startTime := strconv.FormatInt(systemTime(t, client, testEnv).Unix(), 10)
   227  	events, errs, cancel := systemEventsSince(client, startTime)
   228  	defer cancel()
   229  
   230  	// Create a container and wait for the creation events
   231  	cID := container.Run(t, ctx, client)
   232  
   233  	for i := 0; i < 100; i++ {
   234  		c, err := client.ContainerInspect(ctx, cID)
   235  		assert.NilError(t, err)
   236  		if c.State.Running {
   237  			break
   238  		}
   239  		if i == 99 {
   240  			t.Fatal("Container didn't run within 10s")
   241  		}
   242  		time.Sleep(100 * time.Millisecond)
   243  	}
   244  
   245  	created := false
   246  	started := false
   247  	for !created && !started {
   248  		select {
   249  		case event := <-events:
   250  			if event.Type == eventtypes.ContainerEventType && event.Actor.ID == cID {
   251  				if event.Action == "create" {
   252  					created = true
   253  				}
   254  				if event.Action == "start" {
   255  					started = true
   256  				}
   257  			}
   258  		case err := <-errs:
   259  			if err == io.EOF {
   260  				t.Fatal("premature end of event stream")
   261  			}
   262  			assert.NilError(t, err)
   263  		case <-time.After(30 * time.Second):
   264  			// Fail the test
   265  			t.Fatal("event stream timeout")
   266  		}
   267  	}
   268  
   269  	// Ensure both events and container endpoints are passed to the
   270  	// authorization plugin
   271  	assertURIRecorded(t, ctrl.requestsURIs, "/events")
   272  	assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
   273  	assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", cID))
   274  }
   275  
   276  func systemTime(t *testing.T, client client.APIClient, testEnv *environment.Execution) time.Time {
   277  	if testEnv.IsLocalDaemon() {
   278  		return time.Now()
   279  	}
   280  
   281  	ctx := context.Background()
   282  	info, err := client.Info(ctx)
   283  	assert.NilError(t, err)
   284  
   285  	dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
   286  	assert.NilError(t, err, "invalid time format in GET /info response")
   287  	return dt
   288  }
   289  
   290  func systemEventsSince(client client.APIClient, since string) (<-chan eventtypes.Message, <-chan error, func()) {
   291  	eventOptions := types.EventsOptions{
   292  		Since: since,
   293  	}
   294  	ctx, cancel := context.WithCancel(context.Background())
   295  	events, errs := client.Events(ctx, eventOptions)
   296  
   297  	return events, errs, cancel
   298  }
   299  
   300  func TestAuthZPluginErrorResponse(t *testing.T) {
   301  	defer setupTestV1(t)()
   302  	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
   303  	ctrl.reqRes.Allow = true
   304  	ctrl.resRes.Err = errorMessage
   305  
   306  	client, err := d.NewClient()
   307  	assert.NilError(t, err)
   308  
   309  	// Ensure command is blocked
   310  	_, err = client.ServerVersion(context.Background())
   311  	assert.Assert(t, err != nil)
   312  	assert.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage), err.Error())
   313  }
   314  
   315  func TestAuthZPluginErrorRequest(t *testing.T) {
   316  	defer setupTestV1(t)()
   317  	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
   318  	ctrl.reqRes.Err = errorMessage
   319  
   320  	client, err := d.NewClient()
   321  	assert.NilError(t, err)
   322  
   323  	// Ensure command is blocked
   324  	_, err = client.ServerVersion(context.Background())
   325  	assert.Assert(t, err != nil)
   326  	assert.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage), err.Error())
   327  }
   328  
   329  func TestAuthZPluginEnsureNoDuplicatePluginRegistration(t *testing.T) {
   330  	defer setupTestV1(t)()
   331  	d.Start(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
   332  
   333  	ctrl.reqRes.Allow = true
   334  	ctrl.resRes.Allow = true
   335  
   336  	client, err := d.NewClient()
   337  	assert.NilError(t, err)
   338  
   339  	_, err = client.ServerVersion(context.Background())
   340  	assert.NilError(t, err)
   341  
   342  	// assert plugin is only called once..
   343  	assert.Equal(t, 1, ctrl.versionReqCount)
   344  	assert.Equal(t, 1, ctrl.versionResCount)
   345  }
   346  
   347  func TestAuthZPluginEnsureLoadImportWorking(t *testing.T) {
   348  	defer setupTestV1(t)()
   349  	ctrl.reqRes.Allow = true
   350  	ctrl.resRes.Allow = true
   351  	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
   352  
   353  	client, err := d.NewClient()
   354  	assert.NilError(t, err)
   355  
   356  	ctx := context.Background()
   357  
   358  	tmp, err := ioutil.TempDir("", "test-authz-load-import")
   359  	assert.NilError(t, err)
   360  	defer os.RemoveAll(tmp)
   361  
   362  	savedImagePath := filepath.Join(tmp, "save.tar")
   363  
   364  	err = imageSave(client, savedImagePath, "busybox")
   365  	assert.NilError(t, err)
   366  	err = imageLoad(client, savedImagePath)
   367  	assert.NilError(t, err)
   368  
   369  	exportedImagePath := filepath.Join(tmp, "export.tar")
   370  
   371  	cID := container.Run(t, ctx, client)
   372  
   373  	responseReader, err := client.ContainerExport(context.Background(), cID)
   374  	assert.NilError(t, err)
   375  	defer responseReader.Close()
   376  	file, err := os.Create(exportedImagePath)
   377  	assert.NilError(t, err)
   378  	defer file.Close()
   379  	_, err = io.Copy(file, responseReader)
   380  	assert.NilError(t, err)
   381  
   382  	err = imageImport(client, exportedImagePath)
   383  	assert.NilError(t, err)
   384  }
   385  
   386  func TestAuthzPluginEnsureContainerCopyToFrom(t *testing.T) {
   387  	defer setupTestV1(t)()
   388  	ctrl.reqRes.Allow = true
   389  	ctrl.resRes.Allow = true
   390  	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
   391  
   392  	dir, err := ioutil.TempDir("", t.Name())
   393  	assert.Assert(t, err)
   394  	defer os.RemoveAll(dir)
   395  
   396  	f, err := ioutil.TempFile(dir, "send")
   397  	assert.Assert(t, err)
   398  	defer f.Close()
   399  
   400  	buf := make([]byte, 1024)
   401  	fileSize := len(buf) * 1024 * 10
   402  	for written := 0; written < fileSize; {
   403  		n, err := f.Write(buf)
   404  		assert.Assert(t, err)
   405  		written += n
   406  	}
   407  
   408  	ctx := context.Background()
   409  	client, err := d.NewClient()
   410  	assert.Assert(t, err)
   411  
   412  	cID := container.Run(t, ctx, client)
   413  	defer client.ContainerRemove(ctx, cID, types.ContainerRemoveOptions{Force: true})
   414  
   415  	_, err = f.Seek(0, io.SeekStart)
   416  	assert.Assert(t, err)
   417  
   418  	srcInfo, err := archive.CopyInfoSourcePath(f.Name(), false)
   419  	assert.Assert(t, err)
   420  	srcArchive, err := archive.TarResource(srcInfo)
   421  	assert.Assert(t, err)
   422  	defer srcArchive.Close()
   423  
   424  	dstDir, preparedArchive, err := archive.PrepareArchiveCopy(srcArchive, srcInfo, archive.CopyInfo{Path: "/test"})
   425  	assert.Assert(t, err)
   426  
   427  	err = client.CopyToContainer(ctx, cID, dstDir, preparedArchive, types.CopyToContainerOptions{})
   428  	assert.Assert(t, err)
   429  
   430  	rdr, _, err := client.CopyFromContainer(ctx, cID, "/test")
   431  	assert.Assert(t, err)
   432  	_, err = io.Copy(ioutil.Discard, rdr)
   433  	assert.Assert(t, err)
   434  }
   435  
   436  func imageSave(client client.APIClient, path, image string) error {
   437  	ctx := context.Background()
   438  	responseReader, err := client.ImageSave(ctx, []string{image})
   439  	if err != nil {
   440  		return err
   441  	}
   442  	defer responseReader.Close()
   443  	file, err := os.Create(path)
   444  	if err != nil {
   445  		return err
   446  	}
   447  	defer file.Close()
   448  	_, err = io.Copy(file, responseReader)
   449  	return err
   450  }
   451  
   452  func imageLoad(client client.APIClient, path string) error {
   453  	file, err := os.Open(path)
   454  	if err != nil {
   455  		return err
   456  	}
   457  	defer file.Close()
   458  	quiet := true
   459  	ctx := context.Background()
   460  	response, err := client.ImageLoad(ctx, file, quiet)
   461  	if err != nil {
   462  		return err
   463  	}
   464  	defer response.Body.Close()
   465  	return nil
   466  }
   467  
   468  func imageImport(client client.APIClient, path string) error {
   469  	file, err := os.Open(path)
   470  	if err != nil {
   471  		return err
   472  	}
   473  	defer file.Close()
   474  	options := types.ImageImportOptions{}
   475  	ref := ""
   476  	source := types.ImageImportSource{
   477  		Source:     file,
   478  		SourceName: "-",
   479  	}
   480  	ctx := context.Background()
   481  	responseReader, err := client.ImageImport(ctx, source, ref, options)
   482  	if err != nil {
   483  		return err
   484  	}
   485  	defer responseReader.Close()
   486  	return nil
   487  }
   488  
   489  func TestAuthZPluginHeader(t *testing.T) {
   490  	defer setupTestV1(t)()
   491  	ctrl.reqRes.Allow = true
   492  	ctrl.resRes.Allow = true
   493  	d.StartWithBusybox(t, "--debug", "--authorization-plugin="+testAuthZPlugin)
   494  
   495  	daemonURL, err := url.Parse(d.Sock())
   496  	assert.NilError(t, err)
   497  
   498  	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
   499  	assert.NilError(t, err)
   500  	client := httputil.NewClientConn(conn, nil)
   501  	req, err := http.NewRequest("GET", "/version", nil)
   502  	assert.NilError(t, err)
   503  	resp, err := client.Do(req)
   504  	assert.NilError(t, err)
   505  	assert.Equal(t, "application/json", resp.Header["Content-Type"][0])
   506  }
   507  
   508  // assertURIRecorded verifies that the given URI was sent and recorded
   509  // in the authz plugin
   510  func assertURIRecorded(t *testing.T, uris []string, uri string) {
   511  	var found bool
   512  	for _, u := range uris {
   513  		if strings.Contains(u, uri) {
   514  			found = true
   515  			break
   516  		}
   517  	}
   518  	if !found {
   519  		t.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ","))
   520  	}
   521  }