瀏覽代碼

Merge pull request #34941 from dsheets/authz-tests-api-port

integration/plugin/authz: port tests from integration-cli
Vincent Demeester 7 年之前
父節點
當前提交
fe33ea9b0e

+ 1 - 1
hack/make/.integration-test-helpers

@@ -15,7 +15,7 @@ source "$SCRIPTDIR/make/.go-autogen"
 
 integration_api_dirs=${TEST_INTEGRATION_DIR:-"$(
 	find ./integration -type d |
-	grep -vE '^(./integration$|./integration/util)')"}
+	grep -vE '(^./integration($|/util)|/testdata)')"}
 
 run_test_integration() {
 	[[ "$TESTFLAGS" != *-check.f* ]] && run_test_integration_suites

+ 0 - 162
integration-cli/docker_cli_authz_plugin_v2_test.go

@@ -1,162 +0,0 @@
-// +build !windows
-
-package main
-
-import (
-	"fmt"
-
-	"github.com/docker/docker/integration-cli/checker"
-	"github.com/docker/docker/integration-cli/daemon"
-	"github.com/go-check/check"
-)
-
-var (
-	authzPluginName            = "riyaz/authz-no-volume-plugin"
-	authzPluginTag             = "latest"
-	authzPluginNameWithTag     = authzPluginName + ":" + authzPluginTag
-	authzPluginBadManifestName = "riyaz/authz-plugin-bad-manifest"
-	nonexistentAuthzPluginName = "riyaz/nonexistent-authz-plugin"
-)
-
-func init() {
-	check.Suite(&DockerAuthzV2Suite{
-		ds: &DockerSuite{},
-	})
-}
-
-type DockerAuthzV2Suite struct {
-	ds *DockerSuite
-	d  *daemon.Daemon
-}
-
-func (s *DockerAuthzV2Suite) SetUpTest(c *check.C) {
-	testRequires(c, DaemonIsLinux, Network, SameHostDaemon)
-	s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
-		Experimental: testEnv.ExperimentalDaemon(),
-	})
-	s.d.Start(c)
-}
-
-func (s *DockerAuthzV2Suite) TearDownTest(c *check.C) {
-	if s.d != nil {
-		s.d.Stop(c)
-		s.ds.TearDownTest(c)
-	}
-}
-
-func (s *DockerAuthzV2Suite) TestAuthZPluginAllowNonVolumeRequest(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
-
-	// Install authz plugin
-	_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
-	c.Assert(err, checker.IsNil)
-	// start the daemon with the plugin and load busybox, --net=none build fails otherwise
-	// because it needs to pull busybox
-	s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
-	s.d.LoadBusybox(c)
-
-	// defer disabling the plugin
-	defer func() {
-		s.d.Restart(c)
-		_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
-		c.Assert(err, checker.IsNil)
-		_, err = s.d.Cmd("plugin", "rm", authzPluginNameWithTag)
-		c.Assert(err, checker.IsNil)
-	}()
-
-	// Ensure docker run command and accompanying docker ps are successful
-	_, err = s.d.Cmd("run", "-d", "busybox", "top")
-	c.Assert(err, check.IsNil)
-}
-
-func (s *DockerAuthzV2Suite) TestAuthZPluginDisable(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
-	// Install authz plugin
-	_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
-	c.Assert(err, checker.IsNil)
-	// start the daemon with the plugin and load busybox, --net=none build fails otherwise
-	// because it needs to pull busybox
-	s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
-	s.d.LoadBusybox(c)
-
-	// defer removing the plugin
-	defer func() {
-		s.d.Restart(c)
-		_, err = s.d.Cmd("plugin", "rm", "-f", authzPluginNameWithTag)
-		c.Assert(err, checker.IsNil)
-	}()
-
-	out, err := s.d.Cmd("volume", "create")
-	c.Assert(err, check.NotNil)
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
-
-	// disable the plugin
-	_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
-	c.Assert(err, checker.IsNil)
-
-	// now test to see if the docker api works.
-	_, err = s.d.Cmd("volume", "create")
-	c.Assert(err, checker.IsNil)
-}
-
-func (s *DockerAuthzV2Suite) TestAuthZPluginRejectVolumeRequests(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
-	// Install authz plugin
-	_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
-	c.Assert(err, checker.IsNil)
-
-	// restart the daemon with the plugin
-	s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
-
-	// defer disabling the plugin
-	defer func() {
-		s.d.Restart(c)
-		_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
-		c.Assert(err, checker.IsNil)
-		_, err = s.d.Cmd("plugin", "rm", authzPluginNameWithTag)
-		c.Assert(err, checker.IsNil)
-	}()
-
-	out, err := s.d.Cmd("volume", "create")
-	c.Assert(err, check.NotNil)
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
-
-	out, err = s.d.Cmd("volume", "ls")
-	c.Assert(err, check.NotNil)
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
-
-	// The plugin will block the command before it can determine the volume does not exist
-	out, err = s.d.Cmd("volume", "rm", "test")
-	c.Assert(err, check.NotNil)
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
-
-	out, err = s.d.Cmd("volume", "inspect", "test")
-	c.Assert(err, check.NotNil)
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
-
-	out, err = s.d.Cmd("volume", "prune", "-f")
-	c.Assert(err, check.NotNil)
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
-}
-
-func (s *DockerAuthzV2Suite) TestAuthZPluginBadManifestFailsDaemonStart(c *check.C) {
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
-	// Install authz plugin with bad manifest
-	_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginBadManifestName)
-	c.Assert(err, checker.IsNil)
-
-	// start the daemon with the plugin, it will error
-	c.Assert(s.d.RestartWithError("--authorization-plugin="+authzPluginBadManifestName), check.NotNil)
-
-	// restarting the daemon without requiring the plugin will succeed
-	s.d.Restart(c)
-}
-
-func (s *DockerAuthzV2Suite) TestNonexistentAuthZPluginFailsDaemonStart(c *check.C) {
-	testRequires(c, DaemonIsLinux, Network)
-	// start the daemon with a non-existent authz plugin, it will error
-	c.Assert(s.d.RestartWithError("--authorization-plugin="+nonexistentAuthzPluginName), check.NotNil)
-
-	// restarting the daemon without requiring the plugin will succeed
-	s.d.Start(c)
-}

+ 0 - 470
integration-cli/docker_cli_authz_unix_test.go

@@ -1,470 +0,0 @@
-// +build !windows
-
-package main
-
-import (
-	"encoding/json"
-	"fmt"
-	"io/ioutil"
-	"net/http"
-	"net/http/httptest"
-	"os"
-	"path/filepath"
-	"strings"
-
-	"bufio"
-	"bytes"
-	"os/exec"
-	"strconv"
-	"time"
-
-	"net"
-	"net/http/httputil"
-	"net/url"
-
-	"github.com/docker/docker/integration-cli/checker"
-	"github.com/docker/docker/integration-cli/daemon"
-	"github.com/docker/docker/pkg/authorization"
-	"github.com/docker/docker/pkg/plugins"
-	"github.com/go-check/check"
-)
-
-const (
-	testAuthZPlugin     = "authzplugin"
-	unauthorizedMessage = "User unauthorized authz plugin"
-	errorMessage        = "something went wrong..."
-	containerListAPI    = "/containers/json"
-)
-
-var (
-	alwaysAllowed = []string{"/_ping", "/info"}
-)
-
-func init() {
-	check.Suite(&DockerAuthzSuite{
-		ds: &DockerSuite{},
-	})
-}
-
-type DockerAuthzSuite struct {
-	server *httptest.Server
-	ds     *DockerSuite
-	d      *daemon.Daemon
-	ctrl   *authorizationController
-}
-
-type authorizationController struct {
-	reqRes        authorization.Response // reqRes holds the plugin response to the initial client request
-	resRes        authorization.Response // resRes holds the plugin response to the daemon response
-	psRequestCnt  int                    // psRequestCnt counts the number of calls to list container request api
-	psResponseCnt int                    // psResponseCnt counts the number of calls to list containers response API
-	requestsURIs  []string               // requestsURIs stores all request URIs that are sent to the authorization controller
-	reqUser       string
-	resUser       string
-}
-
-func (s *DockerAuthzSuite) SetUpTest(c *check.C) {
-	testRequires(c, SameHostDaemon)
-	s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
-		Experimental: testEnv.ExperimentalDaemon(),
-	})
-	s.ctrl = &authorizationController{}
-}
-
-func (s *DockerAuthzSuite) TearDownTest(c *check.C) {
-	if s.d != nil {
-		s.d.Stop(c)
-		s.ds.TearDownTest(c)
-		s.ctrl = nil
-	}
-}
-
-func (s *DockerAuthzSuite) SetUpSuite(c *check.C) {
-	mux := http.NewServeMux()
-	s.server = httptest.NewServer(mux)
-
-	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
-		b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}})
-		c.Assert(err, check.IsNil)
-		w.Write(b)
-	})
-
-	mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) {
-		defer r.Body.Close()
-		body, err := ioutil.ReadAll(r.Body)
-		c.Assert(err, check.IsNil)
-		authReq := authorization.Request{}
-		err = json.Unmarshal(body, &authReq)
-		c.Assert(err, check.IsNil)
-
-		assertBody(c, authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody)
-		assertAuthHeaders(c, authReq.RequestHeaders)
-
-		// Count only container list api
-		if strings.HasSuffix(authReq.RequestURI, containerListAPI) {
-			s.ctrl.psRequestCnt++
-		}
-
-		s.ctrl.requestsURIs = append(s.ctrl.requestsURIs, authReq.RequestURI)
-
-		reqRes := s.ctrl.reqRes
-		if isAllowed(authReq.RequestURI) {
-			reqRes = authorization.Response{Allow: true}
-		}
-		if reqRes.Err != "" {
-			w.WriteHeader(http.StatusInternalServerError)
-		}
-		b, err := json.Marshal(reqRes)
-		c.Assert(err, check.IsNil)
-		s.ctrl.reqUser = authReq.User
-		w.Write(b)
-	})
-
-	mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) {
-		defer r.Body.Close()
-		body, err := ioutil.ReadAll(r.Body)
-		c.Assert(err, check.IsNil)
-		authReq := authorization.Request{}
-		err = json.Unmarshal(body, &authReq)
-		c.Assert(err, check.IsNil)
-
-		assertBody(c, authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody)
-		assertAuthHeaders(c, authReq.ResponseHeaders)
-
-		// Count only container list api
-		if strings.HasSuffix(authReq.RequestURI, containerListAPI) {
-			s.ctrl.psResponseCnt++
-		}
-		resRes := s.ctrl.resRes
-		if isAllowed(authReq.RequestURI) {
-			resRes = authorization.Response{Allow: true}
-		}
-		if resRes.Err != "" {
-			w.WriteHeader(http.StatusInternalServerError)
-		}
-		b, err := json.Marshal(resRes)
-		c.Assert(err, check.IsNil)
-		s.ctrl.resUser = authReq.User
-		w.Write(b)
-	})
-
-	err := os.MkdirAll("/etc/docker/plugins", 0755)
-	c.Assert(err, checker.IsNil)
-
-	fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin)
-	err = ioutil.WriteFile(fileName, []byte(s.server.URL), 0644)
-	c.Assert(err, checker.IsNil)
-}
-
-// check for always allowed endpoints to not inhibit test framework functions
-func isAllowed(reqURI string) bool {
-	for _, endpoint := range alwaysAllowed {
-		if strings.HasSuffix(reqURI, endpoint) {
-			return true
-		}
-	}
-	return false
-}
-
-// assertAuthHeaders validates authentication headers are removed
-func assertAuthHeaders(c *check.C, headers map[string]string) error {
-	for k := range headers {
-		if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") {
-			c.Errorf("Found authentication headers in request '%v'", headers)
-		}
-	}
-	return nil
-}
-
-// assertBody asserts that body is removed for non text/json requests
-func assertBody(c *check.C, requestURI string, headers map[string]string, body []byte) {
-	if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 {
-		//return fmt.Errorf("Body included for authentication endpoint %s", string(body))
-		c.Errorf("Body included for authentication endpoint %s", string(body))
-	}
-
-	for k, v := range headers {
-		if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" {
-			return
-		}
-	}
-	if len(body) > 0 {
-		c.Errorf("Body included while it should not (Headers: '%v')", headers)
-	}
-}
-
-func (s *DockerAuthzSuite) TearDownSuite(c *check.C) {
-	if s.server == nil {
-		return
-	}
-
-	s.server.Close()
-
-	err := os.RemoveAll("/etc/docker/plugins")
-	c.Assert(err, checker.IsNil)
-}
-
-func (s *DockerAuthzSuite) TestAuthZPluginAllowRequest(c *check.C) {
-	// start the daemon and load busybox, --net=none build fails otherwise
-	// cause it needs to pull busybox
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
-	s.ctrl.reqRes.Allow = true
-	s.ctrl.resRes.Allow = true
-	s.d.LoadBusybox(c)
-
-	// Ensure command successful
-	out, err := s.d.Cmd("run", "-d", "busybox", "top")
-	c.Assert(err, check.IsNil)
-
-	id := strings.TrimSpace(out)
-	assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create")
-	assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id))
-}
-
-func (s *DockerAuthzSuite) TestAuthZPluginTls(c *check.C) {
-
-	const testDaemonHTTPSAddr = "tcp://localhost:4271"
-	// start the daemon and load busybox, --net=none build fails otherwise
-	// cause it needs to pull busybox
-	s.d.Start(c,
-		"--authorization-plugin="+testAuthZPlugin,
-		"--tlsverify",
-		"--tlscacert",
-		"fixtures/https/ca.pem",
-		"--tlscert",
-		"fixtures/https/server-cert.pem",
-		"--tlskey",
-		"fixtures/https/server-key.pem",
-		"-H", testDaemonHTTPSAddr)
-
-	s.ctrl.reqRes.Allow = true
-	s.ctrl.resRes.Allow = true
-
-	out, _ := dockerCmd(
-		c,
-		"--tlsverify",
-		"--tlscacert", "fixtures/https/ca.pem",
-		"--tlscert", "fixtures/https/client-cert.pem",
-		"--tlskey", "fixtures/https/client-key.pem",
-		"-H",
-		testDaemonHTTPSAddr,
-		"version",
-	)
-	if !strings.Contains(out, "Server") {
-		c.Fatalf("docker version should return information of server side")
-	}
-
-	c.Assert(s.ctrl.reqUser, check.Equals, "client")
-	c.Assert(s.ctrl.resUser, check.Equals, "client")
-}
-
-func (s *DockerAuthzSuite) TestAuthZPluginDenyRequest(c *check.C) {
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
-	s.ctrl.reqRes.Allow = false
-	s.ctrl.reqRes.Msg = unauthorizedMessage
-
-	// Ensure command is blocked
-	res, err := s.d.Cmd("ps")
-	c.Assert(err, check.NotNil)
-	c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
-	c.Assert(s.ctrl.psResponseCnt, check.Equals, 0)
-
-	// Ensure unauthorized message appears in response
-	c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage))
-}
-
-// TestAuthZPluginAPIDenyResponse validates that when authorization plugin deny the request, the status code is forbidden
-func (s *DockerAuthzSuite) TestAuthZPluginAPIDenyResponse(c *check.C) {
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
-	s.ctrl.reqRes.Allow = false
-	s.ctrl.resRes.Msg = unauthorizedMessage
-
-	daemonURL, err := url.Parse(s.d.Sock())
-
-	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
-	c.Assert(err, check.IsNil)
-	client := httputil.NewClientConn(conn, nil)
-	req, err := http.NewRequest("GET", "/version", nil)
-	c.Assert(err, check.IsNil)
-	resp, err := client.Do(req)
-
-	c.Assert(err, check.IsNil)
-	c.Assert(resp.StatusCode, checker.Equals, http.StatusForbidden)
-	c.Assert(err, checker.IsNil)
-}
-
-func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) {
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
-	s.ctrl.reqRes.Allow = true
-	s.ctrl.resRes.Allow = false
-	s.ctrl.resRes.Msg = unauthorizedMessage
-
-	// Ensure command is blocked
-	res, err := s.d.Cmd("ps")
-	c.Assert(err, check.NotNil)
-	c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
-	c.Assert(s.ctrl.psResponseCnt, check.Equals, 1)
-
-	// Ensure unauthorized message appears in response
-	c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage))
-}
-
-// TestAuthZPluginAllowEventStream verifies event stream propagates correctly after request pass through by the authorization plugin
-func (s *DockerAuthzSuite) TestAuthZPluginAllowEventStream(c *check.C) {
-	testRequires(c, DaemonIsLinux)
-
-	// start the daemon and load busybox to avoid pulling busybox from Docker Hub
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
-	s.ctrl.reqRes.Allow = true
-	s.ctrl.resRes.Allow = true
-	s.d.LoadBusybox(c)
-
-	startTime := strconv.FormatInt(daemonTime(c).Unix(), 10)
-	// Add another command to to enable event pipelining
-	eventsCmd := exec.Command(dockerBinary, "--host", s.d.Sock(), "events", "--since", startTime)
-	stdout, err := eventsCmd.StdoutPipe()
-	if err != nil {
-		c.Assert(err, check.IsNil)
-	}
-
-	observer := eventObserver{
-		buffer:    new(bytes.Buffer),
-		command:   eventsCmd,
-		scanner:   bufio.NewScanner(stdout),
-		startTime: startTime,
-	}
-
-	err = observer.Start()
-	c.Assert(err, checker.IsNil)
-	defer observer.Stop()
-
-	// Create a container and wait for the creation events
-	out, err := s.d.Cmd("run", "-d", "busybox", "top")
-	c.Assert(err, check.IsNil, check.Commentf(out))
-	containerID := strings.TrimSpace(out)
-	c.Assert(s.d.WaitRun(containerID), checker.IsNil)
-
-	events := map[string]chan bool{
-		"create": make(chan bool, 1),
-		"start":  make(chan bool, 1),
-	}
-
-	matcher := matchEventLine(containerID, "container", events)
-	processor := processEventMatch(events)
-	go observer.Match(matcher, processor)
-
-	// Ensure all events are received
-	for event, eventChannel := range events {
-
-		select {
-		case <-time.After(30 * time.Second):
-			// Fail the test
-			observer.CheckEventError(c, containerID, event, matcher)
-			c.FailNow()
-		case <-eventChannel:
-			// Ignore, event received
-		}
-	}
-
-	// Ensure both events and container endpoints are passed to the authorization plugin
-	assertURIRecorded(c, s.ctrl.requestsURIs, "/events")
-	assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create")
-	assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", containerID))
-}
-
-func (s *DockerAuthzSuite) TestAuthZPluginErrorResponse(c *check.C) {
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
-	s.ctrl.reqRes.Allow = true
-	s.ctrl.resRes.Err = errorMessage
-
-	// Ensure command is blocked
-	res, err := s.d.Cmd("ps")
-	c.Assert(err, check.NotNil)
-
-	c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage))
-}
-
-func (s *DockerAuthzSuite) TestAuthZPluginErrorRequest(c *check.C) {
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
-	s.ctrl.reqRes.Err = errorMessage
-
-	// Ensure command is blocked
-	res, err := s.d.Cmd("ps")
-	c.Assert(err, check.NotNil)
-
-	c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage))
-}
-
-func (s *DockerAuthzSuite) TestAuthZPluginEnsureNoDuplicatePluginRegistration(c *check.C) {
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
-
-	s.ctrl.reqRes.Allow = true
-	s.ctrl.resRes.Allow = true
-
-	out, err := s.d.Cmd("ps")
-	c.Assert(err, check.IsNil, check.Commentf(out))
-
-	// assert plugin is only called once..
-	c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
-	c.Assert(s.ctrl.psResponseCnt, check.Equals, 1)
-}
-
-func (s *DockerAuthzSuite) TestAuthZPluginEnsureLoadImportWorking(c *check.C) {
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
-	s.ctrl.reqRes.Allow = true
-	s.ctrl.resRes.Allow = true
-	s.d.LoadBusybox(c)
-
-	tmp, err := ioutil.TempDir("", "test-authz-load-import")
-	c.Assert(err, check.IsNil)
-	defer os.RemoveAll(tmp)
-
-	savedImagePath := filepath.Join(tmp, "save.tar")
-
-	out, err := s.d.Cmd("save", "-o", savedImagePath, "busybox")
-	c.Assert(err, check.IsNil, check.Commentf(out))
-	out, err = s.d.Cmd("load", "--input", savedImagePath)
-	c.Assert(err, check.IsNil, check.Commentf(out))
-
-	exportedImagePath := filepath.Join(tmp, "export.tar")
-
-	out, err = s.d.Cmd("run", "-d", "--name", "testexport", "busybox")
-	c.Assert(err, check.IsNil, check.Commentf(out))
-	out, err = s.d.Cmd("export", "-o", exportedImagePath, "testexport")
-	c.Assert(err, check.IsNil, check.Commentf(out))
-	out, err = s.d.Cmd("import", exportedImagePath)
-	c.Assert(err, check.IsNil, check.Commentf(out))
-}
-
-func (s *DockerAuthzSuite) TestAuthZPluginHeader(c *check.C) {
-	s.d.Start(c, "--debug", "--authorization-plugin="+testAuthZPlugin)
-	s.ctrl.reqRes.Allow = true
-	s.ctrl.resRes.Allow = true
-	s.d.LoadBusybox(c)
-
-	daemonURL, err := url.Parse(s.d.Sock())
-
-	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
-	c.Assert(err, check.IsNil)
-	client := httputil.NewClientConn(conn, nil)
-	req, err := http.NewRequest("GET", "/version", nil)
-	c.Assert(err, check.IsNil)
-	resp, err := client.Do(req)
-
-	c.Assert(err, check.IsNil)
-	c.Assert(resp.Header["Content-Type"][0], checker.Equals, "application/json")
-}
-
-// assertURIRecorded verifies that the given URI was sent and recorded in the authz plugin
-func assertURIRecorded(c *check.C, uris []string, uri string) {
-	var found bool
-	for _, u := range uris {
-		if strings.Contains(u, uri) {
-			found = true
-			break
-		}
-	}
-	if !found {
-		c.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ","))
-	}
-}

+ 0 - 23
integration-cli/fixtures/https/ca.pem

@@ -1,23 +0,0 @@
------BEGIN CERTIFICATE-----
-MIID0TCCAzqgAwIBAgIJAP2r7GqEJwSnMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
-VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMG
-A1UEChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMI
-Y2hhbmdlbWUxETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWls
-QGhvc3QuZG9tYWluMB4XDTEzMTIwMzE2NTYzMFoXDTIzMTIwMTE2NTYzMFowgaIx
-CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2Nv
-MRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYD
-VQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEW
-EG1haWxAaG9zdC5kb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAn
-0xDw+5y7ZptQacq66pUhRu82JP2WU6IDgo5QUtNU6/CX5PwQATe/OnYTZQFbksxp
-AU9boG0FCkgxfsgPYXEuZxVEGKI2fxfKHOZZI8mrkWmj6eWU/0cvCjGVc9rTITP5
-sNQvg+hORyVDdNp2IdsbMJayiB3AQYMFx3vSDOMTAgMBAAGjggELMIIBBzAdBgNV
-HQ4EFgQUZu7DFz09q0QBa2+ymRm9qgK1NPswgdcGA1UdIwSBzzCBzIAUZu7DFz09
-q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
-QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
-ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
-Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
-hCcEpzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAF8fJKKM+/oOdnNi
-zEd0M1+PmZOyqvjYQn/2ZR8UHH6Imgc/OPQKZXf0bVE1Txc/DaUNn9Isd1SuCuaE
-ic3vAIYYU7PmgeNN6vwec48V96T7jr+GAi6AVMhQEc2hHCfVtx11Xx+x6aHDZzJt
-Zxtf5lL6KSO9Y+EFwM+rju6hm5hW
------END CERTIFICATE-----

+ 1 - 0
integration-cli/fixtures/https/ca.pem

@@ -0,0 +1 @@
+../../../integration/testdata/https/ca.pem

+ 0 - 73
integration-cli/fixtures/https/client-cert.pem

@@ -1,73 +0,0 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 3 (0x3)
-    Signature Algorithm: sha1WithRSAEncryption
-        Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
-        Validity
-            Not Before: Dec  4 14:17:54 2013 GMT
-            Not After : Dec  2 14:17:54 2023 GMT
-        Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=client/name=changeme/emailAddress=mail@host.domain
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                Public-Key: (1024 bit)
-                Modulus:
-                    00:ca:c9:05:d0:09:4e:3e:a4:fc:d5:14:f4:a5:e8:
-                    34:d3:6b:51:e3:f3:62:ea:a1:f0:e8:ed:c4:2a:bc:
-                    f0:4f:ca:07:df:e3:88:fa:f4:21:99:35:0e:3d:ea:
-                    b0:86:e7:c4:d2:8a:83:2b:42:b8:ec:a3:99:62:70:
-                    81:46:cc:fc:a5:1d:d2:63:e8:eb:07:25:9a:e2:25:
-                    6d:11:56:f2:1a:51:a1:b6:3e:1c:57:32:e9:7b:2c:
-                    aa:1b:cc:97:2d:89:2d:b1:c9:5e:35:28:4d:7c:fa:
-                    65:31:3e:f7:70:dd:6e:0b:3c:58:af:a8:2e:24:c0:
-                    7e:4e:78:7d:0a:9e:8f:42:43
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Basic Constraints: 
-                CA:FALSE
-            Netscape Comment: 
-                Easy-RSA Generated Certificate
-            X509v3 Subject Key Identifier: 
-                DE:42:EF:2D:98:A3:6C:A8:AA:E0:8C:71:2C:9D:64:23:A9:E2:7E:81
-            X509v3 Authority Key Identifier: 
-                keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
-                DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
-                serial:FD:AB:EC:6A:84:27:04:A7
-
-            X509v3 Extended Key Usage: 
-                TLS Web Client Authentication
-            X509v3 Key Usage: 
-                Digital Signature
-    Signature Algorithm: sha1WithRSAEncryption
-         1c:44:26:ea:e1:66:25:cb:e4:8e:57:1c:f6:b9:17:22:62:40:
-         12:90:8f:3b:b2:61:7a:54:94:8f:b1:20:0b:bf:a3:51:e3:fa:
-         1c:a1:be:92:3a:d0:76:44:c0:57:83:ab:6a:e4:1a:45:49:a4:
-         af:39:0d:60:32:fc:3a:be:d7:fb:5d:99:7a:1f:87:e7:d5:ab:
-         84:a2:5e:90:d8:bf:fa:89:6d:32:26:02:5e:31:35:68:7f:31:
-         f5:6b:51:46:bc:af:70:ed:5a:09:7d:ec:b2:48:4f:fe:c5:2f:
-         56:04:ad:f6:c1:d2:2a:e4:6a:c4:87:fe:08:35:c5:38:cb:5e:
-         4a:c4
------BEGIN CERTIFICATE-----
-MIIEFTCCA36gAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
-CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
-cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
-MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
-bWFpbjAeFw0xMzEyMDQxNDE3NTRaFw0yMzEyMDIxNDE3NTRaMIGgMQswCQYDVQQG
-EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
-ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEPMA0GA1UEAxMGY2xp
-ZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0
-LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyskF0AlOPqT81RT0
-peg002tR4/Ni6qHw6O3EKrzwT8oH3+OI+vQhmTUOPeqwhufE0oqDK0K47KOZYnCB
-Rsz8pR3SY+jrByWa4iVtEVbyGlGhtj4cVzLpeyyqG8yXLYktscleNShNfPplMT73
-cN1uCzxYr6guJMB+Tnh9Cp6PQkMCAwEAAaOCAVkwggFVMAkGA1UdEwQCMAAwLQYJ
-YIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
-HQ4EFgQU3kLvLZijbKiq4IxxLJ1kI6nifoEwgdcGA1UdIwSBzzCBzIAUZu7DFz09
-q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
-QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
-ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
-Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
-hCcEpzATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcN
-AQEFBQADgYEAHEQm6uFmJcvkjlcc9rkXImJAEpCPO7JhelSUj7EgC7+jUeP6HKG+
-kjrQdkTAV4OrauQaRUmkrzkNYDL8Or7X+12Zeh+H59WrhKJekNi/+oltMiYCXjE1
-aH8x9WtRRryvcO1aCX3sskhP/sUvVgSt9sHSKuRqxIf+CDXFOMteSsQ=
------END CERTIFICATE-----

+ 1 - 0
integration-cli/fixtures/https/client-cert.pem

@@ -0,0 +1 @@
+../../../integration/testdata/https/client-cert.pem

+ 0 - 16
integration-cli/fixtures/https/client-key.pem

@@ -1,16 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMrJBdAJTj6k/NUU
-9KXoNNNrUePzYuqh8OjtxCq88E/KB9/jiPr0IZk1Dj3qsIbnxNKKgytCuOyjmWJw
-gUbM/KUd0mPo6wclmuIlbRFW8hpRobY+HFcy6XssqhvMly2JLbHJXjUoTXz6ZTE+
-93Ddbgs8WK+oLiTAfk54fQqej0JDAgMBAAECgYBOFEzKp2qbMEexe9ofL2N3rDDh
-xkrl8OijpzkLA6i78BxMFn4dsnZlWUpciMrjhsYAExkiRRSS+QMMJimAq1jzQqc3
-FAQV2XGYwkd0cUn7iZGvfNnEPysjsfyYQM+m+sT0ATj4BZjVShC6kkSjTdm1leLN
-OSvcHdcu3Xxg9ufF0QJBAPYdnNt5sIndt2WECePuRVi+uF4mlxTobFY0fjn26yhC
-4RsnhhD3Vldygo9gvnkwrAZYaALGSPBewes2InxvjA8CQQDS7erKiNXpwoqz5XiU
-SVEsIIVTdWzBjGbIqMOu/hUwM5FK4j6JTBks0aTGMyh0YV9L1EzM0X79J29JahCe
-iQKNAkBKNMOGqTpBV0hko1sYDk96YobUXG5RL4L6uvkUIQ7mJMQam+AgXXL7Ctuy
-v0iu4a38e8tgisiTMP7nHHtpaXihAkAOiN54/lzfMsykANgCP9scE1GcoqbP34Dl
-qttxH4kOPT9xzY1JoLjLYdbc4YGUI3GRpBt2sajygNkmUey7P+2xAkBBsVCZFvTw
-qHvOpPS2kX5ml5xoc/QAHK9N7kR+X7XFYx82RTVSqJEK4lPb+aEWn+CjiIewO4Q5
-ksDFuNxAzbhl
------END PRIVATE KEY-----

+ 1 - 0
integration-cli/fixtures/https/client-key.pem

@@ -0,0 +1 @@
+../../../integration/testdata/https/client-key.pem

+ 0 - 76
integration-cli/fixtures/https/server-cert.pem

@@ -1,76 +0,0 @@
-Certificate:
-    Data:
-        Version: 3 (0x2)
-        Serial Number: 4 (0x4)
-    Signature Algorithm: sha1WithRSAEncryption
-        Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
-        Validity
-            Not Before: Dec  4 15:01:20 2013 GMT
-            Not After : Dec  2 15:01:20 2023 GMT
-        Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=*/name=changeme/emailAddress=mail@host.domain
-        Subject Public Key Info:
-            Public Key Algorithm: rsaEncryption
-                Public-Key: (1024 bit)
-                Modulus:
-                    00:c1:ff:7d:30:6f:64:4a:b1:92:b1:71:d1:c1:74:
-                    e2:1d:db:2d:11:24:e1:00:d4:00:ae:6f:c8:9e:ae:
-                    67:b3:4a:bd:f7:e6:9e:57:6d:19:4c:3c:23:94:2d:
-                    3d:d6:63:84:d8:fa:76:2b:38:12:c1:ed:20:9d:32:
-                    e0:e8:c2:bf:9a:77:70:04:3f:7f:ca:8c:2c:82:d6:
-                    3d:25:5c:02:1a:4f:64:93:03:dd:9c:42:97:5e:09:
-                    49:af:f0:c2:e1:30:08:0e:21:46:95:d1:13:59:c0:
-                    c8:76:be:94:0d:8b:43:67:21:33:b2:08:60:9d:76:
-                    a8:05:32:1e:f9:95:09:14:75
-                Exponent: 65537 (0x10001)
-        X509v3 extensions:
-            X509v3 Basic Constraints: 
-                CA:FALSE
-            Netscape Cert Type: 
-                SSL Server
-            Netscape Comment: 
-                Easy-RSA Generated Server Certificate
-            X509v3 Subject Key Identifier: 
-                14:02:FD:FD:DD:13:38:E0:71:EA:D1:BE:C0:0E:89:1A:2D:B6:19:06
-            X509v3 Authority Key Identifier: 
-                keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
-                DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
-                serial:FD:AB:EC:6A:84:27:04:A7
-
-            X509v3 Extended Key Usage: 
-                TLS Web Server Authentication
-            X509v3 Key Usage: 
-                Digital Signature, Key Encipherment
-    Signature Algorithm: sha1WithRSAEncryption
-         40:0f:10:39:c4:b7:0f:0d:2f:bf:d2:16:cc:8e:d3:9a:fb:8b:
-         ce:4b:7b:0d:48:77:ce:f1:fe:d5:8f:ea:b1:71:ed:49:1d:9f:
-         23:3a:16:d4:70:7c:c5:29:bf:e4:90:34:d0:f0:00:24:f4:e4:
-         df:2c:c3:83:01:66:61:c9:a8:ab:29:e7:98:6d:27:89:4a:76:
-         c9:2e:19:8e:fe:6e:d5:f8:99:11:0e:97:67:4b:34:e3:1e:e3:
-         9f:35:00:a5:32:f9:b5:2c:f2:e0:c5:2e:cc:81:bd:18:dd:5c:
-         12:c8:6b:fa:0c:17:74:30:55:f6:6e:20:9a:6c:1e:09:b4:0c:
-         15:42
------BEGIN CERTIFICATE-----
-MIIEKjCCA5OgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
-CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
-cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
-MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
-bWFpbjAeFw0xMzEyMDQxNTAxMjBaFw0yMzEyMDIxNTAxMjBaMIGbMQswCQYDVQQG
-EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
-ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEKMAgGA1UEAxQBKjER
-MA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21h
-aW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMH/fTBvZEqxkrFx0cF04h3b
-LREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y4OjCv5p3
-cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+lA2LQ2ch
-M7IIYJ12qAUyHvmVCRR1AgMBAAGjggFzMIIBbzAJBgNVHRMEAjAAMBEGCWCGSAGG
-+EIBAQQEAwIGQDA0BglghkgBhvhCAQ0EJxYlRWFzeS1SU0EgR2VuZXJhdGVkIFNl
-cnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUFAL9/d0TOOBx6tG+wA6JGi22GQYw
-gdcGA1UdIwSBzzCBzIAUZu7DFz09q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJ
-BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUw
-EwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQD
-EwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1h
-aWxAaG9zdC5kb21haW6CCQD9q+xqhCcEpzATBgNVHSUEDDAKBggrBgEFBQcDATAL
-BgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAQA8QOcS3Dw0vv9IWzI7TmvuL
-zkt7DUh3zvH+1Y/qsXHtSR2fIzoW1HB8xSm/5JA00PAAJPTk3yzDgwFmYcmoqynn
-mG0niUp2yS4Zjv5u1fiZEQ6XZ0s04x7jnzUApTL5tSzy4MUuzIG9GN1cEshr+gwX
-dDBV9m4gmmweCbQMFUI=
------END CERTIFICATE-----

+ 1 - 0
integration-cli/fixtures/https/server-cert.pem

@@ -0,0 +1 @@
+../../../integration/testdata/https/server-cert.pem

+ 0 - 16
integration-cli/fixtures/https/server-key.pem

@@ -1,16 +0,0 @@
------BEGIN PRIVATE KEY-----
-MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMH/fTBvZEqxkrFx
-0cF04h3bLREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y
-4OjCv5p3cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+
-lA2LQ2chM7IIYJ12qAUyHvmVCRR1AgMBAAECgYAmwckb9RUfSwyYgLm8IYLPHiuJ
-wkllZfVg5Bo7gXJcQnFjZmJ56uTj8xvUjZlODIHM63TSO5ibv6kFXtXKCqZGd2M+
-wGbhZ0f+2GvKcwMmJERnIQjuoNaYSQLT0tM0VB9Iz0rJlZC+tzPZ+5pPqEumRdsS
-IzWNXfF42AhcbwAQYQJBAPVXtMYIJc9EZsz86ZcQiMPWUpCX5vnRmtwL8kKyR8D5
-4KfYeiowyFffSRMMcclwNHq7TgSXN+nIXM9WyzyzwikCQQDKbNA28AgZp9aT54HP
-WnbeE2pmt+uk/zl/BtxJSoK6H+69Jec+lf7EgL7HgOWYRSNot4uQWu8IhsHLTiUq
-+0FtAkEAqwlRxRy4/x24bP+D+QRV0/D97j93joFJbE4Hved7jlSlAV4xDGilwlyv
-HNB4Iu5OJ6Gcaibhm+FKkmD3noHSwQJBAIpu3fokLzX0bS+bDFBU6qO3HXX/47xj
-+tsfQvkwZrSI8AkU6c8IX0HdVhsz0FBRQAT2ORDQz1XCarfxykNZrwUCQQCGCBIc
-BBCWzhHlswlGidWJg3HqqO6hPPClEr3B5G87oCsdeYwiO23XT6rUnoJXfJHp6oCW
-5nCwDu5ZTP+khltg
------END PRIVATE KEY-----

+ 1 - 0
integration-cli/fixtures/https/server-key.pem

@@ -0,0 +1 @@
+../../../integration/testdata/https/server-key.pem

+ 467 - 0
integration/plugin/authz/authz_plugin_test.go

@@ -0,0 +1,467 @@
+// +build !windows
+
+package authz
+
+import (
+	"context"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"net/http"
+	"net/http/httputil"
+	"net/url"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/container"
+	eventtypes "github.com/docker/docker/api/types/events"
+	networktypes "github.com/docker/docker/api/types/network"
+	"github.com/docker/docker/client"
+	"github.com/docker/docker/integration/util/request"
+	"github.com/docker/docker/internal/test/environment"
+	"github.com/docker/docker/pkg/authorization"
+	"github.com/gotestyourself/gotestyourself/skip"
+	"github.com/stretchr/testify/require"
+)
+
+const (
+	testAuthZPlugin     = "authzplugin"
+	unauthorizedMessage = "User unauthorized authz plugin"
+	errorMessage        = "something went wrong..."
+	serverVersionAPI    = "/version"
+)
+
+var (
+	alwaysAllowed = []string{"/_ping", "/info"}
+	ctrl          *authorizationController
+)
+
+type authorizationController struct {
+	reqRes          authorization.Response // reqRes holds the plugin response to the initial client request
+	resRes          authorization.Response // resRes holds the plugin response to the daemon response
+	versionReqCount int                    // versionReqCount counts the number of requests to the server version API endpoint
+	versionResCount int                    // versionResCount counts the number of responses from the server version API endpoint
+	requestsURIs    []string               // requestsURIs stores all request URIs that are sent to the authorization controller
+	reqUser         string
+	resUser         string
+}
+
+func setupTestV1(t *testing.T) func() {
+	ctrl = &authorizationController{}
+	teardown := setupTest(t)
+
+	err := os.MkdirAll("/etc/docker/plugins", 0755)
+	require.Nil(t, err)
+
+	fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin)
+	err = ioutil.WriteFile(fileName, []byte(server.URL), 0644)
+	require.Nil(t, err)
+
+	return func() {
+		err := os.RemoveAll("/etc/docker/plugins")
+		require.Nil(t, err)
+
+		teardown()
+		ctrl = nil
+	}
+}
+
+// check for always allowed endpoints to not inhibit test framework functions
+func isAllowed(reqURI string) bool {
+	for _, endpoint := range alwaysAllowed {
+		if strings.HasSuffix(reqURI, endpoint) {
+			return true
+		}
+	}
+	return false
+}
+
+func TestAuthZPluginAllowRequest(t *testing.T) {
+	defer setupTestV1(t)()
+	ctrl.reqRes.Allow = true
+	ctrl.resRes.Allow = true
+	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	// Ensure command successful
+	createResponse, err := client.ContainerCreate(context.Background(), &container.Config{Cmd: []string{"top"}, Image: "busybox"}, &container.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	require.Nil(t, err)
+
+	err = client.ContainerStart(context.Background(), createResponse.ID, types.ContainerStartOptions{})
+	require.Nil(t, err)
+
+	assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
+	assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", createResponse.ID))
+
+	_, err = client.ServerVersion(context.Background())
+	require.Nil(t, err)
+	require.Equal(t, 1, ctrl.versionReqCount)
+	require.Equal(t, 1, ctrl.versionResCount)
+}
+
+func TestAuthZPluginTLS(t *testing.T) {
+	defer setupTestV1(t)()
+	const (
+		testDaemonHTTPSAddr = "tcp://localhost:4271"
+		cacertPath          = "../../testdata/https/ca.pem"
+		serverCertPath      = "../../testdata/https/server-cert.pem"
+		serverKeyPath       = "../../testdata/https/server-key.pem"
+		clientCertPath      = "../../testdata/https/client-cert.pem"
+		clientKeyPath       = "../../testdata/https/client-key.pem"
+	)
+
+	d.Start(t,
+		"--authorization-plugin="+testAuthZPlugin,
+		"--tlsverify",
+		"--tlscacert", cacertPath,
+		"--tlscert", serverCertPath,
+		"--tlskey", serverKeyPath,
+		"-H", testDaemonHTTPSAddr)
+
+	ctrl.reqRes.Allow = true
+	ctrl.resRes.Allow = true
+
+	client, err := request.NewTLSAPIClient(t, testDaemonHTTPSAddr, cacertPath, clientCertPath, clientKeyPath)
+	require.Nil(t, err)
+
+	_, err = client.ServerVersion(context.Background())
+	require.Nil(t, err)
+
+	require.Equal(t, "client", ctrl.reqUser)
+	require.Equal(t, "client", ctrl.resUser)
+}
+
+func TestAuthZPluginDenyRequest(t *testing.T) {
+	defer setupTestV1(t)()
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
+	ctrl.reqRes.Allow = false
+	ctrl.reqRes.Msg = unauthorizedMessage
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	// Ensure command is blocked
+	_, err = client.ServerVersion(context.Background())
+	require.NotNil(t, err)
+	require.Equal(t, 1, ctrl.versionReqCount)
+	require.Equal(t, 0, ctrl.versionResCount)
+
+	// Ensure unauthorized message appears in response
+	require.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
+}
+
+// TestAuthZPluginAPIDenyResponse validates that when authorization
+// plugin deny the request, the status code is forbidden
+func TestAuthZPluginAPIDenyResponse(t *testing.T) {
+	defer setupTestV1(t)()
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
+	ctrl.reqRes.Allow = false
+	ctrl.resRes.Msg = unauthorizedMessage
+
+	daemonURL, err := url.Parse(d.Sock())
+	require.Nil(t, err)
+
+	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
+	require.Nil(t, err)
+	client := httputil.NewClientConn(conn, nil)
+	req, err := http.NewRequest("GET", "/version", nil)
+	require.Nil(t, err)
+	resp, err := client.Do(req)
+
+	require.Nil(t, err)
+	require.Equal(t, http.StatusForbidden, resp.StatusCode)
+}
+
+func TestAuthZPluginDenyResponse(t *testing.T) {
+	defer setupTestV1(t)()
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
+	ctrl.reqRes.Allow = true
+	ctrl.resRes.Allow = false
+	ctrl.resRes.Msg = unauthorizedMessage
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	// Ensure command is blocked
+	_, err = client.ServerVersion(context.Background())
+	require.NotNil(t, err)
+	require.Equal(t, 1, ctrl.versionReqCount)
+	require.Equal(t, 1, ctrl.versionResCount)
+
+	// Ensure unauthorized message appears in response
+	require.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
+}
+
+// TestAuthZPluginAllowEventStream verifies event stream propagates
+// correctly after request pass through by the authorization plugin
+func TestAuthZPluginAllowEventStream(t *testing.T) {
+	skip.IfCondition(t, testEnv.DaemonInfo.OSType != "linux")
+
+	defer setupTestV1(t)()
+	ctrl.reqRes.Allow = true
+	ctrl.resRes.Allow = true
+	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	startTime := strconv.FormatInt(systemTime(t, client, testEnv).Unix(), 10)
+	events, errs, cancel := systemEventsSince(client, startTime)
+	defer cancel()
+
+	// Create a container and wait for the creation events
+	createResponse, err := client.ContainerCreate(context.Background(), &container.Config{Cmd: []string{"top"}, Image: "busybox"}, &container.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	require.Nil(t, err)
+
+	err = client.ContainerStart(context.Background(), createResponse.ID, types.ContainerStartOptions{})
+	require.Nil(t, err)
+
+	for i := 0; i < 100; i++ {
+		c, err := client.ContainerInspect(context.Background(), createResponse.ID)
+		require.Nil(t, err)
+		if c.State.Running {
+			break
+		}
+		if i == 99 {
+			t.Fatal("Container didn't run within 10s")
+		}
+		time.Sleep(100 * time.Millisecond)
+	}
+
+	created := false
+	started := false
+	for !created && !started {
+		select {
+		case event := <-events:
+			if event.Type == eventtypes.ContainerEventType && event.Actor.ID == createResponse.ID {
+				if event.Action == "create" {
+					created = true
+				}
+				if event.Action == "start" {
+					started = true
+				}
+			}
+		case err := <-errs:
+			if err == io.EOF {
+				t.Fatal("premature end of event stream")
+			}
+			require.Nil(t, err)
+		case <-time.After(30 * time.Second):
+			// Fail the test
+			t.Fatal("event stream timeout")
+		}
+	}
+
+	// Ensure both events and container endpoints are passed to the
+	// authorization plugin
+	assertURIRecorded(t, ctrl.requestsURIs, "/events")
+	assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
+	assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", createResponse.ID))
+}
+
+func systemTime(t *testing.T, client client.APIClient, testEnv *environment.Execution) time.Time {
+	if testEnv.IsLocalDaemon() {
+		return time.Now()
+	}
+
+	ctx := context.Background()
+	info, err := client.Info(ctx)
+	require.Nil(t, err)
+
+	dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
+	require.Nil(t, err, "invalid time format in GET /info response")
+	return dt
+}
+
+func systemEventsSince(client client.APIClient, since string) (<-chan eventtypes.Message, <-chan error, func()) {
+	eventOptions := types.EventsOptions{
+		Since: since,
+	}
+	ctx, cancel := context.WithCancel(context.Background())
+	events, errs := client.Events(ctx, eventOptions)
+
+	return events, errs, cancel
+}
+
+func TestAuthZPluginErrorResponse(t *testing.T) {
+	defer setupTestV1(t)()
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
+	ctrl.reqRes.Allow = true
+	ctrl.resRes.Err = errorMessage
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	// Ensure command is blocked
+	_, err = client.ServerVersion(context.Background())
+	require.NotNil(t, err)
+	require.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage), err.Error())
+}
+
+func TestAuthZPluginErrorRequest(t *testing.T) {
+	defer setupTestV1(t)()
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
+	ctrl.reqRes.Err = errorMessage
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	// Ensure command is blocked
+	_, err = client.ServerVersion(context.Background())
+	require.NotNil(t, err)
+	require.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage), err.Error())
+}
+
+func TestAuthZPluginEnsureNoDuplicatePluginRegistration(t *testing.T) {
+	defer setupTestV1(t)()
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
+
+	ctrl.reqRes.Allow = true
+	ctrl.resRes.Allow = true
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	_, err = client.ServerVersion(context.Background())
+	require.Nil(t, err)
+
+	// assert plugin is only called once..
+	require.Equal(t, 1, ctrl.versionReqCount)
+	require.Equal(t, 1, ctrl.versionResCount)
+}
+
+func TestAuthZPluginEnsureLoadImportWorking(t *testing.T) {
+	defer setupTestV1(t)()
+	ctrl.reqRes.Allow = true
+	ctrl.resRes.Allow = true
+	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	tmp, err := ioutil.TempDir("", "test-authz-load-import")
+	require.Nil(t, err)
+	defer os.RemoveAll(tmp)
+
+	savedImagePath := filepath.Join(tmp, "save.tar")
+
+	err = imageSave(client, savedImagePath, "busybox")
+	require.Nil(t, err)
+	err = imageLoad(client, savedImagePath)
+	require.Nil(t, err)
+
+	exportedImagePath := filepath.Join(tmp, "export.tar")
+
+	createResponse, err := client.ContainerCreate(context.Background(), &container.Config{Cmd: []string{}, Image: "busybox"}, &container.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	require.Nil(t, err)
+
+	err = client.ContainerStart(context.Background(), createResponse.ID, types.ContainerStartOptions{})
+	require.Nil(t, err)
+
+	responseReader, err := client.ContainerExport(context.Background(), createResponse.ID)
+	require.Nil(t, err)
+	defer responseReader.Close()
+	file, err := os.Create(exportedImagePath)
+	require.Nil(t, err)
+	defer file.Close()
+	_, err = io.Copy(file, responseReader)
+	require.Nil(t, err)
+
+	err = imageImport(client, exportedImagePath)
+	require.Nil(t, err)
+}
+
+func imageSave(client client.APIClient, path, image string) error {
+	ctx := context.Background()
+	responseReader, err := client.ImageSave(ctx, []string{image})
+	if err != nil {
+		return err
+	}
+	defer responseReader.Close()
+	file, err := os.Create(path)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+	_, err = io.Copy(file, responseReader)
+	return err
+}
+
+func imageLoad(client client.APIClient, path string) error {
+	file, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+	quiet := true
+	ctx := context.Background()
+	response, err := client.ImageLoad(ctx, file, quiet)
+	if err != nil {
+		return err
+	}
+	defer response.Body.Close()
+	return nil
+}
+
+func imageImport(client client.APIClient, path string) error {
+	file, err := os.Open(path)
+	if err != nil {
+		return err
+	}
+	defer file.Close()
+	options := types.ImageImportOptions{}
+	ref := ""
+	source := types.ImageImportSource{
+		Source:     file,
+		SourceName: "-",
+	}
+	ctx := context.Background()
+	responseReader, err := client.ImageImport(ctx, source, ref, options)
+	if err != nil {
+		return err
+	}
+	defer responseReader.Close()
+	return nil
+}
+
+func TestAuthZPluginHeader(t *testing.T) {
+	defer setupTestV1(t)()
+	ctrl.reqRes.Allow = true
+	ctrl.resRes.Allow = true
+	d.StartWithBusybox(t, "--debug", "--authorization-plugin="+testAuthZPlugin)
+
+	daemonURL, err := url.Parse(d.Sock())
+	require.Nil(t, err)
+
+	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
+	require.Nil(t, err)
+	client := httputil.NewClientConn(conn, nil)
+	req, err := http.NewRequest("GET", "/version", nil)
+	require.Nil(t, err)
+	resp, err := client.Do(req)
+	require.Nil(t, err)
+	require.Equal(t, "application/json", resp.Header["Content-Type"][0])
+}
+
+// assertURIRecorded verifies that the given URI was sent and recorded
+// in the authz plugin
+func assertURIRecorded(t *testing.T, uris []string, uri string) {
+	var found bool
+	for _, u := range uris {
+		if strings.Contains(u, uri) {
+			found = true
+			break
+		}
+	}
+	if !found {
+		t.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ","))
+	}
+}

+ 178 - 0
integration/plugin/authz/authz_plugin_v2_test.go

@@ -0,0 +1,178 @@
+// +build !windows
+
+package authz
+
+import (
+	"context"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/docker/docker/api/types"
+	"github.com/docker/docker/api/types/container"
+	"github.com/docker/docker/api/types/filters"
+	networktypes "github.com/docker/docker/api/types/network"
+	volumetypes "github.com/docker/docker/api/types/volume"
+	"github.com/docker/docker/client"
+	"github.com/docker/docker/integration/util/requirement"
+	"github.com/gotestyourself/gotestyourself/skip"
+	"github.com/stretchr/testify/require"
+)
+
+var (
+	authzPluginName            = "riyaz/authz-no-volume-plugin"
+	authzPluginTag             = "latest"
+	authzPluginNameWithTag     = authzPluginName + ":" + authzPluginTag
+	authzPluginBadManifestName = "riyaz/authz-plugin-bad-manifest"
+	nonexistentAuthzPluginName = "riyaz/nonexistent-authz-plugin"
+)
+
+func setupTestV2(t *testing.T) func() {
+	skip.IfCondition(t, testEnv.DaemonInfo.OSType != "linux")
+	skip.IfCondition(t, !requirement.HasHubConnectivity(t))
+
+	teardown := setupTest(t)
+
+	d.Start(t)
+
+	return teardown
+}
+
+func TestAuthZPluginV2AllowNonVolumeRequest(t *testing.T) {
+	skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
+	defer setupTestV2(t)()
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	// Install authz plugin
+	err = pluginInstallGrantAllPermissions(client, authzPluginNameWithTag)
+	require.Nil(t, err)
+	// start the daemon with the plugin and load busybox, --net=none build fails otherwise
+	// because it needs to pull busybox
+	d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
+	d.LoadBusybox(t)
+
+	// Ensure docker run command and accompanying docker ps are successful
+	createResponse, err := client.ContainerCreate(context.Background(), &container.Config{Cmd: []string{"top"}, Image: "busybox"}, &container.HostConfig{}, &networktypes.NetworkingConfig{}, "")
+	require.Nil(t, err)
+
+	err = client.ContainerStart(context.Background(), createResponse.ID, types.ContainerStartOptions{})
+	require.Nil(t, err)
+
+	_, err = client.ContainerInspect(context.Background(), createResponse.ID)
+	require.Nil(t, err)
+}
+
+func TestAuthZPluginV2Disable(t *testing.T) {
+	skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
+	defer setupTestV2(t)()
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	// Install authz plugin
+	err = pluginInstallGrantAllPermissions(client, authzPluginNameWithTag)
+	require.Nil(t, err)
+
+	d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
+	d.LoadBusybox(t)
+
+	_, err = client.VolumeCreate(context.Background(), volumetypes.VolumesCreateBody{Driver: "local"})
+	require.NotNil(t, err)
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
+
+	// disable the plugin
+	err = client.PluginDisable(context.Background(), authzPluginNameWithTag, types.PluginDisableOptions{})
+	require.Nil(t, err)
+
+	// now test to see if the docker api works.
+	_, err = client.VolumeCreate(context.Background(), volumetypes.VolumesCreateBody{Driver: "local"})
+	require.Nil(t, err)
+}
+
+func TestAuthZPluginV2RejectVolumeRequests(t *testing.T) {
+	skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
+	defer setupTestV2(t)()
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	// Install authz plugin
+	err = pluginInstallGrantAllPermissions(client, authzPluginNameWithTag)
+	require.Nil(t, err)
+
+	// restart the daemon with the plugin
+	d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
+
+	_, err = client.VolumeCreate(context.Background(), volumetypes.VolumesCreateBody{Driver: "local"})
+	require.NotNil(t, err)
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
+
+	_, err = client.VolumeList(context.Background(), filters.Args{})
+	require.NotNil(t, err)
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
+
+	// The plugin will block the command before it can determine the volume does not exist
+	err = client.VolumeRemove(context.Background(), "test", false)
+	require.NotNil(t, err)
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
+
+	_, err = client.VolumeInspect(context.Background(), "test")
+	require.NotNil(t, err)
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
+
+	_, err = client.VolumesPrune(context.Background(), filters.Args{})
+	require.NotNil(t, err)
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
+}
+
+func TestAuthZPluginV2BadManifestFailsDaemonStart(t *testing.T) {
+	skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
+	defer setupTestV2(t)()
+
+	client, err := d.NewClient()
+	require.Nil(t, err)
+
+	// Install authz plugin with bad manifest
+	err = pluginInstallGrantAllPermissions(client, authzPluginBadManifestName)
+	require.Nil(t, err)
+
+	// start the daemon with the plugin, it will error
+	err = d.RestartWithError("--authorization-plugin=" + authzPluginBadManifestName)
+	require.NotNil(t, err)
+
+	// restarting the daemon without requiring the plugin will succeed
+	d.Start(t)
+}
+
+func TestAuthZPluginV2NonexistentFailsDaemonStart(t *testing.T) {
+	defer setupTestV2(t)()
+
+	// start the daemon with a non-existent authz plugin, it will error
+	err := d.RestartWithError("--authorization-plugin=" + nonexistentAuthzPluginName)
+	require.NotNil(t, err)
+
+	// restarting the daemon without requiring the plugin will succeed
+	d.Start(t)
+}
+
+func pluginInstallGrantAllPermissions(client client.APIClient, name string) error {
+	ctx := context.Background()
+	options := types.PluginInstallOptions{
+		RemoteRef:            name,
+		AcceptAllPermissions: true,
+	}
+	responseReader, err := client.PluginInstall(ctx, "", options)
+	if err != nil {
+		return err
+	}
+	defer responseReader.Close()
+	// we have to read the response out here because the client API
+	// actually starts a goroutine which we can only be sure has
+	// completed when we get EOF from reading responseBody
+	_, err = ioutil.ReadAll(responseReader)
+	return err
+}

+ 177 - 0
integration/plugin/authz/main_test.go

@@ -0,0 +1,177 @@
+// +build !windows
+
+package authz
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"strings"
+	"testing"
+
+	"github.com/docker/docker/integration-cli/daemon"
+	"github.com/docker/docker/internal/test/environment"
+	"github.com/docker/docker/pkg/authorization"
+	"github.com/docker/docker/pkg/plugins"
+)
+
+var (
+	testEnv *environment.Execution
+	d       *daemon.Daemon
+	server  *httptest.Server
+)
+
+const dockerdBinary = "dockerd"
+
+func TestMain(m *testing.M) {
+	var err error
+	testEnv, err = environment.New()
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+
+	testEnv.Print()
+	setupSuite()
+	exitCode := m.Run()
+	teardownSuite()
+
+	os.Exit(exitCode)
+}
+
+func setupTest(t *testing.T) func() {
+	environment.ProtectAll(t, testEnv)
+
+	d = daemon.New(t, "", dockerdBinary, daemon.Config{
+		Experimental: testEnv.DaemonInfo.ExperimentalBuild,
+	})
+
+	return func() {
+		if d != nil {
+			d.Stop(t)
+		}
+		testEnv.Clean(t)
+	}
+}
+
+func setupSuite() {
+	mux := http.NewServeMux()
+	server = httptest.NewServer(mux)
+
+	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
+		b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}})
+		if err != nil {
+			panic("could not marshal json for /Plugin.Activate: " + err.Error())
+		}
+		w.Write(b)
+	})
+
+	mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		body, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			panic("could not read body for /AuthZPlugin.AuthZReq: " + err.Error())
+		}
+		authReq := authorization.Request{}
+		err = json.Unmarshal(body, &authReq)
+		if err != nil {
+			panic("could not unmarshal json for /AuthZPlugin.AuthZReq: " + err.Error())
+		}
+
+		assertBody(authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody)
+		assertAuthHeaders(authReq.RequestHeaders)
+
+		// Count only server version api
+		if strings.HasSuffix(authReq.RequestURI, serverVersionAPI) {
+			ctrl.versionReqCount++
+		}
+
+		ctrl.requestsURIs = append(ctrl.requestsURIs, authReq.RequestURI)
+
+		reqRes := ctrl.reqRes
+		if isAllowed(authReq.RequestURI) {
+			reqRes = authorization.Response{Allow: true}
+		}
+		if reqRes.Err != "" {
+			w.WriteHeader(http.StatusInternalServerError)
+		}
+		b, err := json.Marshal(reqRes)
+		if err != nil {
+			panic("could not marshal json for /AuthZPlugin.AuthZReq: " + err.Error())
+		}
+
+		ctrl.reqUser = authReq.User
+		w.Write(b)
+	})
+
+	mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		body, err := ioutil.ReadAll(r.Body)
+		if err != nil {
+			panic("could not read body for /AuthZPlugin.AuthZRes: " + err.Error())
+		}
+		authReq := authorization.Request{}
+		err = json.Unmarshal(body, &authReq)
+		if err != nil {
+			panic("could not unmarshal json for /AuthZPlugin.AuthZRes: " + err.Error())
+		}
+
+		assertBody(authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody)
+		assertAuthHeaders(authReq.ResponseHeaders)
+
+		// Count only server version api
+		if strings.HasSuffix(authReq.RequestURI, serverVersionAPI) {
+			ctrl.versionResCount++
+		}
+		resRes := ctrl.resRes
+		if isAllowed(authReq.RequestURI) {
+			resRes = authorization.Response{Allow: true}
+		}
+		if resRes.Err != "" {
+			w.WriteHeader(http.StatusInternalServerError)
+		}
+		b, err := json.Marshal(resRes)
+		if err != nil {
+			panic("could not marshal json for /AuthZPlugin.AuthZRes: " + err.Error())
+		}
+		ctrl.resUser = authReq.User
+		w.Write(b)
+	})
+}
+
+func teardownSuite() {
+	if server == nil {
+		return
+	}
+
+	server.Close()
+}
+
+// assertAuthHeaders validates authentication headers are removed
+func assertAuthHeaders(headers map[string]string) error {
+	for k := range headers {
+		if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") {
+			panic(fmt.Sprintf("Found authentication headers in request '%v'", headers))
+		}
+	}
+	return nil
+}
+
+// assertBody asserts that body is removed for non text/json requests
+func assertBody(requestURI string, headers map[string]string, body []byte) {
+	if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 {
+		panic("Body included for authentication endpoint " + string(body))
+	}
+
+	for k, v := range headers {
+		if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" {
+			return
+		}
+	}
+	if len(body) > 0 {
+		panic(fmt.Sprintf("Body included while it should not (Headers: '%v')", headers))
+	}
+}

+ 1 - 0
integration/plugin/pkg_test.go

@@ -0,0 +1 @@
+package plugin

+ 23 - 0
integration/testdata/https/ca.pem

@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIID0TCCAzqgAwIBAgIJAP2r7GqEJwSnMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
+VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMG
+A1UEChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMI
+Y2hhbmdlbWUxETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWls
+QGhvc3QuZG9tYWluMB4XDTEzMTIwMzE2NTYzMFoXDTIzMTIwMTE2NTYzMFowgaIx
+CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2Nv
+MRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYD
+VQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEW
+EG1haWxAaG9zdC5kb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAn
+0xDw+5y7ZptQacq66pUhRu82JP2WU6IDgo5QUtNU6/CX5PwQATe/OnYTZQFbksxp
+AU9boG0FCkgxfsgPYXEuZxVEGKI2fxfKHOZZI8mrkWmj6eWU/0cvCjGVc9rTITP5
+sNQvg+hORyVDdNp2IdsbMJayiB3AQYMFx3vSDOMTAgMBAAGjggELMIIBBzAdBgNV
+HQ4EFgQUZu7DFz09q0QBa2+ymRm9qgK1NPswgdcGA1UdIwSBzzCBzIAUZu7DFz09
+q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
+QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
+ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
+Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
+hCcEpzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAF8fJKKM+/oOdnNi
+zEd0M1+PmZOyqvjYQn/2ZR8UHH6Imgc/OPQKZXf0bVE1Txc/DaUNn9Isd1SuCuaE
+ic3vAIYYU7PmgeNN6vwec48V96T7jr+GAi6AVMhQEc2hHCfVtx11Xx+x6aHDZzJt
+Zxtf5lL6KSO9Y+EFwM+rju6hm5hW
+-----END CERTIFICATE-----

+ 73 - 0
integration/testdata/https/client-cert.pem

@@ -0,0 +1,73 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 3 (0x3)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
+        Validity
+            Not Before: Dec  4 14:17:54 2013 GMT
+            Not After : Dec  2 14:17:54 2023 GMT
+        Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=client/name=changeme/emailAddress=mail@host.domain
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:ca:c9:05:d0:09:4e:3e:a4:fc:d5:14:f4:a5:e8:
+                    34:d3:6b:51:e3:f3:62:ea:a1:f0:e8:ed:c4:2a:bc:
+                    f0:4f:ca:07:df:e3:88:fa:f4:21:99:35:0e:3d:ea:
+                    b0:86:e7:c4:d2:8a:83:2b:42:b8:ec:a3:99:62:70:
+                    81:46:cc:fc:a5:1d:d2:63:e8:eb:07:25:9a:e2:25:
+                    6d:11:56:f2:1a:51:a1:b6:3e:1c:57:32:e9:7b:2c:
+                    aa:1b:cc:97:2d:89:2d:b1:c9:5e:35:28:4d:7c:fa:
+                    65:31:3e:f7:70:dd:6e:0b:3c:58:af:a8:2e:24:c0:
+                    7e:4e:78:7d:0a:9e:8f:42:43
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Comment: 
+                Easy-RSA Generated Certificate
+            X509v3 Subject Key Identifier: 
+                DE:42:EF:2D:98:A3:6C:A8:AA:E0:8C:71:2C:9D:64:23:A9:E2:7E:81
+            X509v3 Authority Key Identifier: 
+                keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
+                DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
+                serial:FD:AB:EC:6A:84:27:04:A7
+
+            X509v3 Extended Key Usage: 
+                TLS Web Client Authentication
+            X509v3 Key Usage: 
+                Digital Signature
+    Signature Algorithm: sha1WithRSAEncryption
+         1c:44:26:ea:e1:66:25:cb:e4:8e:57:1c:f6:b9:17:22:62:40:
+         12:90:8f:3b:b2:61:7a:54:94:8f:b1:20:0b:bf:a3:51:e3:fa:
+         1c:a1:be:92:3a:d0:76:44:c0:57:83:ab:6a:e4:1a:45:49:a4:
+         af:39:0d:60:32:fc:3a:be:d7:fb:5d:99:7a:1f:87:e7:d5:ab:
+         84:a2:5e:90:d8:bf:fa:89:6d:32:26:02:5e:31:35:68:7f:31:
+         f5:6b:51:46:bc:af:70:ed:5a:09:7d:ec:b2:48:4f:fe:c5:2f:
+         56:04:ad:f6:c1:d2:2a:e4:6a:c4:87:fe:08:35:c5:38:cb:5e:
+         4a:c4
+-----BEGIN CERTIFICATE-----
+MIIEFTCCA36gAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
+CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
+cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
+MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
+bWFpbjAeFw0xMzEyMDQxNDE3NTRaFw0yMzEyMDIxNDE3NTRaMIGgMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
+ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEPMA0GA1UEAxMGY2xp
+ZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0
+LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyskF0AlOPqT81RT0
+peg002tR4/Ni6qHw6O3EKrzwT8oH3+OI+vQhmTUOPeqwhufE0oqDK0K47KOZYnCB
+Rsz8pR3SY+jrByWa4iVtEVbyGlGhtj4cVzLpeyyqG8yXLYktscleNShNfPplMT73
+cN1uCzxYr6guJMB+Tnh9Cp6PQkMCAwEAAaOCAVkwggFVMAkGA1UdEwQCMAAwLQYJ
+YIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
+HQ4EFgQU3kLvLZijbKiq4IxxLJ1kI6nifoEwgdcGA1UdIwSBzzCBzIAUZu7DFz09
+q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
+QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
+ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
+Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
+hCcEpzATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcN
+AQEFBQADgYEAHEQm6uFmJcvkjlcc9rkXImJAEpCPO7JhelSUj7EgC7+jUeP6HKG+
+kjrQdkTAV4OrauQaRUmkrzkNYDL8Or7X+12Zeh+H59WrhKJekNi/+oltMiYCXjE1
+aH8x9WtRRryvcO1aCX3sskhP/sUvVgSt9sHSKuRqxIf+CDXFOMteSsQ=
+-----END CERTIFICATE-----

+ 16 - 0
integration/testdata/https/client-key.pem

@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMrJBdAJTj6k/NUU
+9KXoNNNrUePzYuqh8OjtxCq88E/KB9/jiPr0IZk1Dj3qsIbnxNKKgytCuOyjmWJw
+gUbM/KUd0mPo6wclmuIlbRFW8hpRobY+HFcy6XssqhvMly2JLbHJXjUoTXz6ZTE+
+93Ddbgs8WK+oLiTAfk54fQqej0JDAgMBAAECgYBOFEzKp2qbMEexe9ofL2N3rDDh
+xkrl8OijpzkLA6i78BxMFn4dsnZlWUpciMrjhsYAExkiRRSS+QMMJimAq1jzQqc3
+FAQV2XGYwkd0cUn7iZGvfNnEPysjsfyYQM+m+sT0ATj4BZjVShC6kkSjTdm1leLN
+OSvcHdcu3Xxg9ufF0QJBAPYdnNt5sIndt2WECePuRVi+uF4mlxTobFY0fjn26yhC
+4RsnhhD3Vldygo9gvnkwrAZYaALGSPBewes2InxvjA8CQQDS7erKiNXpwoqz5XiU
+SVEsIIVTdWzBjGbIqMOu/hUwM5FK4j6JTBks0aTGMyh0YV9L1EzM0X79J29JahCe
+iQKNAkBKNMOGqTpBV0hko1sYDk96YobUXG5RL4L6uvkUIQ7mJMQam+AgXXL7Ctuy
+v0iu4a38e8tgisiTMP7nHHtpaXihAkAOiN54/lzfMsykANgCP9scE1GcoqbP34Dl
+qttxH4kOPT9xzY1JoLjLYdbc4YGUI3GRpBt2sajygNkmUey7P+2xAkBBsVCZFvTw
+qHvOpPS2kX5ml5xoc/QAHK9N7kR+X7XFYx82RTVSqJEK4lPb+aEWn+CjiIewO4Q5
+ksDFuNxAzbhl
+-----END PRIVATE KEY-----

+ 76 - 0
integration/testdata/https/server-cert.pem

@@ -0,0 +1,76 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 4 (0x4)
+    Signature Algorithm: sha1WithRSAEncryption
+        Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
+        Validity
+            Not Before: Dec  4 15:01:20 2013 GMT
+            Not After : Dec  2 15:01:20 2023 GMT
+        Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=*/name=changeme/emailAddress=mail@host.domain
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (1024 bit)
+                Modulus:
+                    00:c1:ff:7d:30:6f:64:4a:b1:92:b1:71:d1:c1:74:
+                    e2:1d:db:2d:11:24:e1:00:d4:00:ae:6f:c8:9e:ae:
+                    67:b3:4a:bd:f7:e6:9e:57:6d:19:4c:3c:23:94:2d:
+                    3d:d6:63:84:d8:fa:76:2b:38:12:c1:ed:20:9d:32:
+                    e0:e8:c2:bf:9a:77:70:04:3f:7f:ca:8c:2c:82:d6:
+                    3d:25:5c:02:1a:4f:64:93:03:dd:9c:42:97:5e:09:
+                    49:af:f0:c2:e1:30:08:0e:21:46:95:d1:13:59:c0:
+                    c8:76:be:94:0d:8b:43:67:21:33:b2:08:60:9d:76:
+                    a8:05:32:1e:f9:95:09:14:75
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            Netscape Cert Type: 
+                SSL Server
+            Netscape Comment: 
+                Easy-RSA Generated Server Certificate
+            X509v3 Subject Key Identifier: 
+                14:02:FD:FD:DD:13:38:E0:71:EA:D1:BE:C0:0E:89:1A:2D:B6:19:06
+            X509v3 Authority Key Identifier: 
+                keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
+                DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
+                serial:FD:AB:EC:6A:84:27:04:A7
+
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication
+            X509v3 Key Usage: 
+                Digital Signature, Key Encipherment
+    Signature Algorithm: sha1WithRSAEncryption
+         40:0f:10:39:c4:b7:0f:0d:2f:bf:d2:16:cc:8e:d3:9a:fb:8b:
+         ce:4b:7b:0d:48:77:ce:f1:fe:d5:8f:ea:b1:71:ed:49:1d:9f:
+         23:3a:16:d4:70:7c:c5:29:bf:e4:90:34:d0:f0:00:24:f4:e4:
+         df:2c:c3:83:01:66:61:c9:a8:ab:29:e7:98:6d:27:89:4a:76:
+         c9:2e:19:8e:fe:6e:d5:f8:99:11:0e:97:67:4b:34:e3:1e:e3:
+         9f:35:00:a5:32:f9:b5:2c:f2:e0:c5:2e:cc:81:bd:18:dd:5c:
+         12:c8:6b:fa:0c:17:74:30:55:f6:6e:20:9a:6c:1e:09:b4:0c:
+         15:42
+-----BEGIN CERTIFICATE-----
+MIIEKjCCA5OgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
+CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
+cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
+MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
+bWFpbjAeFw0xMzEyMDQxNTAxMjBaFw0yMzEyMDIxNTAxMjBaMIGbMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
+ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEKMAgGA1UEAxQBKjER
+MA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21h
+aW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMH/fTBvZEqxkrFx0cF04h3b
+LREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y4OjCv5p3
+cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+lA2LQ2ch
+M7IIYJ12qAUyHvmVCRR1AgMBAAGjggFzMIIBbzAJBgNVHRMEAjAAMBEGCWCGSAGG
++EIBAQQEAwIGQDA0BglghkgBhvhCAQ0EJxYlRWFzeS1SU0EgR2VuZXJhdGVkIFNl
+cnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUFAL9/d0TOOBx6tG+wA6JGi22GQYw
+gdcGA1UdIwSBzzCBzIAUZu7DFz09q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJ
+BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUw
+EwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQD
+EwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1h
+aWxAaG9zdC5kb21haW6CCQD9q+xqhCcEpzATBgNVHSUEDDAKBggrBgEFBQcDATAL
+BgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAQA8QOcS3Dw0vv9IWzI7TmvuL
+zkt7DUh3zvH+1Y/qsXHtSR2fIzoW1HB8xSm/5JA00PAAJPTk3yzDgwFmYcmoqynn
+mG0niUp2yS4Zjv5u1fiZEQ6XZ0s04x7jnzUApTL5tSzy4MUuzIG9GN1cEshr+gwX
+dDBV9m4gmmweCbQMFUI=
+-----END CERTIFICATE-----

+ 16 - 0
integration/testdata/https/server-key.pem

@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMH/fTBvZEqxkrFx
+0cF04h3bLREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y
+4OjCv5p3cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+
+lA2LQ2chM7IIYJ12qAUyHvmVCRR1AgMBAAECgYAmwckb9RUfSwyYgLm8IYLPHiuJ
+wkllZfVg5Bo7gXJcQnFjZmJ56uTj8xvUjZlODIHM63TSO5ibv6kFXtXKCqZGd2M+
+wGbhZ0f+2GvKcwMmJERnIQjuoNaYSQLT0tM0VB9Iz0rJlZC+tzPZ+5pPqEumRdsS
+IzWNXfF42AhcbwAQYQJBAPVXtMYIJc9EZsz86ZcQiMPWUpCX5vnRmtwL8kKyR8D5
+4KfYeiowyFffSRMMcclwNHq7TgSXN+nIXM9WyzyzwikCQQDKbNA28AgZp9aT54HP
+WnbeE2pmt+uk/zl/BtxJSoK6H+69Jec+lf7EgL7HgOWYRSNot4uQWu8IhsHLTiUq
++0FtAkEAqwlRxRy4/x24bP+D+QRV0/D97j93joFJbE4Hved7jlSlAV4xDGilwlyv
+HNB4Iu5OJ6Gcaibhm+FKkmD3noHSwQJBAIpu3fokLzX0bS+bDFBU6qO3HXX/47xj
++tsfQvkwZrSI8AkU6c8IX0HdVhsz0FBRQAT2ORDQz1XCarfxykNZrwUCQQCGCBIc
+BBCWzhHlswlGidWJg3HqqO6hPPClEr3B5G87oCsdeYwiO23XT6rUnoJXfJHp6oCW
+5nCwDu5ZTP+khltg
+-----END PRIVATE KEY-----

+ 38 - 0
integration/util/request/client.go

@@ -1,9 +1,15 @@
 package request
 
 import (
+	"net"
+	"net/http"
 	"testing"
+	"time"
 
+	"github.com/docker/docker/api"
 	"github.com/docker/docker/client"
+	"github.com/docker/go-connections/sockets"
+	"github.com/docker/go-connections/tlsconfig"
 	"github.com/stretchr/testify/require"
 )
 
@@ -13,3 +19,35 @@ func NewAPIClient(t *testing.T) client.APIClient {
 	require.NoError(t, err)
 	return clt
 }
+
+// NewTLSAPIClient returns a docker API client configured with the
+// provided TLS settings
+func NewTLSAPIClient(t *testing.T, host, cacertPath, certPath, keyPath string) (client.APIClient, error) {
+	opts := tlsconfig.Options{
+		CAFile:             cacertPath,
+		CertFile:           certPath,
+		KeyFile:            keyPath,
+		ExclusiveRootPools: true,
+	}
+	config, err := tlsconfig.Client(opts)
+	require.Nil(t, err)
+	tr := &http.Transport{
+		TLSClientConfig: config,
+		DialContext: (&net.Dialer{
+			KeepAlive: 30 * time.Second,
+			Timeout:   30 * time.Second,
+		}).DialContext,
+	}
+	proto, addr, _, err := client.ParseHost(host)
+	require.Nil(t, err)
+
+	sockets.ConfigureTransport(tr, proto, addr)
+
+	httpClient := &http.Client{
+		Transport:     tr,
+		CheckRedirect: client.CheckRedirect,
+	}
+	verStr := api.DefaultVersion
+	customHeaders := map[string]string{}
+	return client.NewClient(host, verStr, httpClient, customHeaders)
+}

+ 26 - 0
integration/util/requirement/requirement.go

@@ -0,0 +1,26 @@
+package requirement
+
+import (
+	"net/http"
+	"strings"
+	"testing"
+	"time"
+)
+
+// HasHubConnectivity checks to see if https://hub.docker.com is
+// accessible from the present environment
+func HasHubConnectivity(t *testing.T) bool {
+	// Set a timeout on the GET at 15s
+	var timeout = 15 * time.Second
+	var url = "https://hub.docker.com"
+
+	client := http.Client{Timeout: timeout}
+	resp, err := client.Get(url)
+	if err != nil && strings.Contains(err.Error(), "use of closed network connection") {
+		t.Fatalf("Timeout for GET request on %s", url)
+	}
+	if resp != nil {
+		resp.Body.Close()
+	}
+	return err == nil
+}