Browse code

integration/plugin/authz: port tests from integration-cli

Signed-off-by: David Sheets <dsheets@docker.com>

David Sheets authored on 2017/09/22 22:05:56
Showing 28 changed files
... ...
@@ -15,7 +15,7 @@ source "$SCRIPTDIR/make/.go-autogen"
15 15
 
16 16
 integration_api_dirs=${TEST_INTEGRATION_DIR:-"$(
17 17
 	find ./integration -type d |
18
-	grep -vE '^(./integration$|./integration/util)')"}
18
+	grep -vE '^(./integration($|/util|/internal|/testdata|/plugin$))')"}
19 19
 
20 20
 run_test_integration() {
21 21
 	[[ "$TESTFLAGS" != *-check.f* ]] && run_test_integration_suites
22 22
deleted file mode 100644
... ...
@@ -1,162 +0,0 @@
1
-// +build !windows
2
-
3
-package main
4
-
5
-import (
6
-	"fmt"
7
-
8
-	"github.com/docker/docker/integration-cli/checker"
9
-	"github.com/docker/docker/integration-cli/daemon"
10
-	"github.com/go-check/check"
11
-)
12
-
13
-var (
14
-	authzPluginName            = "riyaz/authz-no-volume-plugin"
15
-	authzPluginTag             = "latest"
16
-	authzPluginNameWithTag     = authzPluginName + ":" + authzPluginTag
17
-	authzPluginBadManifestName = "riyaz/authz-plugin-bad-manifest"
18
-	nonexistentAuthzPluginName = "riyaz/nonexistent-authz-plugin"
19
-)
20
-
21
-func init() {
22
-	check.Suite(&DockerAuthzV2Suite{
23
-		ds: &DockerSuite{},
24
-	})
25
-}
26
-
27
-type DockerAuthzV2Suite struct {
28
-	ds *DockerSuite
29
-	d  *daemon.Daemon
30
-}
31
-
32
-func (s *DockerAuthzV2Suite) SetUpTest(c *check.C) {
33
-	testRequires(c, DaemonIsLinux, Network, SameHostDaemon)
34
-	s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
35
-		Experimental: testEnv.ExperimentalDaemon(),
36
-	})
37
-	s.d.Start(c)
38
-}
39
-
40
-func (s *DockerAuthzV2Suite) TearDownTest(c *check.C) {
41
-	if s.d != nil {
42
-		s.d.Stop(c)
43
-		s.ds.TearDownTest(c)
44
-	}
45
-}
46
-
47
-func (s *DockerAuthzV2Suite) TestAuthZPluginAllowNonVolumeRequest(c *check.C) {
48
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
49
-
50
-	// Install authz plugin
51
-	_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
52
-	c.Assert(err, checker.IsNil)
53
-	// start the daemon with the plugin and load busybox, --net=none build fails otherwise
54
-	// because it needs to pull busybox
55
-	s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
56
-	s.d.LoadBusybox(c)
57
-
58
-	// defer disabling the plugin
59
-	defer func() {
60
-		s.d.Restart(c)
61
-		_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
62
-		c.Assert(err, checker.IsNil)
63
-		_, err = s.d.Cmd("plugin", "rm", authzPluginNameWithTag)
64
-		c.Assert(err, checker.IsNil)
65
-	}()
66
-
67
-	// Ensure docker run command and accompanying docker ps are successful
68
-	_, err = s.d.Cmd("run", "-d", "busybox", "top")
69
-	c.Assert(err, check.IsNil)
70
-}
71
-
72
-func (s *DockerAuthzV2Suite) TestAuthZPluginDisable(c *check.C) {
73
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
74
-	// Install authz plugin
75
-	_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
76
-	c.Assert(err, checker.IsNil)
77
-	// start the daemon with the plugin and load busybox, --net=none build fails otherwise
78
-	// because it needs to pull busybox
79
-	s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
80
-	s.d.LoadBusybox(c)
81
-
82
-	// defer removing the plugin
83
-	defer func() {
84
-		s.d.Restart(c)
85
-		_, err = s.d.Cmd("plugin", "rm", "-f", authzPluginNameWithTag)
86
-		c.Assert(err, checker.IsNil)
87
-	}()
88
-
89
-	out, err := s.d.Cmd("volume", "create")
90
-	c.Assert(err, check.NotNil)
91
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
92
-
93
-	// disable the plugin
94
-	_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
95
-	c.Assert(err, checker.IsNil)
96
-
97
-	// now test to see if the docker api works.
98
-	_, err = s.d.Cmd("volume", "create")
99
-	c.Assert(err, checker.IsNil)
100
-}
101
-
102
-func (s *DockerAuthzV2Suite) TestAuthZPluginRejectVolumeRequests(c *check.C) {
103
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
104
-	// Install authz plugin
105
-	_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginNameWithTag)
106
-	c.Assert(err, checker.IsNil)
107
-
108
-	// restart the daemon with the plugin
109
-	s.d.Restart(c, "--authorization-plugin="+authzPluginNameWithTag)
110
-
111
-	// defer disabling the plugin
112
-	defer func() {
113
-		s.d.Restart(c)
114
-		_, err = s.d.Cmd("plugin", "disable", authzPluginNameWithTag)
115
-		c.Assert(err, checker.IsNil)
116
-		_, err = s.d.Cmd("plugin", "rm", authzPluginNameWithTag)
117
-		c.Assert(err, checker.IsNil)
118
-	}()
119
-
120
-	out, err := s.d.Cmd("volume", "create")
121
-	c.Assert(err, check.NotNil)
122
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
123
-
124
-	out, err = s.d.Cmd("volume", "ls")
125
-	c.Assert(err, check.NotNil)
126
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
127
-
128
-	// The plugin will block the command before it can determine the volume does not exist
129
-	out, err = s.d.Cmd("volume", "rm", "test")
130
-	c.Assert(err, check.NotNil)
131
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
132
-
133
-	out, err = s.d.Cmd("volume", "inspect", "test")
134
-	c.Assert(err, check.NotNil)
135
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
136
-
137
-	out, err = s.d.Cmd("volume", "prune", "-f")
138
-	c.Assert(err, check.NotNil)
139
-	c.Assert(out, checker.Contains, fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag))
140
-}
141
-
142
-func (s *DockerAuthzV2Suite) TestAuthZPluginBadManifestFailsDaemonStart(c *check.C) {
143
-	testRequires(c, DaemonIsLinux, IsAmd64, Network)
144
-	// Install authz plugin with bad manifest
145
-	_, err := s.d.Cmd("plugin", "install", "--grant-all-permissions", authzPluginBadManifestName)
146
-	c.Assert(err, checker.IsNil)
147
-
148
-	// start the daemon with the plugin, it will error
149
-	c.Assert(s.d.RestartWithError("--authorization-plugin="+authzPluginBadManifestName), check.NotNil)
150
-
151
-	// restarting the daemon without requiring the plugin will succeed
152
-	s.d.Restart(c)
153
-}
154
-
155
-func (s *DockerAuthzV2Suite) TestNonexistentAuthZPluginFailsDaemonStart(c *check.C) {
156
-	testRequires(c, DaemonIsLinux, Network)
157
-	// start the daemon with a non-existent authz plugin, it will error
158
-	c.Assert(s.d.RestartWithError("--authorization-plugin="+nonexistentAuthzPluginName), check.NotNil)
159
-
160
-	// restarting the daemon without requiring the plugin will succeed
161
-	s.d.Start(c)
162
-}
163 1
deleted file mode 100644
... ...
@@ -1,470 +0,0 @@
1
-// +build !windows
2
-
3
-package main
4
-
5
-import (
6
-	"encoding/json"
7
-	"fmt"
8
-	"io/ioutil"
9
-	"net/http"
10
-	"net/http/httptest"
11
-	"os"
12
-	"path/filepath"
13
-	"strings"
14
-
15
-	"bufio"
16
-	"bytes"
17
-	"os/exec"
18
-	"strconv"
19
-	"time"
20
-
21
-	"net"
22
-	"net/http/httputil"
23
-	"net/url"
24
-
25
-	"github.com/docker/docker/integration-cli/checker"
26
-	"github.com/docker/docker/integration-cli/daemon"
27
-	"github.com/docker/docker/pkg/authorization"
28
-	"github.com/docker/docker/pkg/plugins"
29
-	"github.com/go-check/check"
30
-)
31
-
32
-const (
33
-	testAuthZPlugin     = "authzplugin"
34
-	unauthorizedMessage = "User unauthorized authz plugin"
35
-	errorMessage        = "something went wrong..."
36
-	containerListAPI    = "/containers/json"
37
-)
38
-
39
-var (
40
-	alwaysAllowed = []string{"/_ping", "/info"}
41
-)
42
-
43
-func init() {
44
-	check.Suite(&DockerAuthzSuite{
45
-		ds: &DockerSuite{},
46
-	})
47
-}
48
-
49
-type DockerAuthzSuite struct {
50
-	server *httptest.Server
51
-	ds     *DockerSuite
52
-	d      *daemon.Daemon
53
-	ctrl   *authorizationController
54
-}
55
-
56
-type authorizationController struct {
57
-	reqRes        authorization.Response // reqRes holds the plugin response to the initial client request
58
-	resRes        authorization.Response // resRes holds the plugin response to the daemon response
59
-	psRequestCnt  int                    // psRequestCnt counts the number of calls to list container request api
60
-	psResponseCnt int                    // psResponseCnt counts the number of calls to list containers response API
61
-	requestsURIs  []string               // requestsURIs stores all request URIs that are sent to the authorization controller
62
-	reqUser       string
63
-	resUser       string
64
-}
65
-
66
-func (s *DockerAuthzSuite) SetUpTest(c *check.C) {
67
-	testRequires(c, SameHostDaemon)
68
-	s.d = daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{
69
-		Experimental: testEnv.ExperimentalDaemon(),
70
-	})
71
-	s.ctrl = &authorizationController{}
72
-}
73
-
74
-func (s *DockerAuthzSuite) TearDownTest(c *check.C) {
75
-	if s.d != nil {
76
-		s.d.Stop(c)
77
-		s.ds.TearDownTest(c)
78
-		s.ctrl = nil
79
-	}
80
-}
81
-
82
-func (s *DockerAuthzSuite) SetUpSuite(c *check.C) {
83
-	mux := http.NewServeMux()
84
-	s.server = httptest.NewServer(mux)
85
-
86
-	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
87
-		b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}})
88
-		c.Assert(err, check.IsNil)
89
-		w.Write(b)
90
-	})
91
-
92
-	mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) {
93
-		defer r.Body.Close()
94
-		body, err := ioutil.ReadAll(r.Body)
95
-		c.Assert(err, check.IsNil)
96
-		authReq := authorization.Request{}
97
-		err = json.Unmarshal(body, &authReq)
98
-		c.Assert(err, check.IsNil)
99
-
100
-		assertBody(c, authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody)
101
-		assertAuthHeaders(c, authReq.RequestHeaders)
102
-
103
-		// Count only container list api
104
-		if strings.HasSuffix(authReq.RequestURI, containerListAPI) {
105
-			s.ctrl.psRequestCnt++
106
-		}
107
-
108
-		s.ctrl.requestsURIs = append(s.ctrl.requestsURIs, authReq.RequestURI)
109
-
110
-		reqRes := s.ctrl.reqRes
111
-		if isAllowed(authReq.RequestURI) {
112
-			reqRes = authorization.Response{Allow: true}
113
-		}
114
-		if reqRes.Err != "" {
115
-			w.WriteHeader(http.StatusInternalServerError)
116
-		}
117
-		b, err := json.Marshal(reqRes)
118
-		c.Assert(err, check.IsNil)
119
-		s.ctrl.reqUser = authReq.User
120
-		w.Write(b)
121
-	})
122
-
123
-	mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) {
124
-		defer r.Body.Close()
125
-		body, err := ioutil.ReadAll(r.Body)
126
-		c.Assert(err, check.IsNil)
127
-		authReq := authorization.Request{}
128
-		err = json.Unmarshal(body, &authReq)
129
-		c.Assert(err, check.IsNil)
130
-
131
-		assertBody(c, authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody)
132
-		assertAuthHeaders(c, authReq.ResponseHeaders)
133
-
134
-		// Count only container list api
135
-		if strings.HasSuffix(authReq.RequestURI, containerListAPI) {
136
-			s.ctrl.psResponseCnt++
137
-		}
138
-		resRes := s.ctrl.resRes
139
-		if isAllowed(authReq.RequestURI) {
140
-			resRes = authorization.Response{Allow: true}
141
-		}
142
-		if resRes.Err != "" {
143
-			w.WriteHeader(http.StatusInternalServerError)
144
-		}
145
-		b, err := json.Marshal(resRes)
146
-		c.Assert(err, check.IsNil)
147
-		s.ctrl.resUser = authReq.User
148
-		w.Write(b)
149
-	})
150
-
151
-	err := os.MkdirAll("/etc/docker/plugins", 0755)
152
-	c.Assert(err, checker.IsNil)
153
-
154
-	fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin)
155
-	err = ioutil.WriteFile(fileName, []byte(s.server.URL), 0644)
156
-	c.Assert(err, checker.IsNil)
157
-}
158
-
159
-// check for always allowed endpoints to not inhibit test framework functions
160
-func isAllowed(reqURI string) bool {
161
-	for _, endpoint := range alwaysAllowed {
162
-		if strings.HasSuffix(reqURI, endpoint) {
163
-			return true
164
-		}
165
-	}
166
-	return false
167
-}
168
-
169
-// assertAuthHeaders validates authentication headers are removed
170
-func assertAuthHeaders(c *check.C, headers map[string]string) error {
171
-	for k := range headers {
172
-		if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") {
173
-			c.Errorf("Found authentication headers in request '%v'", headers)
174
-		}
175
-	}
176
-	return nil
177
-}
178
-
179
-// assertBody asserts that body is removed for non text/json requests
180
-func assertBody(c *check.C, requestURI string, headers map[string]string, body []byte) {
181
-	if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 {
182
-		//return fmt.Errorf("Body included for authentication endpoint %s", string(body))
183
-		c.Errorf("Body included for authentication endpoint %s", string(body))
184
-	}
185
-
186
-	for k, v := range headers {
187
-		if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" {
188
-			return
189
-		}
190
-	}
191
-	if len(body) > 0 {
192
-		c.Errorf("Body included while it should not (Headers: '%v')", headers)
193
-	}
194
-}
195
-
196
-func (s *DockerAuthzSuite) TearDownSuite(c *check.C) {
197
-	if s.server == nil {
198
-		return
199
-	}
200
-
201
-	s.server.Close()
202
-
203
-	err := os.RemoveAll("/etc/docker/plugins")
204
-	c.Assert(err, checker.IsNil)
205
-}
206
-
207
-func (s *DockerAuthzSuite) TestAuthZPluginAllowRequest(c *check.C) {
208
-	// start the daemon and load busybox, --net=none build fails otherwise
209
-	// cause it needs to pull busybox
210
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
211
-	s.ctrl.reqRes.Allow = true
212
-	s.ctrl.resRes.Allow = true
213
-	s.d.LoadBusybox(c)
214
-
215
-	// Ensure command successful
216
-	out, err := s.d.Cmd("run", "-d", "busybox", "top")
217
-	c.Assert(err, check.IsNil)
218
-
219
-	id := strings.TrimSpace(out)
220
-	assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create")
221
-	assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id))
222
-}
223
-
224
-func (s *DockerAuthzSuite) TestAuthZPluginTls(c *check.C) {
225
-
226
-	const testDaemonHTTPSAddr = "tcp://localhost:4271"
227
-	// start the daemon and load busybox, --net=none build fails otherwise
228
-	// cause it needs to pull busybox
229
-	s.d.Start(c,
230
-		"--authorization-plugin="+testAuthZPlugin,
231
-		"--tlsverify",
232
-		"--tlscacert",
233
-		"fixtures/https/ca.pem",
234
-		"--tlscert",
235
-		"fixtures/https/server-cert.pem",
236
-		"--tlskey",
237
-		"fixtures/https/server-key.pem",
238
-		"-H", testDaemonHTTPSAddr)
239
-
240
-	s.ctrl.reqRes.Allow = true
241
-	s.ctrl.resRes.Allow = true
242
-
243
-	out, _ := dockerCmd(
244
-		c,
245
-		"--tlsverify",
246
-		"--tlscacert", "fixtures/https/ca.pem",
247
-		"--tlscert", "fixtures/https/client-cert.pem",
248
-		"--tlskey", "fixtures/https/client-key.pem",
249
-		"-H",
250
-		testDaemonHTTPSAddr,
251
-		"version",
252
-	)
253
-	if !strings.Contains(out, "Server") {
254
-		c.Fatalf("docker version should return information of server side")
255
-	}
256
-
257
-	c.Assert(s.ctrl.reqUser, check.Equals, "client")
258
-	c.Assert(s.ctrl.resUser, check.Equals, "client")
259
-}
260
-
261
-func (s *DockerAuthzSuite) TestAuthZPluginDenyRequest(c *check.C) {
262
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
263
-	s.ctrl.reqRes.Allow = false
264
-	s.ctrl.reqRes.Msg = unauthorizedMessage
265
-
266
-	// Ensure command is blocked
267
-	res, err := s.d.Cmd("ps")
268
-	c.Assert(err, check.NotNil)
269
-	c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
270
-	c.Assert(s.ctrl.psResponseCnt, check.Equals, 0)
271
-
272
-	// Ensure unauthorized message appears in response
273
-	c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage))
274
-}
275
-
276
-// TestAuthZPluginAPIDenyResponse validates that when authorization plugin deny the request, the status code is forbidden
277
-func (s *DockerAuthzSuite) TestAuthZPluginAPIDenyResponse(c *check.C) {
278
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
279
-	s.ctrl.reqRes.Allow = false
280
-	s.ctrl.resRes.Msg = unauthorizedMessage
281
-
282
-	daemonURL, err := url.Parse(s.d.Sock())
283
-
284
-	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
285
-	c.Assert(err, check.IsNil)
286
-	client := httputil.NewClientConn(conn, nil)
287
-	req, err := http.NewRequest("GET", "/version", nil)
288
-	c.Assert(err, check.IsNil)
289
-	resp, err := client.Do(req)
290
-
291
-	c.Assert(err, check.IsNil)
292
-	c.Assert(resp.StatusCode, checker.Equals, http.StatusForbidden)
293
-	c.Assert(err, checker.IsNil)
294
-}
295
-
296
-func (s *DockerAuthzSuite) TestAuthZPluginDenyResponse(c *check.C) {
297
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
298
-	s.ctrl.reqRes.Allow = true
299
-	s.ctrl.resRes.Allow = false
300
-	s.ctrl.resRes.Msg = unauthorizedMessage
301
-
302
-	// Ensure command is blocked
303
-	res, err := s.d.Cmd("ps")
304
-	c.Assert(err, check.NotNil)
305
-	c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
306
-	c.Assert(s.ctrl.psResponseCnt, check.Equals, 1)
307
-
308
-	// Ensure unauthorized message appears in response
309
-	c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s\n", testAuthZPlugin, unauthorizedMessage))
310
-}
311
-
312
-// TestAuthZPluginAllowEventStream verifies event stream propagates correctly after request pass through by the authorization plugin
313
-func (s *DockerAuthzSuite) TestAuthZPluginAllowEventStream(c *check.C) {
314
-	testRequires(c, DaemonIsLinux)
315
-
316
-	// start the daemon and load busybox to avoid pulling busybox from Docker Hub
317
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
318
-	s.ctrl.reqRes.Allow = true
319
-	s.ctrl.resRes.Allow = true
320
-	s.d.LoadBusybox(c)
321
-
322
-	startTime := strconv.FormatInt(daemonTime(c).Unix(), 10)
323
-	// Add another command to to enable event pipelining
324
-	eventsCmd := exec.Command(dockerBinary, "--host", s.d.Sock(), "events", "--since", startTime)
325
-	stdout, err := eventsCmd.StdoutPipe()
326
-	if err != nil {
327
-		c.Assert(err, check.IsNil)
328
-	}
329
-
330
-	observer := eventObserver{
331
-		buffer:    new(bytes.Buffer),
332
-		command:   eventsCmd,
333
-		scanner:   bufio.NewScanner(stdout),
334
-		startTime: startTime,
335
-	}
336
-
337
-	err = observer.Start()
338
-	c.Assert(err, checker.IsNil)
339
-	defer observer.Stop()
340
-
341
-	// Create a container and wait for the creation events
342
-	out, err := s.d.Cmd("run", "-d", "busybox", "top")
343
-	c.Assert(err, check.IsNil, check.Commentf(out))
344
-	containerID := strings.TrimSpace(out)
345
-	c.Assert(s.d.WaitRun(containerID), checker.IsNil)
346
-
347
-	events := map[string]chan bool{
348
-		"create": make(chan bool, 1),
349
-		"start":  make(chan bool, 1),
350
-	}
351
-
352
-	matcher := matchEventLine(containerID, "container", events)
353
-	processor := processEventMatch(events)
354
-	go observer.Match(matcher, processor)
355
-
356
-	// Ensure all events are received
357
-	for event, eventChannel := range events {
358
-
359
-		select {
360
-		case <-time.After(30 * time.Second):
361
-			// Fail the test
362
-			observer.CheckEventError(c, containerID, event, matcher)
363
-			c.FailNow()
364
-		case <-eventChannel:
365
-			// Ignore, event received
366
-		}
367
-	}
368
-
369
-	// Ensure both events and container endpoints are passed to the authorization plugin
370
-	assertURIRecorded(c, s.ctrl.requestsURIs, "/events")
371
-	assertURIRecorded(c, s.ctrl.requestsURIs, "/containers/create")
372
-	assertURIRecorded(c, s.ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", containerID))
373
-}
374
-
375
-func (s *DockerAuthzSuite) TestAuthZPluginErrorResponse(c *check.C) {
376
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
377
-	s.ctrl.reqRes.Allow = true
378
-	s.ctrl.resRes.Err = errorMessage
379
-
380
-	// Ensure command is blocked
381
-	res, err := s.d.Cmd("ps")
382
-	c.Assert(err, check.NotNil)
383
-
384
-	c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage))
385
-}
386
-
387
-func (s *DockerAuthzSuite) TestAuthZPluginErrorRequest(c *check.C) {
388
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin)
389
-	s.ctrl.reqRes.Err = errorMessage
390
-
391
-	// Ensure command is blocked
392
-	res, err := s.d.Cmd("ps")
393
-	c.Assert(err, check.NotNil)
394
-
395
-	c.Assert(res, check.Equals, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s\n", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage))
396
-}
397
-
398
-func (s *DockerAuthzSuite) TestAuthZPluginEnsureNoDuplicatePluginRegistration(c *check.C) {
399
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
400
-
401
-	s.ctrl.reqRes.Allow = true
402
-	s.ctrl.resRes.Allow = true
403
-
404
-	out, err := s.d.Cmd("ps")
405
-	c.Assert(err, check.IsNil, check.Commentf(out))
406
-
407
-	// assert plugin is only called once..
408
-	c.Assert(s.ctrl.psRequestCnt, check.Equals, 1)
409
-	c.Assert(s.ctrl.psResponseCnt, check.Equals, 1)
410
-}
411
-
412
-func (s *DockerAuthzSuite) TestAuthZPluginEnsureLoadImportWorking(c *check.C) {
413
-	s.d.Start(c, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
414
-	s.ctrl.reqRes.Allow = true
415
-	s.ctrl.resRes.Allow = true
416
-	s.d.LoadBusybox(c)
417
-
418
-	tmp, err := ioutil.TempDir("", "test-authz-load-import")
419
-	c.Assert(err, check.IsNil)
420
-	defer os.RemoveAll(tmp)
421
-
422
-	savedImagePath := filepath.Join(tmp, "save.tar")
423
-
424
-	out, err := s.d.Cmd("save", "-o", savedImagePath, "busybox")
425
-	c.Assert(err, check.IsNil, check.Commentf(out))
426
-	out, err = s.d.Cmd("load", "--input", savedImagePath)
427
-	c.Assert(err, check.IsNil, check.Commentf(out))
428
-
429
-	exportedImagePath := filepath.Join(tmp, "export.tar")
430
-
431
-	out, err = s.d.Cmd("run", "-d", "--name", "testexport", "busybox")
432
-	c.Assert(err, check.IsNil, check.Commentf(out))
433
-	out, err = s.d.Cmd("export", "-o", exportedImagePath, "testexport")
434
-	c.Assert(err, check.IsNil, check.Commentf(out))
435
-	out, err = s.d.Cmd("import", exportedImagePath)
436
-	c.Assert(err, check.IsNil, check.Commentf(out))
437
-}
438
-
439
-func (s *DockerAuthzSuite) TestAuthZPluginHeader(c *check.C) {
440
-	s.d.Start(c, "--debug", "--authorization-plugin="+testAuthZPlugin)
441
-	s.ctrl.reqRes.Allow = true
442
-	s.ctrl.resRes.Allow = true
443
-	s.d.LoadBusybox(c)
444
-
445
-	daemonURL, err := url.Parse(s.d.Sock())
446
-
447
-	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
448
-	c.Assert(err, check.IsNil)
449
-	client := httputil.NewClientConn(conn, nil)
450
-	req, err := http.NewRequest("GET", "/version", nil)
451
-	c.Assert(err, check.IsNil)
452
-	resp, err := client.Do(req)
453
-
454
-	c.Assert(err, check.IsNil)
455
-	c.Assert(resp.Header["Content-Type"][0], checker.Equals, "application/json")
456
-}
457
-
458
-// assertURIRecorded verifies that the given URI was sent and recorded in the authz plugin
459
-func assertURIRecorded(c *check.C, uris []string, uri string) {
460
-	var found bool
461
-	for _, u := range uris {
462
-		if strings.Contains(u, uri) {
463
-			found = true
464
-			break
465
-		}
466
-	}
467
-	if !found {
468
-		c.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ","))
469
-	}
470
-}
471 1
deleted file mode 100644
... ...
@@ -1,23 +0,0 @@
1
-MIID0TCCAzqgAwIBAgIJAP2r7GqEJwSnMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
2
-VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMG
3
-A1UEChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMI
4
-Y2hhbmdlbWUxETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWls
5
-QGhvc3QuZG9tYWluMB4XDTEzMTIwMzE2NTYzMFoXDTIzMTIwMTE2NTYzMFowgaIx
6
-CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2Nv
7
-MRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYD
8
-VQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEW
9
-EG1haWxAaG9zdC5kb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAn
10
-0xDw+5y7ZptQacq66pUhRu82JP2WU6IDgo5QUtNU6/CX5PwQATe/OnYTZQFbksxp
11
-AU9boG0FCkgxfsgPYXEuZxVEGKI2fxfKHOZZI8mrkWmj6eWU/0cvCjGVc9rTITP5
12
-sNQvg+hORyVDdNp2IdsbMJayiB3AQYMFx3vSDOMTAgMBAAGjggELMIIBBzAdBgNV
13
-HQ4EFgQUZu7DFz09q0QBa2+ymRm9qgK1NPswgdcGA1UdIwSBzzCBzIAUZu7DFz09
14
-q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
15
-QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
16
-ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
17
-Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
18
-hCcEpzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAF8fJKKM+/oOdnNi
19
-zEd0M1+PmZOyqvjYQn/2ZR8UHH6Imgc/OPQKZXf0bVE1Txc/DaUNn9Isd1SuCuaE
20
-ic3vAIYYU7PmgeNN6vwec48V96T7jr+GAi6AVMhQEc2hHCfVtx11Xx+x6aHDZzJt
21
-Zxtf5lL6KSO9Y+EFwM+rju6hm5hW
22 1
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../../../integration/testdata/https/ca.pem
0 1
\ No newline at end of file
1 2
deleted file mode 100644
... ...
@@ -1,73 +0,0 @@
1
-Certificate:
2
-    Data:
3
-        Version: 3 (0x2)
4
-        Serial Number: 3 (0x3)
5
-    Signature Algorithm: sha1WithRSAEncryption
6
-        Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
7
-        Validity
8
-            Not Before: Dec  4 14:17:54 2013 GMT
9
-            Not After : Dec  2 14:17:54 2023 GMT
10
-        Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=client/name=changeme/emailAddress=mail@host.domain
11
-        Subject Public Key Info:
12
-            Public Key Algorithm: rsaEncryption
13
-                Public-Key: (1024 bit)
14
-                Modulus:
15
-                    00:ca:c9:05:d0:09:4e:3e:a4:fc:d5:14:f4:a5:e8:
16
-                    34:d3:6b:51:e3:f3:62:ea:a1:f0:e8:ed:c4:2a:bc:
17
-                    f0:4f:ca:07:df:e3:88:fa:f4:21:99:35:0e:3d:ea:
18
-                    b0:86:e7:c4:d2:8a:83:2b:42:b8:ec:a3:99:62:70:
19
-                    81:46:cc:fc:a5:1d:d2:63:e8:eb:07:25:9a:e2:25:
20
-                    6d:11:56:f2:1a:51:a1:b6:3e:1c:57:32:e9:7b:2c:
21
-                    aa:1b:cc:97:2d:89:2d:b1:c9:5e:35:28:4d:7c:fa:
22
-                    65:31:3e:f7:70:dd:6e:0b:3c:58:af:a8:2e:24:c0:
23
-                    7e:4e:78:7d:0a:9e:8f:42:43
24
-                Exponent: 65537 (0x10001)
25
-        X509v3 extensions:
26
-            X509v3 Basic Constraints: 
27
-                CA:FALSE
28
-            Netscape Comment: 
29
-                Easy-RSA Generated Certificate
30
-            X509v3 Subject Key Identifier: 
31
-                DE:42:EF:2D:98:A3:6C:A8:AA:E0:8C:71:2C:9D:64:23:A9:E2:7E:81
32
-            X509v3 Authority Key Identifier: 
33
-                keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
34
-                DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
35
-                serial:FD:AB:EC:6A:84:27:04:A7
36
-
37
-            X509v3 Extended Key Usage: 
38
-                TLS Web Client Authentication
39
-            X509v3 Key Usage: 
40
-                Digital Signature
41
-    Signature Algorithm: sha1WithRSAEncryption
42
-         1c:44:26:ea:e1:66:25:cb:e4:8e:57:1c:f6:b9:17:22:62:40:
43
-         12:90:8f:3b:b2:61:7a:54:94:8f:b1:20:0b:bf:a3:51:e3:fa:
44
-         1c:a1:be:92:3a:d0:76:44:c0:57:83:ab:6a:e4:1a:45:49:a4:
45
-         af:39:0d:60:32:fc:3a:be:d7:fb:5d:99:7a:1f:87:e7:d5:ab:
46
-         84:a2:5e:90:d8:bf:fa:89:6d:32:26:02:5e:31:35:68:7f:31:
47
-         f5:6b:51:46:bc:af:70:ed:5a:09:7d:ec:b2:48:4f:fe:c5:2f:
48
-         56:04:ad:f6:c1:d2:2a:e4:6a:c4:87:fe:08:35:c5:38:cb:5e:
49
-         4a:c4
50
-MIIEFTCCA36gAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
51
-CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
52
-cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
53
-MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
54
-bWFpbjAeFw0xMzEyMDQxNDE3NTRaFw0yMzEyMDIxNDE3NTRaMIGgMQswCQYDVQQG
55
-EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
56
-ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEPMA0GA1UEAxMGY2xp
57
-ZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0
58
-LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyskF0AlOPqT81RT0
59
-peg002tR4/Ni6qHw6O3EKrzwT8oH3+OI+vQhmTUOPeqwhufE0oqDK0K47KOZYnCB
60
-Rsz8pR3SY+jrByWa4iVtEVbyGlGhtj4cVzLpeyyqG8yXLYktscleNShNfPplMT73
61
-cN1uCzxYr6guJMB+Tnh9Cp6PQkMCAwEAAaOCAVkwggFVMAkGA1UdEwQCMAAwLQYJ
62
-YIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
63
-HQ4EFgQU3kLvLZijbKiq4IxxLJ1kI6nifoEwgdcGA1UdIwSBzzCBzIAUZu7DFz09
64
-q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
65
-QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
66
-ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
67
-Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
68
-hCcEpzATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcN
69
-AQEFBQADgYEAHEQm6uFmJcvkjlcc9rkXImJAEpCPO7JhelSUj7EgC7+jUeP6HKG+
70
-kjrQdkTAV4OrauQaRUmkrzkNYDL8Or7X+12Zeh+H59WrhKJekNi/+oltMiYCXjE1
71
-aH8x9WtRRryvcO1aCX3sskhP/sUvVgSt9sHSKuRqxIf+CDXFOMteSsQ=
72 1
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../../../integration/testdata/https/client-cert.pem
0 1
\ No newline at end of file
1 2
deleted file mode 100644
... ...
@@ -1,16 +0,0 @@
1
-MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMrJBdAJTj6k/NUU
2
-9KXoNNNrUePzYuqh8OjtxCq88E/KB9/jiPr0IZk1Dj3qsIbnxNKKgytCuOyjmWJw
3
-gUbM/KUd0mPo6wclmuIlbRFW8hpRobY+HFcy6XssqhvMly2JLbHJXjUoTXz6ZTE+
4
-93Ddbgs8WK+oLiTAfk54fQqej0JDAgMBAAECgYBOFEzKp2qbMEexe9ofL2N3rDDh
5
-xkrl8OijpzkLA6i78BxMFn4dsnZlWUpciMrjhsYAExkiRRSS+QMMJimAq1jzQqc3
6
-FAQV2XGYwkd0cUn7iZGvfNnEPysjsfyYQM+m+sT0ATj4BZjVShC6kkSjTdm1leLN
7
-OSvcHdcu3Xxg9ufF0QJBAPYdnNt5sIndt2WECePuRVi+uF4mlxTobFY0fjn26yhC
8
-4RsnhhD3Vldygo9gvnkwrAZYaALGSPBewes2InxvjA8CQQDS7erKiNXpwoqz5XiU
9
-SVEsIIVTdWzBjGbIqMOu/hUwM5FK4j6JTBks0aTGMyh0YV9L1EzM0X79J29JahCe
10
-iQKNAkBKNMOGqTpBV0hko1sYDk96YobUXG5RL4L6uvkUIQ7mJMQam+AgXXL7Ctuy
11
-v0iu4a38e8tgisiTMP7nHHtpaXihAkAOiN54/lzfMsykANgCP9scE1GcoqbP34Dl
12
-qttxH4kOPT9xzY1JoLjLYdbc4YGUI3GRpBt2sajygNkmUey7P+2xAkBBsVCZFvTw
13
-qHvOpPS2kX5ml5xoc/QAHK9N7kR+X7XFYx82RTVSqJEK4lPb+aEWn+CjiIewO4Q5
14
-ksDFuNxAzbhl
15 1
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../../../integration/testdata/https/client-key.pem
0 1
\ No newline at end of file
1 2
deleted file mode 100644
... ...
@@ -1,76 +0,0 @@
1
-Certificate:
2
-    Data:
3
-        Version: 3 (0x2)
4
-        Serial Number: 4 (0x4)
5
-    Signature Algorithm: sha1WithRSAEncryption
6
-        Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
7
-        Validity
8
-            Not Before: Dec  4 15:01:20 2013 GMT
9
-            Not After : Dec  2 15:01:20 2023 GMT
10
-        Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=*/name=changeme/emailAddress=mail@host.domain
11
-        Subject Public Key Info:
12
-            Public Key Algorithm: rsaEncryption
13
-                Public-Key: (1024 bit)
14
-                Modulus:
15
-                    00:c1:ff:7d:30:6f:64:4a:b1:92:b1:71:d1:c1:74:
16
-                    e2:1d:db:2d:11:24:e1:00:d4:00:ae:6f:c8:9e:ae:
17
-                    67:b3:4a:bd:f7:e6:9e:57:6d:19:4c:3c:23:94:2d:
18
-                    3d:d6:63:84:d8:fa:76:2b:38:12:c1:ed:20:9d:32:
19
-                    e0:e8:c2:bf:9a:77:70:04:3f:7f:ca:8c:2c:82:d6:
20
-                    3d:25:5c:02:1a:4f:64:93:03:dd:9c:42:97:5e:09:
21
-                    49:af:f0:c2:e1:30:08:0e:21:46:95:d1:13:59:c0:
22
-                    c8:76:be:94:0d:8b:43:67:21:33:b2:08:60:9d:76:
23
-                    a8:05:32:1e:f9:95:09:14:75
24
-                Exponent: 65537 (0x10001)
25
-        X509v3 extensions:
26
-            X509v3 Basic Constraints: 
27
-                CA:FALSE
28
-            Netscape Cert Type: 
29
-                SSL Server
30
-            Netscape Comment: 
31
-                Easy-RSA Generated Server Certificate
32
-            X509v3 Subject Key Identifier: 
33
-                14:02:FD:FD:DD:13:38:E0:71:EA:D1:BE:C0:0E:89:1A:2D:B6:19:06
34
-            X509v3 Authority Key Identifier: 
35
-                keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
36
-                DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
37
-                serial:FD:AB:EC:6A:84:27:04:A7
38
-
39
-            X509v3 Extended Key Usage: 
40
-                TLS Web Server Authentication
41
-            X509v3 Key Usage: 
42
-                Digital Signature, Key Encipherment
43
-    Signature Algorithm: sha1WithRSAEncryption
44
-         40:0f:10:39:c4:b7:0f:0d:2f:bf:d2:16:cc:8e:d3:9a:fb:8b:
45
-         ce:4b:7b:0d:48:77:ce:f1:fe:d5:8f:ea:b1:71:ed:49:1d:9f:
46
-         23:3a:16:d4:70:7c:c5:29:bf:e4:90:34:d0:f0:00:24:f4:e4:
47
-         df:2c:c3:83:01:66:61:c9:a8:ab:29:e7:98:6d:27:89:4a:76:
48
-         c9:2e:19:8e:fe:6e:d5:f8:99:11:0e:97:67:4b:34:e3:1e:e3:
49
-         9f:35:00:a5:32:f9:b5:2c:f2:e0:c5:2e:cc:81:bd:18:dd:5c:
50
-         12:c8:6b:fa:0c:17:74:30:55:f6:6e:20:9a:6c:1e:09:b4:0c:
51
-         15:42
52
-MIIEKjCCA5OgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
53
-CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
54
-cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
55
-MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
56
-bWFpbjAeFw0xMzEyMDQxNTAxMjBaFw0yMzEyMDIxNTAxMjBaMIGbMQswCQYDVQQG
57
-EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
58
-ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEKMAgGA1UEAxQBKjER
59
-MA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21h
60
-aW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMH/fTBvZEqxkrFx0cF04h3b
61
-LREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y4OjCv5p3
62
-cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+lA2LQ2ch
63
-M7IIYJ12qAUyHvmVCRR1AgMBAAGjggFzMIIBbzAJBgNVHRMEAjAAMBEGCWCGSAGG
64
-+EIBAQQEAwIGQDA0BglghkgBhvhCAQ0EJxYlRWFzeS1SU0EgR2VuZXJhdGVkIFNl
65
-cnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUFAL9/d0TOOBx6tG+wA6JGi22GQYw
66
-gdcGA1UdIwSBzzCBzIAUZu7DFz09q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJ
67
-BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUw
68
-EwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQD
69
-EwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1h
70
-aWxAaG9zdC5kb21haW6CCQD9q+xqhCcEpzATBgNVHSUEDDAKBggrBgEFBQcDATAL
71
-BgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAQA8QOcS3Dw0vv9IWzI7TmvuL
72
-zkt7DUh3zvH+1Y/qsXHtSR2fIzoW1HB8xSm/5JA00PAAJPTk3yzDgwFmYcmoqynn
73
-mG0niUp2yS4Zjv5u1fiZEQ6XZ0s04x7jnzUApTL5tSzy4MUuzIG9GN1cEshr+gwX
74
-dDBV9m4gmmweCbQMFUI=
75 1
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../../../integration/testdata/https/server-cert.pem
0 1
\ No newline at end of file
1 2
deleted file mode 100644
... ...
@@ -1,16 +0,0 @@
1
-MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMH/fTBvZEqxkrFx
2
-0cF04h3bLREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y
3
-4OjCv5p3cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+
4
-lA2LQ2chM7IIYJ12qAUyHvmVCRR1AgMBAAECgYAmwckb9RUfSwyYgLm8IYLPHiuJ
5
-wkllZfVg5Bo7gXJcQnFjZmJ56uTj8xvUjZlODIHM63TSO5ibv6kFXtXKCqZGd2M+
6
-wGbhZ0f+2GvKcwMmJERnIQjuoNaYSQLT0tM0VB9Iz0rJlZC+tzPZ+5pPqEumRdsS
7
-IzWNXfF42AhcbwAQYQJBAPVXtMYIJc9EZsz86ZcQiMPWUpCX5vnRmtwL8kKyR8D5
8
-4KfYeiowyFffSRMMcclwNHq7TgSXN+nIXM9WyzyzwikCQQDKbNA28AgZp9aT54HP
9
-WnbeE2pmt+uk/zl/BtxJSoK6H+69Jec+lf7EgL7HgOWYRSNot4uQWu8IhsHLTiUq
10
-+0FtAkEAqwlRxRy4/x24bP+D+QRV0/D97j93joFJbE4Hved7jlSlAV4xDGilwlyv
11
-HNB4Iu5OJ6Gcaibhm+FKkmD3noHSwQJBAIpu3fokLzX0bS+bDFBU6qO3HXX/47xj
12
-+tsfQvkwZrSI8AkU6c8IX0HdVhsz0FBRQAT2ORDQz1XCarfxykNZrwUCQQCGCBIc
13
-BBCWzhHlswlGidWJg3HqqO6hPPClEr3B5G87oCsdeYwiO23XT6rUnoJXfJHp6oCW
14
-5nCwDu5ZTP+khltg
15 1
new file mode 120000
... ...
@@ -0,0 +1 @@
0
+../../../integration/testdata/https/server-key.pem
0 1
\ No newline at end of file
1 2
new file mode 100644
... ...
@@ -0,0 +1,128 @@
0
+package container
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"os"
6
+	"time"
7
+
8
+	"github.com/docker/docker/api/types"
9
+	"github.com/docker/docker/api/types/container"
10
+	networktypes "github.com/docker/docker/api/types/network"
11
+	"github.com/docker/docker/client"
12
+)
13
+
14
+// CreateVMemLabel creates a container with the supplied parameters
15
+func CreateVMemLabel(client client.APIClient, v []string, memoryMB int, labels map[string]string, img string, cmd []string) (string, error) {
16
+	ctx := context.Background()
17
+	config := container.Config{
18
+		Cmd:    cmd,
19
+		Image:  img,
20
+		Labels: labels,
21
+	}
22
+	hostConfig := container.HostConfig{
23
+		Binds: v,
24
+		Resources: container.Resources{
25
+			Memory: int64(memoryMB * 1024 * 1024),
26
+		},
27
+	}
28
+	networkingConfig := networktypes.NetworkingConfig{}
29
+	name := ""
30
+	response, err := client.ContainerCreate(ctx, &config, &hostConfig, &networkingConfig, name)
31
+
32
+	if err != nil {
33
+		return "", err
34
+	}
35
+
36
+	return response.ID, nil
37
+}
38
+
39
+// Run runs the command provided in the container image named
40
+func Run(client client.APIClient, img string, cmd []string) (string, error) {
41
+	return RunV(client, []string{}, img, cmd)
42
+}
43
+
44
+// RunV runs the command provided in the container image named with
45
+// the equivalent of -v bind mounts
46
+func RunV(client client.APIClient, v []string, img string, cmd []string) (string, error) {
47
+	return RunVMem(client, v, 0, img, cmd)
48
+}
49
+
50
+// RunVMem runs the command provided in the container image named with
51
+// the equivalent of -v bind mounts and a specified memory limit
52
+func RunVMem(client client.APIClient, v []string, memoryMB int, img string, cmd []string) (string, error) {
53
+	return RunVMemLabel(client, v, memoryMB, map[string]string{}, img, cmd)
54
+}
55
+
56
+// RunVLabel runs the command provided in the container image named
57
+// with the equivalent of -v bind mounts and the specified labels
58
+func RunVLabel(client client.APIClient, v []string, labels map[string]string, img string, cmd []string) (string, error) {
59
+	return RunVMemLabel(client, v, 0, labels, img, cmd)
60
+}
61
+
62
+// RunVMemLabel runs the command provided in the container image named
63
+// with the equivalent of -v bind mounts, a specified memory limit,
64
+// and the specified labels
65
+func RunVMemLabel(client client.APIClient, v []string, memoryMB int, labels map[string]string, img string, cmd []string) (string, error) {
66
+	containerID, err := CreateVMemLabel(client, v, memoryMB, labels, img, cmd)
67
+	if err != nil {
68
+		return "", err
69
+	}
70
+
71
+	ctx := context.Background()
72
+	if err := client.ContainerStart(ctx, containerID, types.ContainerStartOptions{}); err != nil {
73
+		return "", err
74
+	}
75
+
76
+	return containerID, nil
77
+}
78
+
79
+// Wait waits until the named container has exited
80
+func Wait(client client.APIClient, container string) (int64, error) {
81
+	resultC, errC := client.ContainerWait(context.Background(), container, "")
82
+
83
+	select {
84
+	case result := <-resultC:
85
+		return result.StatusCode, nil
86
+	case err := <-errC:
87
+		return -1, err
88
+	}
89
+}
90
+
91
+// Stop stops the named container
92
+func Stop(client client.APIClient, container string) error {
93
+	timeout := time.Duration(10) * time.Second
94
+	ctx := context.Background()
95
+	return client.ContainerStop(ctx, container, &timeout)
96
+}
97
+
98
+// Start starts the named container
99
+func Start(client client.APIClient, container string) error {
100
+	ctx := context.Background()
101
+	return client.ContainerStart(ctx, container, types.ContainerStartOptions{})
102
+}
103
+
104
+// Kill kills the named container with SIGKILL
105
+func Kill(client client.APIClient, container string) error {
106
+	ctx := context.Background()
107
+	return client.ContainerKill(ctx, container, "KILL")
108
+}
109
+
110
+// Export exports a container's file system as a tarball
111
+func Export(client client.APIClient, path, name string) error {
112
+	ctx := context.Background()
113
+	responseReader, err := client.ContainerExport(ctx, name)
114
+	if err != nil {
115
+		return err
116
+	}
117
+	defer responseReader.Close()
118
+
119
+	file, err := os.Create(path)
120
+	if err != nil {
121
+		return err
122
+	}
123
+	defer file.Close()
124
+
125
+	_, err = io.Copy(file, responseReader)
126
+	return err
127
+}
0 128
new file mode 100644
... ...
@@ -0,0 +1,66 @@
0
+package image
1
+
2
+import (
3
+	"context"
4
+	"io"
5
+	"os"
6
+
7
+	"github.com/docker/docker/api/types"
8
+	"github.com/docker/docker/client"
9
+)
10
+
11
+// Save saves an image to a tarball names by path
12
+func Save(client client.APIClient, path, image string) error {
13
+	ctx := context.Background()
14
+	responseReader, err := client.ImageSave(ctx, []string{image})
15
+	if err != nil {
16
+		return err
17
+	}
18
+	defer responseReader.Close()
19
+	file, err := os.Create(path)
20
+	if err != nil {
21
+		return err
22
+	}
23
+	defer file.Close()
24
+	_, err = io.Copy(file, responseReader)
25
+	return err
26
+}
27
+
28
+// Load loads an image from a tarball named by path
29
+func Load(client client.APIClient, path string) error {
30
+	file, err := os.Open(path)
31
+	if err != nil {
32
+		return err
33
+	}
34
+	defer file.Close()
35
+	quiet := true
36
+	ctx := context.Background()
37
+	response, err := client.ImageLoad(ctx, file, quiet)
38
+	if err != nil {
39
+		return err
40
+	}
41
+	defer response.Body.Close()
42
+	return nil
43
+}
44
+
45
+// Import imports the contents of a tarball named by path
46
+func Import(client client.APIClient, path string) error {
47
+	file, err := os.Open(path)
48
+	if err != nil {
49
+		return err
50
+	}
51
+	defer file.Close()
52
+	options := types.ImageImportOptions{}
53
+	ref := ""
54
+	source := types.ImageImportSource{
55
+		Source:     file,
56
+		SourceName: "-",
57
+	}
58
+	ctx := context.Background()
59
+	responseReader, err := client.ImageImport(ctx, source, ref, options)
60
+	if err != nil {
61
+		return err
62
+	}
63
+	defer responseReader.Close()
64
+	return nil
65
+}
0 66
new file mode 100644
... ...
@@ -0,0 +1,59 @@
0
+package plugin
1
+
2
+import (
3
+	"context"
4
+	"io/ioutil"
5
+
6
+	"github.com/docker/docker/api/types"
7
+	"github.com/docker/docker/client"
8
+)
9
+
10
+// InstallGrantAllPermissions installs the plugin named and grants it
11
+// all permissions it may require
12
+func InstallGrantAllPermissions(client client.APIClient, name string) error {
13
+	ctx := context.Background()
14
+	options := types.PluginInstallOptions{
15
+		RemoteRef:            name,
16
+		AcceptAllPermissions: true,
17
+	}
18
+	responseReader, err := client.PluginInstall(ctx, "", options)
19
+	if err != nil {
20
+		return err
21
+	}
22
+	defer responseReader.Close()
23
+	// we have to read the response out here because the client API
24
+	// actually starts a goroutine which we can only be sure has
25
+	// completed when we get EOF from reading responseBody
26
+	_, err = ioutil.ReadAll(responseReader)
27
+	return err
28
+}
29
+
30
+// Enable enables the named plugin
31
+func Enable(client client.APIClient, name string) error {
32
+	ctx := context.Background()
33
+	options := types.PluginEnableOptions{}
34
+	return client.PluginEnable(ctx, name, options)
35
+}
36
+
37
+// Disable disables the named plugin
38
+func Disable(client client.APIClient, name string) error {
39
+	ctx := context.Background()
40
+	options := types.PluginDisableOptions{}
41
+	return client.PluginDisable(ctx, name, options)
42
+}
43
+
44
+// Rm removes the named plugin
45
+func Rm(client client.APIClient, name string) error {
46
+	return remove(client, name, false)
47
+}
48
+
49
+// RmF forces the removal of the named plugin
50
+func RmF(client client.APIClient, name string) error {
51
+	return remove(client, name, true)
52
+}
53
+
54
+func remove(client client.APIClient, name string, force bool) error {
55
+	ctx := context.Background()
56
+	options := types.PluginRemoveOptions{Force: force}
57
+	return client.PluginRemove(ctx, name, options)
58
+}
0 59
new file mode 100644
... ...
@@ -0,0 +1,45 @@
0
+package system
1
+
2
+import (
3
+	"context"
4
+	"testing"
5
+	"time"
6
+
7
+	"github.com/docker/docker/api/types"
8
+	"github.com/docker/docker/api/types/events"
9
+	"github.com/docker/docker/client"
10
+	"github.com/docker/docker/internal/test/environment"
11
+	"github.com/stretchr/testify/require"
12
+)
13
+
14
+// Time provides the current time on the daemon host
15
+func Time(t *testing.T, client client.APIClient, testEnv *environment.Execution) time.Time {
16
+	if testEnv.IsLocalDaemon() {
17
+		return time.Now()
18
+	}
19
+
20
+	ctx := context.Background()
21
+	info, err := client.Info(ctx)
22
+	require.Nil(t, err)
23
+
24
+	dt, err := time.Parse(time.RFC3339Nano, info.SystemTime)
25
+	require.Nil(t, err, "invalid time format in GET /info response")
26
+	return dt
27
+}
28
+
29
+// Version provides the version of the daemon
30
+func Version(client client.APIClient) (types.Version, error) {
31
+	ctx := context.Background()
32
+	return client.ServerVersion(ctx)
33
+}
34
+
35
+// EventsSince returns event and error streams since a provided time
36
+func EventsSince(client client.APIClient, since string) (<-chan events.Message, <-chan error, func()) {
37
+	eventOptions := types.EventsOptions{
38
+		Since: since,
39
+	}
40
+	ctx, cancel := context.WithCancel(context.Background())
41
+	events, errs := client.Events(ctx, eventOptions)
42
+
43
+	return events, errs, cancel
44
+}
0 45
new file mode 100644
... ...
@@ -0,0 +1,61 @@
0
+package volume
1
+
2
+import (
3
+	"context"
4
+
5
+	"github.com/docker/docker/api/types"
6
+	"github.com/docker/docker/api/types/filters"
7
+	volumetypes "github.com/docker/docker/api/types/volume"
8
+	"github.com/docker/docker/client"
9
+)
10
+
11
+// Create creates a volume using the named driver with the specified options
12
+func Create(client client.APIClient, driver string, opts map[string]string) (string, error) {
13
+	volReq := volumetypes.VolumesCreateBody{
14
+		Driver:     driver,
15
+		DriverOpts: opts,
16
+		Name:       "",
17
+	}
18
+
19
+	ctx := context.Background()
20
+	vol, err := client.VolumeCreate(ctx, volReq)
21
+	if err != nil {
22
+		return "", err
23
+	}
24
+	return vol.Name, nil
25
+}
26
+
27
+// Rm removes the volume named
28
+func Rm(client client.APIClient, name string) error {
29
+	ctx := context.Background()
30
+	return client.VolumeRemove(ctx, name, false)
31
+}
32
+
33
+// Ls lists the volumes available
34
+func Ls(client client.APIClient) ([]string, error) {
35
+	ctx := context.Background()
36
+	volumes, err := client.VolumeList(ctx, filters.Args{})
37
+	if err != nil {
38
+		return []string{}, err
39
+	}
40
+
41
+	names := []string{}
42
+	for _, volume := range volumes.Volumes {
43
+		names = append(names, volume.Name)
44
+	}
45
+
46
+	return names, nil
47
+}
48
+
49
+// Prune removes all volumes not used by at least one container
50
+func Prune(client client.APIClient) (types.VolumesPruneReport, error) {
51
+	ctx := context.Background()
52
+
53
+	return client.VolumesPrune(ctx, filters.Args{})
54
+}
55
+
56
+// Inspect retrieves detailed information about the named volume
57
+func Inspect(client client.APIClient, name string) (types.Volume, error) {
58
+	ctx := context.Background()
59
+	return client.VolumeInspect(ctx, name)
60
+}
0 61
new file mode 100644
... ...
@@ -0,0 +1,370 @@
0
+// +build !windows
1
+
2
+package authz
3
+
4
+import (
5
+	"context"
6
+	"fmt"
7
+	"io"
8
+	"io/ioutil"
9
+	"net"
10
+	"net/http"
11
+	"net/http/httputil"
12
+	"net/url"
13
+	"os"
14
+	"path/filepath"
15
+	"strconv"
16
+	"strings"
17
+	"testing"
18
+	"time"
19
+
20
+	eventtypes "github.com/docker/docker/api/types/events"
21
+	"github.com/docker/docker/integration/internal/api/container"
22
+	"github.com/docker/docker/integration/internal/api/image"
23
+	"github.com/docker/docker/integration/internal/api/system"
24
+	"github.com/docker/docker/integration/util/request"
25
+	"github.com/docker/docker/pkg/authorization"
26
+	"github.com/gotestyourself/gotestyourself/skip"
27
+	"github.com/stretchr/testify/require"
28
+)
29
+
30
+const (
31
+	testAuthZPlugin     = "authzplugin"
32
+	unauthorizedMessage = "User unauthorized authz plugin"
33
+	errorMessage        = "something went wrong..."
34
+	serverVersionAPI    = "/version"
35
+)
36
+
37
+var (
38
+	alwaysAllowed = []string{"/_ping", "/info"}
39
+	ctrl          *authorizationController
40
+)
41
+
42
+type authorizationController struct {
43
+	reqRes          authorization.Response // reqRes holds the plugin response to the initial client request
44
+	resRes          authorization.Response // resRes holds the plugin response to the daemon response
45
+	versionReqCount int                    // versionReqCount counts the number of requests to the server version API endpoint
46
+	versionResCount int                    // versionResCount counts the number of responses from the server version API endpoint
47
+	requestsURIs    []string               // requestsURIs stores all request URIs that are sent to the authorization controller
48
+	reqUser         string
49
+	resUser         string
50
+}
51
+
52
+func setupTestV1(t *testing.T) func() {
53
+	ctrl = &authorizationController{}
54
+	teardown := setupTest(t)
55
+
56
+	err := os.MkdirAll("/etc/docker/plugins", 0755)
57
+	require.Nil(t, err)
58
+
59
+	fileName := fmt.Sprintf("/etc/docker/plugins/%s.spec", testAuthZPlugin)
60
+	err = ioutil.WriteFile(fileName, []byte(server.URL), 0644)
61
+	require.Nil(t, err)
62
+
63
+	return func() {
64
+		err := os.RemoveAll("/etc/docker/plugins")
65
+		require.Nil(t, err)
66
+
67
+		teardown()
68
+		ctrl = nil
69
+	}
70
+}
71
+
72
+// check for always allowed endpoints to not inhibit test framework functions
73
+func isAllowed(reqURI string) bool {
74
+	for _, endpoint := range alwaysAllowed {
75
+		if strings.HasSuffix(reqURI, endpoint) {
76
+			return true
77
+		}
78
+	}
79
+	return false
80
+}
81
+
82
+func TestAuthZPluginAllowRequest(t *testing.T) {
83
+	defer setupTestV1(t)()
84
+	ctrl.reqRes.Allow = true
85
+	ctrl.resRes.Allow = true
86
+	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
87
+
88
+	client, err := d.NewClient()
89
+	require.Nil(t, err)
90
+
91
+	// Ensure command successful
92
+	id, err := container.Run(client, "busybox", []string{"top"})
93
+	require.Nil(t, err)
94
+
95
+	assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
96
+	assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id))
97
+
98
+	_, err = system.Version(client)
99
+	require.Nil(t, err)
100
+	require.Equal(t, 1, ctrl.versionReqCount)
101
+	require.Equal(t, 1, ctrl.versionResCount)
102
+}
103
+
104
+func TestAuthZPluginTLS(t *testing.T) {
105
+	defer setupTestV1(t)()
106
+	const (
107
+		testDaemonHTTPSAddr = "tcp://localhost:4271"
108
+		cacertPath          = "../../testdata/https/ca.pem"
109
+		serverCertPath      = "../../testdata/https/server-cert.pem"
110
+		serverKeyPath       = "../../testdata/https/server-key.pem"
111
+		clientCertPath      = "../../testdata/https/client-cert.pem"
112
+		clientKeyPath       = "../../testdata/https/client-key.pem"
113
+	)
114
+
115
+	d.Start(t,
116
+		"--authorization-plugin="+testAuthZPlugin,
117
+		"--tlsverify",
118
+		"--tlscacert", cacertPath,
119
+		"--tlscert", serverCertPath,
120
+		"--tlskey", serverKeyPath,
121
+		"-H", testDaemonHTTPSAddr)
122
+
123
+	ctrl.reqRes.Allow = true
124
+	ctrl.resRes.Allow = true
125
+
126
+	client, err := request.NewTLSAPIClient(t, testDaemonHTTPSAddr, cacertPath, clientCertPath, clientKeyPath)
127
+	require.Nil(t, err)
128
+
129
+	_, err = system.Version(client)
130
+	require.Nil(t, err)
131
+
132
+	require.Equal(t, "client", ctrl.reqUser)
133
+	require.Equal(t, "client", ctrl.resUser)
134
+}
135
+
136
+func TestAuthZPluginDenyRequest(t *testing.T) {
137
+	defer setupTestV1(t)()
138
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
139
+	ctrl.reqRes.Allow = false
140
+	ctrl.reqRes.Msg = unauthorizedMessage
141
+
142
+	client, err := d.NewClient()
143
+	require.Nil(t, err)
144
+
145
+	// Ensure command is blocked
146
+	_, err = system.Version(client)
147
+	require.NotNil(t, err)
148
+	require.Equal(t, 1, ctrl.versionReqCount)
149
+	require.Equal(t, 0, ctrl.versionResCount)
150
+
151
+	// Ensure unauthorized message appears in response
152
+	require.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
153
+}
154
+
155
+// TestAuthZPluginAPIDenyResponse validates that when authorization
156
+// plugin deny the request, the status code is forbidden
157
+func TestAuthZPluginAPIDenyResponse(t *testing.T) {
158
+	defer setupTestV1(t)()
159
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
160
+	ctrl.reqRes.Allow = false
161
+	ctrl.resRes.Msg = unauthorizedMessage
162
+
163
+	daemonURL, err := url.Parse(d.Sock())
164
+	require.Nil(t, err)
165
+
166
+	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
167
+	require.Nil(t, err)
168
+	client := httputil.NewClientConn(conn, nil)
169
+	req, err := http.NewRequest("GET", "/version", nil)
170
+	require.Nil(t, err)
171
+	resp, err := client.Do(req)
172
+
173
+	require.Nil(t, err)
174
+	require.Equal(t, http.StatusForbidden, resp.StatusCode)
175
+}
176
+
177
+func TestAuthZPluginDenyResponse(t *testing.T) {
178
+	defer setupTestV1(t)()
179
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
180
+	ctrl.reqRes.Allow = true
181
+	ctrl.resRes.Allow = false
182
+	ctrl.resRes.Msg = unauthorizedMessage
183
+
184
+	client, err := d.NewClient()
185
+	require.Nil(t, err)
186
+
187
+	// Ensure command is blocked
188
+	_, err = system.Version(client)
189
+	require.NotNil(t, err)
190
+	require.Equal(t, 1, ctrl.versionReqCount)
191
+	require.Equal(t, 1, ctrl.versionResCount)
192
+
193
+	// Ensure unauthorized message appears in response
194
+	require.Equal(t, fmt.Sprintf("Error response from daemon: authorization denied by plugin %s: %s", testAuthZPlugin, unauthorizedMessage), err.Error())
195
+}
196
+
197
+// TestAuthZPluginAllowEventStream verifies event stream propagates
198
+// correctly after request pass through by the authorization plugin
199
+func TestAuthZPluginAllowEventStream(t *testing.T) {
200
+	skip.IfCondition(t, testEnv.DaemonInfo.OSType != "linux")
201
+
202
+	defer setupTestV1(t)()
203
+	ctrl.reqRes.Allow = true
204
+	ctrl.resRes.Allow = true
205
+	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin)
206
+
207
+	client, err := d.NewClient()
208
+	require.Nil(t, err)
209
+
210
+	startTime := strconv.FormatInt(system.Time(t, client, testEnv).Unix(), 10)
211
+	events, errs, cancel := system.EventsSince(client, startTime)
212
+	defer cancel()
213
+
214
+	// Create a container and wait for the creation events
215
+	id, err := container.Run(client, "busybox", []string{"top"})
216
+	require.Nil(t, err)
217
+	for i := 0; i < 100; i++ {
218
+		c, err := client.ContainerInspect(context.Background(), id)
219
+		require.Nil(t, err)
220
+		if c.State.Running {
221
+			break
222
+		}
223
+		if i == 99 {
224
+			t.Fatal("Container didn't run within 10s")
225
+		}
226
+		time.Sleep(100 * time.Millisecond)
227
+	}
228
+
229
+	created := false
230
+	started := false
231
+	for !created && !started {
232
+		select {
233
+		case event := <-events:
234
+			if event.Type == eventtypes.ContainerEventType && event.Actor.ID == id {
235
+				if event.Action == "create" {
236
+					created = true
237
+				}
238
+				if event.Action == "start" {
239
+					started = true
240
+				}
241
+			}
242
+		case err := <-errs:
243
+			if err == io.EOF {
244
+				t.Fatal("premature end of event stream")
245
+			}
246
+			require.Nil(t, err)
247
+		case <-time.After(30 * time.Second):
248
+			// Fail the test
249
+			t.Fatal("event stream timeout")
250
+		}
251
+	}
252
+
253
+	// Ensure both events and container endpoints are passed to the
254
+	// authorization plugin
255
+	assertURIRecorded(t, ctrl.requestsURIs, "/events")
256
+	assertURIRecorded(t, ctrl.requestsURIs, "/containers/create")
257
+	assertURIRecorded(t, ctrl.requestsURIs, fmt.Sprintf("/containers/%s/start", id))
258
+}
259
+
260
+func TestAuthZPluginErrorResponse(t *testing.T) {
261
+	defer setupTestV1(t)()
262
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
263
+	ctrl.reqRes.Allow = true
264
+	ctrl.resRes.Err = errorMessage
265
+
266
+	client, err := d.NewClient()
267
+	require.Nil(t, err)
268
+
269
+	// Ensure command is blocked
270
+	_, err = system.Version(client)
271
+	require.NotNil(t, err)
272
+	require.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiResponse, errorMessage), err.Error())
273
+}
274
+
275
+func TestAuthZPluginErrorRequest(t *testing.T) {
276
+	defer setupTestV1(t)()
277
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin)
278
+	ctrl.reqRes.Err = errorMessage
279
+
280
+	client, err := d.NewClient()
281
+	require.Nil(t, err)
282
+
283
+	// Ensure command is blocked
284
+	_, err = system.Version(client)
285
+	require.NotNil(t, err)
286
+	require.Equal(t, fmt.Sprintf("Error response from daemon: plugin %s failed with error: %s: %s", testAuthZPlugin, authorization.AuthZApiRequest, errorMessage), err.Error())
287
+}
288
+
289
+func TestAuthZPluginEnsureNoDuplicatePluginRegistration(t *testing.T) {
290
+	defer setupTestV1(t)()
291
+	d.Start(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
292
+
293
+	ctrl.reqRes.Allow = true
294
+	ctrl.resRes.Allow = true
295
+
296
+	client, err := d.NewClient()
297
+	require.Nil(t, err)
298
+
299
+	_, err = system.Version(client)
300
+	require.Nil(t, err)
301
+
302
+	// assert plugin is only called once..
303
+	require.Equal(t, 1, ctrl.versionReqCount)
304
+	require.Equal(t, 1, ctrl.versionResCount)
305
+}
306
+
307
+func TestAuthZPluginEnsureLoadImportWorking(t *testing.T) {
308
+	defer setupTestV1(t)()
309
+	ctrl.reqRes.Allow = true
310
+	ctrl.resRes.Allow = true
311
+	d.StartWithBusybox(t, "--authorization-plugin="+testAuthZPlugin, "--authorization-plugin="+testAuthZPlugin)
312
+
313
+	client, err := d.NewClient()
314
+	require.Nil(t, err)
315
+
316
+	tmp, err := ioutil.TempDir("", "test-authz-load-import")
317
+	require.Nil(t, err)
318
+	defer os.RemoveAll(tmp)
319
+
320
+	savedImagePath := filepath.Join(tmp, "save.tar")
321
+
322
+	err = image.Save(client, savedImagePath, "busybox")
323
+	require.Nil(t, err)
324
+	err = image.Load(client, savedImagePath)
325
+	require.Nil(t, err)
326
+
327
+	exportedImagePath := filepath.Join(tmp, "export.tar")
328
+
329
+	id, err := container.Run(client, "busybox", []string{})
330
+	require.Nil(t, err)
331
+	err = container.Export(client, exportedImagePath, id)
332
+	require.Nil(t, err)
333
+	err = image.Import(client, exportedImagePath)
334
+	require.Nil(t, err)
335
+}
336
+
337
+func TestAuthZPluginHeader(t *testing.T) {
338
+	defer setupTestV1(t)()
339
+	ctrl.reqRes.Allow = true
340
+	ctrl.resRes.Allow = true
341
+	d.StartWithBusybox(t, "--debug", "--authorization-plugin="+testAuthZPlugin)
342
+
343
+	daemonURL, err := url.Parse(d.Sock())
344
+	require.Nil(t, err)
345
+
346
+	conn, err := net.DialTimeout(daemonURL.Scheme, daemonURL.Path, time.Second*10)
347
+	require.Nil(t, err)
348
+	client := httputil.NewClientConn(conn, nil)
349
+	req, err := http.NewRequest("GET", "/version", nil)
350
+	require.Nil(t, err)
351
+	resp, err := client.Do(req)
352
+	require.Nil(t, err)
353
+	require.Equal(t, "application/json", resp.Header["Content-Type"][0])
354
+}
355
+
356
+// assertURIRecorded verifies that the given URI was sent and recorded
357
+// in the authz plugin
358
+func assertURIRecorded(t *testing.T, uris []string, uri string) {
359
+	var found bool
360
+	for _, u := range uris {
361
+		if strings.Contains(u, uri) {
362
+			found = true
363
+			break
364
+		}
365
+	}
366
+	if !found {
367
+		t.Fatalf("Expected to find URI '%s', recorded uris '%s'", uri, strings.Join(uris, ","))
368
+	}
369
+}
0 370
new file mode 100644
... ...
@@ -0,0 +1,153 @@
0
+// +build !windows
1
+
2
+package authz
3
+
4
+import (
5
+	"context"
6
+	"fmt"
7
+	"os"
8
+	"strings"
9
+	"testing"
10
+
11
+	"github.com/docker/docker/integration/internal/api/container"
12
+	"github.com/docker/docker/integration/internal/api/plugin"
13
+	"github.com/docker/docker/integration/internal/api/volume"
14
+	"github.com/docker/docker/integration/util/requirement"
15
+	"github.com/gotestyourself/gotestyourself/skip"
16
+	"github.com/stretchr/testify/require"
17
+)
18
+
19
+var (
20
+	authzPluginName            = "riyaz/authz-no-volume-plugin"
21
+	authzPluginTag             = "latest"
22
+	authzPluginNameWithTag     = authzPluginName + ":" + authzPluginTag
23
+	authzPluginBadManifestName = "riyaz/authz-plugin-bad-manifest"
24
+	nonexistentAuthzPluginName = "riyaz/nonexistent-authz-plugin"
25
+)
26
+
27
+func setupTestV2(t *testing.T) func() {
28
+	skip.IfCondition(t, testEnv.DaemonInfo.OSType != "linux")
29
+	requirement.HasHubConnectivity(t)
30
+
31
+	teardown := setupTest(t)
32
+
33
+	d.Start(t)
34
+
35
+	return teardown
36
+}
37
+
38
+func TestAuthZPluginV2AllowNonVolumeRequest(t *testing.T) {
39
+	skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
40
+	defer setupTestV2(t)()
41
+
42
+	client, err := d.NewClient()
43
+	require.Nil(t, err)
44
+
45
+	// Install authz plugin
46
+	err = plugin.InstallGrantAllPermissions(client, authzPluginNameWithTag)
47
+	require.Nil(t, err)
48
+	// start the daemon with the plugin and load busybox, --net=none build fails otherwise
49
+	// because it needs to pull busybox
50
+	d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
51
+	d.LoadBusybox(t)
52
+
53
+	// Ensure docker run command and accompanying docker ps are successful
54
+	id, err := container.Run(client, "busybox", []string{"top"})
55
+	require.Nil(t, err)
56
+
57
+	_, err = client.ContainerInspect(context.Background(), id)
58
+	require.Nil(t, err)
59
+}
60
+
61
+func TestAuthZPluginV2Disable(t *testing.T) {
62
+	skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
63
+	defer setupTestV2(t)()
64
+
65
+	client, err := d.NewClient()
66
+	require.Nil(t, err)
67
+
68
+	// Install authz plugin
69
+	err = plugin.InstallGrantAllPermissions(client, authzPluginNameWithTag)
70
+	require.Nil(t, err)
71
+
72
+	d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
73
+	d.LoadBusybox(t)
74
+
75
+	_, err = volume.Create(client, "local", map[string]string{})
76
+	require.NotNil(t, err)
77
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
78
+
79
+	// disable the plugin
80
+	err = plugin.Disable(client, authzPluginNameWithTag)
81
+	require.Nil(t, err)
82
+
83
+	// now test to see if the docker api works.
84
+	_, err = volume.Create(client, "local", map[string]string{})
85
+	require.Nil(t, err)
86
+}
87
+
88
+func TestAuthZPluginV2RejectVolumeRequests(t *testing.T) {
89
+	skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
90
+	defer setupTestV2(t)()
91
+
92
+	client, err := d.NewClient()
93
+	require.Nil(t, err)
94
+
95
+	// Install authz plugin
96
+	err = plugin.InstallGrantAllPermissions(client, authzPluginNameWithTag)
97
+	require.Nil(t, err)
98
+
99
+	// restart the daemon with the plugin
100
+	d.Restart(t, "--authorization-plugin="+authzPluginNameWithTag)
101
+
102
+	_, err = volume.Create(client, "local", map[string]string{})
103
+	require.NotNil(t, err)
104
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
105
+
106
+	_, err = volume.Ls(client)
107
+	require.NotNil(t, err)
108
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
109
+
110
+	// The plugin will block the command before it can determine the volume does not exist
111
+	err = volume.Rm(client, "test")
112
+	require.NotNil(t, err)
113
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
114
+
115
+	_, err = volume.Inspect(client, "test")
116
+	require.NotNil(t, err)
117
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
118
+
119
+	_, err = volume.Prune(client)
120
+	require.NotNil(t, err)
121
+	require.True(t, strings.Contains(err.Error(), fmt.Sprintf("Error response from daemon: plugin %s failed with error:", authzPluginNameWithTag)))
122
+}
123
+
124
+func TestAuthZPluginV2BadManifestFailsDaemonStart(t *testing.T) {
125
+	skip.IfCondition(t, os.Getenv("DOCKER_ENGINE_GOARCH") != "amd64")
126
+	defer setupTestV2(t)()
127
+
128
+	client, err := d.NewClient()
129
+	require.Nil(t, err)
130
+
131
+	// Install authz plugin with bad manifest
132
+	err = plugin.InstallGrantAllPermissions(client, authzPluginBadManifestName)
133
+	require.Nil(t, err)
134
+
135
+	// start the daemon with the plugin, it will error
136
+	err = d.RestartWithError("--authorization-plugin=" + authzPluginBadManifestName)
137
+	require.NotNil(t, err)
138
+
139
+	// restarting the daemon without requiring the plugin will succeed
140
+	d.Start(t)
141
+}
142
+
143
+func TestAuthZPluginV2NonexistentFailsDaemonStart(t *testing.T) {
144
+	defer setupTestV2(t)()
145
+
146
+	// start the daemon with a non-existent authz plugin, it will error
147
+	err := d.RestartWithError("--authorization-plugin=" + nonexistentAuthzPluginName)
148
+	require.NotNil(t, err)
149
+
150
+	// restarting the daemon without requiring the plugin will succeed
151
+	d.Start(t)
152
+}
0 153
new file mode 100644
... ...
@@ -0,0 +1,177 @@
0
+// +build !windows
1
+
2
+package authz
3
+
4
+import (
5
+	"encoding/json"
6
+	"fmt"
7
+	"io/ioutil"
8
+	"net/http"
9
+	"net/http/httptest"
10
+	"os"
11
+	"strings"
12
+	"testing"
13
+
14
+	"github.com/docker/docker/integration-cli/daemon"
15
+	"github.com/docker/docker/internal/test/environment"
16
+	"github.com/docker/docker/pkg/authorization"
17
+	"github.com/docker/docker/pkg/plugins"
18
+)
19
+
20
+var (
21
+	testEnv *environment.Execution
22
+	d       *daemon.Daemon
23
+	server  *httptest.Server
24
+)
25
+
26
+const dockerdBinary = "dockerd"
27
+
28
+func TestMain(m *testing.M) {
29
+	var err error
30
+	testEnv, err = environment.New()
31
+	if err != nil {
32
+		fmt.Println(err)
33
+		os.Exit(1)
34
+	}
35
+
36
+	testEnv.Print()
37
+	setupSuite()
38
+	exitCode := m.Run()
39
+	teardownSuite()
40
+
41
+	os.Exit(exitCode)
42
+}
43
+
44
+func setupTest(t *testing.T) func() {
45
+	environment.ProtectAll(t, testEnv)
46
+
47
+	d = daemon.New(t, "", dockerdBinary, daemon.Config{
48
+		Experimental: testEnv.DaemonInfo.ExperimentalBuild,
49
+	})
50
+
51
+	return func() {
52
+		if d != nil {
53
+			d.Stop(t)
54
+		}
55
+		testEnv.Clean(t)
56
+	}
57
+}
58
+
59
+func setupSuite() {
60
+	mux := http.NewServeMux()
61
+	server = httptest.NewServer(mux)
62
+
63
+	mux.HandleFunc("/Plugin.Activate", func(w http.ResponseWriter, r *http.Request) {
64
+		b, err := json.Marshal(plugins.Manifest{Implements: []string{authorization.AuthZApiImplements}})
65
+		if err != nil {
66
+			panic("could not marshal json for /Plugin.Activate: " + err.Error())
67
+		}
68
+		w.Write(b)
69
+	})
70
+
71
+	mux.HandleFunc("/AuthZPlugin.AuthZReq", func(w http.ResponseWriter, r *http.Request) {
72
+		defer r.Body.Close()
73
+		body, err := ioutil.ReadAll(r.Body)
74
+		if err != nil {
75
+			panic("could not read body for /AuthZPlugin.AuthZReq: " + err.Error())
76
+		}
77
+		authReq := authorization.Request{}
78
+		err = json.Unmarshal(body, &authReq)
79
+		if err != nil {
80
+			panic("could not unmarshal json for /AuthZPlugin.AuthZReq: " + err.Error())
81
+		}
82
+
83
+		assertBody(authReq.RequestURI, authReq.RequestHeaders, authReq.RequestBody)
84
+		assertAuthHeaders(authReq.RequestHeaders)
85
+
86
+		// Count only server version api
87
+		if strings.HasSuffix(authReq.RequestURI, serverVersionAPI) {
88
+			ctrl.versionReqCount++
89
+		}
90
+
91
+		ctrl.requestsURIs = append(ctrl.requestsURIs, authReq.RequestURI)
92
+
93
+		reqRes := ctrl.reqRes
94
+		if isAllowed(authReq.RequestURI) {
95
+			reqRes = authorization.Response{Allow: true}
96
+		}
97
+		if reqRes.Err != "" {
98
+			w.WriteHeader(http.StatusInternalServerError)
99
+		}
100
+		b, err := json.Marshal(reqRes)
101
+		if err != nil {
102
+			panic("could not marshal json for /AuthZPlugin.AuthZReq: " + err.Error())
103
+		}
104
+
105
+		ctrl.reqUser = authReq.User
106
+		w.Write(b)
107
+	})
108
+
109
+	mux.HandleFunc("/AuthZPlugin.AuthZRes", func(w http.ResponseWriter, r *http.Request) {
110
+		defer r.Body.Close()
111
+		body, err := ioutil.ReadAll(r.Body)
112
+		if err != nil {
113
+			panic("could not read body for /AuthZPlugin.AuthZRes: " + err.Error())
114
+		}
115
+		authReq := authorization.Request{}
116
+		err = json.Unmarshal(body, &authReq)
117
+		if err != nil {
118
+			panic("could not unmarshal json for /AuthZPlugin.AuthZRes: " + err.Error())
119
+		}
120
+
121
+		assertBody(authReq.RequestURI, authReq.ResponseHeaders, authReq.ResponseBody)
122
+		assertAuthHeaders(authReq.ResponseHeaders)
123
+
124
+		// Count only server version api
125
+		if strings.HasSuffix(authReq.RequestURI, serverVersionAPI) {
126
+			ctrl.versionResCount++
127
+		}
128
+		resRes := ctrl.resRes
129
+		if isAllowed(authReq.RequestURI) {
130
+			resRes = authorization.Response{Allow: true}
131
+		}
132
+		if resRes.Err != "" {
133
+			w.WriteHeader(http.StatusInternalServerError)
134
+		}
135
+		b, err := json.Marshal(resRes)
136
+		if err != nil {
137
+			panic("could not marshal json for /AuthZPlugin.AuthZRes: " + err.Error())
138
+		}
139
+		ctrl.resUser = authReq.User
140
+		w.Write(b)
141
+	})
142
+}
143
+
144
+func teardownSuite() {
145
+	if server == nil {
146
+		return
147
+	}
148
+
149
+	server.Close()
150
+}
151
+
152
+// assertAuthHeaders validates authentication headers are removed
153
+func assertAuthHeaders(headers map[string]string) error {
154
+	for k := range headers {
155
+		if strings.Contains(strings.ToLower(k), "auth") || strings.Contains(strings.ToLower(k), "x-registry") {
156
+			panic(fmt.Sprintf("Found authentication headers in request '%v'", headers))
157
+		}
158
+	}
159
+	return nil
160
+}
161
+
162
+// assertBody asserts that body is removed for non text/json requests
163
+func assertBody(requestURI string, headers map[string]string, body []byte) {
164
+	if strings.Contains(strings.ToLower(requestURI), "auth") && len(body) > 0 {
165
+		panic("Body included for authentication endpoint " + string(body))
166
+	}
167
+
168
+	for k, v := range headers {
169
+		if strings.EqualFold(k, "Content-Type") && strings.HasPrefix(v, "text/") || v == "application/json" {
170
+			return
171
+		}
172
+	}
173
+	if len(body) > 0 {
174
+		panic(fmt.Sprintf("Body included while it should not (Headers: '%v')", headers))
175
+	}
176
+}
0 177
new file mode 100644
... ...
@@ -0,0 +1,23 @@
0
+-----BEGIN CERTIFICATE-----
1
+MIID0TCCAzqgAwIBAgIJAP2r7GqEJwSnMA0GCSqGSIb3DQEBBQUAMIGiMQswCQYD
2
+VQQGEwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMG
3
+A1UEChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTERMA8GA1UEAxMI
4
+Y2hhbmdlbWUxETAPBgNVBCkTCGNoYW5nZW1lMR8wHQYJKoZIhvcNAQkBFhBtYWls
5
+QGhvc3QuZG9tYWluMB4XDTEzMTIwMzE2NTYzMFoXDTIzMTIwMTE2NTYzMFowgaIx
6
+CzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2Nv
7
+MRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYD
8
+VQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEW
9
+EG1haWxAaG9zdC5kb21haW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALAn
10
+0xDw+5y7ZptQacq66pUhRu82JP2WU6IDgo5QUtNU6/CX5PwQATe/OnYTZQFbksxp
11
+AU9boG0FCkgxfsgPYXEuZxVEGKI2fxfKHOZZI8mrkWmj6eWU/0cvCjGVc9rTITP5
12
+sNQvg+hORyVDdNp2IdsbMJayiB3AQYMFx3vSDOMTAgMBAAGjggELMIIBBzAdBgNV
13
+HQ4EFgQUZu7DFz09q0QBa2+ymRm9qgK1NPswgdcGA1UdIwSBzzCBzIAUZu7DFz09
14
+q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
15
+QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
16
+ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
17
+Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
18
+hCcEpzAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAF8fJKKM+/oOdnNi
19
+zEd0M1+PmZOyqvjYQn/2ZR8UHH6Imgc/OPQKZXf0bVE1Txc/DaUNn9Isd1SuCuaE
20
+ic3vAIYYU7PmgeNN6vwec48V96T7jr+GAi6AVMhQEc2hHCfVtx11Xx+x6aHDZzJt
21
+Zxtf5lL6KSO9Y+EFwM+rju6hm5hW
22
+-----END CERTIFICATE-----
0 23
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+Certificate:
1
+    Data:
2
+        Version: 3 (0x2)
3
+        Serial Number: 3 (0x3)
4
+    Signature Algorithm: sha1WithRSAEncryption
5
+        Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
6
+        Validity
7
+            Not Before: Dec  4 14:17:54 2013 GMT
8
+            Not After : Dec  2 14:17:54 2023 GMT
9
+        Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=client/name=changeme/emailAddress=mail@host.domain
10
+        Subject Public Key Info:
11
+            Public Key Algorithm: rsaEncryption
12
+                Public-Key: (1024 bit)
13
+                Modulus:
14
+                    00:ca:c9:05:d0:09:4e:3e:a4:fc:d5:14:f4:a5:e8:
15
+                    34:d3:6b:51:e3:f3:62:ea:a1:f0:e8:ed:c4:2a:bc:
16
+                    f0:4f:ca:07:df:e3:88:fa:f4:21:99:35:0e:3d:ea:
17
+                    b0:86:e7:c4:d2:8a:83:2b:42:b8:ec:a3:99:62:70:
18
+                    81:46:cc:fc:a5:1d:d2:63:e8:eb:07:25:9a:e2:25:
19
+                    6d:11:56:f2:1a:51:a1:b6:3e:1c:57:32:e9:7b:2c:
20
+                    aa:1b:cc:97:2d:89:2d:b1:c9:5e:35:28:4d:7c:fa:
21
+                    65:31:3e:f7:70:dd:6e:0b:3c:58:af:a8:2e:24:c0:
22
+                    7e:4e:78:7d:0a:9e:8f:42:43
23
+                Exponent: 65537 (0x10001)
24
+        X509v3 extensions:
25
+            X509v3 Basic Constraints: 
26
+                CA:FALSE
27
+            Netscape Comment: 
28
+                Easy-RSA Generated Certificate
29
+            X509v3 Subject Key Identifier: 
30
+                DE:42:EF:2D:98:A3:6C:A8:AA:E0:8C:71:2C:9D:64:23:A9:E2:7E:81
31
+            X509v3 Authority Key Identifier: 
32
+                keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
33
+                DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
34
+                serial:FD:AB:EC:6A:84:27:04:A7
35
+
36
+            X509v3 Extended Key Usage: 
37
+                TLS Web Client Authentication
38
+            X509v3 Key Usage: 
39
+                Digital Signature
40
+    Signature Algorithm: sha1WithRSAEncryption
41
+         1c:44:26:ea:e1:66:25:cb:e4:8e:57:1c:f6:b9:17:22:62:40:
42
+         12:90:8f:3b:b2:61:7a:54:94:8f:b1:20:0b:bf:a3:51:e3:fa:
43
+         1c:a1:be:92:3a:d0:76:44:c0:57:83:ab:6a:e4:1a:45:49:a4:
44
+         af:39:0d:60:32:fc:3a:be:d7:fb:5d:99:7a:1f:87:e7:d5:ab:
45
+         84:a2:5e:90:d8:bf:fa:89:6d:32:26:02:5e:31:35:68:7f:31:
46
+         f5:6b:51:46:bc:af:70:ed:5a:09:7d:ec:b2:48:4f:fe:c5:2f:
47
+         56:04:ad:f6:c1:d2:2a:e4:6a:c4:87:fe:08:35:c5:38:cb:5e:
48
+         4a:c4
49
+-----BEGIN CERTIFICATE-----
50
+MIIEFTCCA36gAwIBAgIBAzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
51
+CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
52
+cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
53
+MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
54
+bWFpbjAeFw0xMzEyMDQxNDE3NTRaFw0yMzEyMDIxNDE3NTRaMIGgMQswCQYDVQQG
55
+EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
56
+ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEPMA0GA1UEAxMGY2xp
57
+ZW50MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0
58
+LmRvbWFpbjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAyskF0AlOPqT81RT0
59
+peg002tR4/Ni6qHw6O3EKrzwT8oH3+OI+vQhmTUOPeqwhufE0oqDK0K47KOZYnCB
60
+Rsz8pR3SY+jrByWa4iVtEVbyGlGhtj4cVzLpeyyqG8yXLYktscleNShNfPplMT73
61
+cN1uCzxYr6guJMB+Tnh9Cp6PQkMCAwEAAaOCAVkwggFVMAkGA1UdEwQCMAAwLQYJ
62
+YIZIAYb4QgENBCAWHkVhc3ktUlNBIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAdBgNV
63
+HQ4EFgQU3kLvLZijbKiq4IxxLJ1kI6nifoEwgdcGA1UdIwSBzzCBzIAUZu7DFz09
64
+q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJD
65
+QTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUwEwYDVQQKEwxGb3J0LUZ1bnN0b24x
66
+ETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQDEwhjaGFuZ2VtZTERMA8GA1UEKRMI
67
+Y2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21haW6CCQD9q+xq
68
+hCcEpzATBgNVHSUEDDAKBggrBgEFBQcDAjALBgNVHQ8EBAMCB4AwDQYJKoZIhvcN
69
+AQEFBQADgYEAHEQm6uFmJcvkjlcc9rkXImJAEpCPO7JhelSUj7EgC7+jUeP6HKG+
70
+kjrQdkTAV4OrauQaRUmkrzkNYDL8Or7X+12Zeh+H59WrhKJekNi/+oltMiYCXjE1
71
+aH8x9WtRRryvcO1aCX3sskhP/sUvVgSt9sHSKuRqxIf+CDXFOMteSsQ=
72
+-----END CERTIFICATE-----
0 73
new file mode 100644
... ...
@@ -0,0 +1,16 @@
0
+-----BEGIN PRIVATE KEY-----
1
+MIICdQIBADANBgkqhkiG9w0BAQEFAASCAl8wggJbAgEAAoGBAMrJBdAJTj6k/NUU
2
+9KXoNNNrUePzYuqh8OjtxCq88E/KB9/jiPr0IZk1Dj3qsIbnxNKKgytCuOyjmWJw
3
+gUbM/KUd0mPo6wclmuIlbRFW8hpRobY+HFcy6XssqhvMly2JLbHJXjUoTXz6ZTE+
4
+93Ddbgs8WK+oLiTAfk54fQqej0JDAgMBAAECgYBOFEzKp2qbMEexe9ofL2N3rDDh
5
+xkrl8OijpzkLA6i78BxMFn4dsnZlWUpciMrjhsYAExkiRRSS+QMMJimAq1jzQqc3
6
+FAQV2XGYwkd0cUn7iZGvfNnEPysjsfyYQM+m+sT0ATj4BZjVShC6kkSjTdm1leLN
7
+OSvcHdcu3Xxg9ufF0QJBAPYdnNt5sIndt2WECePuRVi+uF4mlxTobFY0fjn26yhC
8
+4RsnhhD3Vldygo9gvnkwrAZYaALGSPBewes2InxvjA8CQQDS7erKiNXpwoqz5XiU
9
+SVEsIIVTdWzBjGbIqMOu/hUwM5FK4j6JTBks0aTGMyh0YV9L1EzM0X79J29JahCe
10
+iQKNAkBKNMOGqTpBV0hko1sYDk96YobUXG5RL4L6uvkUIQ7mJMQam+AgXXL7Ctuy
11
+v0iu4a38e8tgisiTMP7nHHtpaXihAkAOiN54/lzfMsykANgCP9scE1GcoqbP34Dl
12
+qttxH4kOPT9xzY1JoLjLYdbc4YGUI3GRpBt2sajygNkmUey7P+2xAkBBsVCZFvTw
13
+qHvOpPS2kX5ml5xoc/QAHK9N7kR+X7XFYx82RTVSqJEK4lPb+aEWn+CjiIewO4Q5
14
+ksDFuNxAzbhl
15
+-----END PRIVATE KEY-----
0 16
new file mode 100644
... ...
@@ -0,0 +1,76 @@
0
+Certificate:
1
+    Data:
2
+        Version: 3 (0x2)
3
+        Serial Number: 4 (0x4)
4
+    Signature Algorithm: sha1WithRSAEncryption
5
+        Issuer: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=changeme/name=changeme/emailAddress=mail@host.domain
6
+        Validity
7
+            Not Before: Dec  4 15:01:20 2013 GMT
8
+            Not After : Dec  2 15:01:20 2023 GMT
9
+        Subject: C=US, ST=CA, L=SanFrancisco, O=Fort-Funston, OU=changeme, CN=*/name=changeme/emailAddress=mail@host.domain
10
+        Subject Public Key Info:
11
+            Public Key Algorithm: rsaEncryption
12
+                Public-Key: (1024 bit)
13
+                Modulus:
14
+                    00:c1:ff:7d:30:6f:64:4a:b1:92:b1:71:d1:c1:74:
15
+                    e2:1d:db:2d:11:24:e1:00:d4:00:ae:6f:c8:9e:ae:
16
+                    67:b3:4a:bd:f7:e6:9e:57:6d:19:4c:3c:23:94:2d:
17
+                    3d:d6:63:84:d8:fa:76:2b:38:12:c1:ed:20:9d:32:
18
+                    e0:e8:c2:bf:9a:77:70:04:3f:7f:ca:8c:2c:82:d6:
19
+                    3d:25:5c:02:1a:4f:64:93:03:dd:9c:42:97:5e:09:
20
+                    49:af:f0:c2:e1:30:08:0e:21:46:95:d1:13:59:c0:
21
+                    c8:76:be:94:0d:8b:43:67:21:33:b2:08:60:9d:76:
22
+                    a8:05:32:1e:f9:95:09:14:75
23
+                Exponent: 65537 (0x10001)
24
+        X509v3 extensions:
25
+            X509v3 Basic Constraints: 
26
+                CA:FALSE
27
+            Netscape Cert Type: 
28
+                SSL Server
29
+            Netscape Comment: 
30
+                Easy-RSA Generated Server Certificate
31
+            X509v3 Subject Key Identifier: 
32
+                14:02:FD:FD:DD:13:38:E0:71:EA:D1:BE:C0:0E:89:1A:2D:B6:19:06
33
+            X509v3 Authority Key Identifier: 
34
+                keyid:66:EE:C3:17:3D:3D:AB:44:01:6B:6F:B2:99:19:BD:AA:02:B5:34:FB
35
+                DirName:/C=US/ST=CA/L=SanFrancisco/O=Fort-Funston/OU=changeme/CN=changeme/name=changeme/emailAddress=mail@host.domain
36
+                serial:FD:AB:EC:6A:84:27:04:A7
37
+
38
+            X509v3 Extended Key Usage: 
39
+                TLS Web Server Authentication
40
+            X509v3 Key Usage: 
41
+                Digital Signature, Key Encipherment
42
+    Signature Algorithm: sha1WithRSAEncryption
43
+         40:0f:10:39:c4:b7:0f:0d:2f:bf:d2:16:cc:8e:d3:9a:fb:8b:
44
+         ce:4b:7b:0d:48:77:ce:f1:fe:d5:8f:ea:b1:71:ed:49:1d:9f:
45
+         23:3a:16:d4:70:7c:c5:29:bf:e4:90:34:d0:f0:00:24:f4:e4:
46
+         df:2c:c3:83:01:66:61:c9:a8:ab:29:e7:98:6d:27:89:4a:76:
47
+         c9:2e:19:8e:fe:6e:d5:f8:99:11:0e:97:67:4b:34:e3:1e:e3:
48
+         9f:35:00:a5:32:f9:b5:2c:f2:e0:c5:2e:cc:81:bd:18:dd:5c:
49
+         12:c8:6b:fa:0c:17:74:30:55:f6:6e:20:9a:6c:1e:09:b4:0c:
50
+         15:42
51
+-----BEGIN CERTIFICATE-----
52
+MIIEKjCCA5OgAwIBAgIBBDANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMCVVMx
53
+CzAJBgNVBAgTAkNBMRUwEwYDVQQHEwxTYW5GcmFuY2lzY28xFTATBgNVBAoTDEZv
54
+cnQtRnVuc3RvbjERMA8GA1UECxMIY2hhbmdlbWUxETAPBgNVBAMTCGNoYW5nZW1l
55
+MREwDwYDVQQpEwhjaGFuZ2VtZTEfMB0GCSqGSIb3DQEJARYQbWFpbEBob3N0LmRv
56
+bWFpbjAeFw0xMzEyMDQxNTAxMjBaFw0yMzEyMDIxNTAxMjBaMIGbMQswCQYDVQQG
57
+EwJVUzELMAkGA1UECBMCQ0ExFTATBgNVBAcTDFNhbkZyYW5jaXNjbzEVMBMGA1UE
58
+ChMMRm9ydC1GdW5zdG9uMREwDwYDVQQLEwhjaGFuZ2VtZTEKMAgGA1UEAxQBKjER
59
+MA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1haWxAaG9zdC5kb21h
60
+aW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMH/fTBvZEqxkrFx0cF04h3b
61
+LREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y4OjCv5p3
62
+cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+lA2LQ2ch
63
+M7IIYJ12qAUyHvmVCRR1AgMBAAGjggFzMIIBbzAJBgNVHRMEAjAAMBEGCWCGSAGG
64
++EIBAQQEAwIGQDA0BglghkgBhvhCAQ0EJxYlRWFzeS1SU0EgR2VuZXJhdGVkIFNl
65
+cnZlciBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUFAL9/d0TOOBx6tG+wA6JGi22GQYw
66
+gdcGA1UdIwSBzzCBzIAUZu7DFz09q0QBa2+ymRm9qgK1NPuhgaikgaUwgaIxCzAJ
67
+BgNVBAYTAlVTMQswCQYDVQQIEwJDQTEVMBMGA1UEBxMMU2FuRnJhbmNpc2NvMRUw
68
+EwYDVQQKEwxGb3J0LUZ1bnN0b24xETAPBgNVBAsTCGNoYW5nZW1lMREwDwYDVQQD
69
+EwhjaGFuZ2VtZTERMA8GA1UEKRMIY2hhbmdlbWUxHzAdBgkqhkiG9w0BCQEWEG1h
70
+aWxAaG9zdC5kb21haW6CCQD9q+xqhCcEpzATBgNVHSUEDDAKBggrBgEFBQcDATAL
71
+BgNVHQ8EBAMCBaAwDQYJKoZIhvcNAQEFBQADgYEAQA8QOcS3Dw0vv9IWzI7TmvuL
72
+zkt7DUh3zvH+1Y/qsXHtSR2fIzoW1HB8xSm/5JA00PAAJPTk3yzDgwFmYcmoqynn
73
+mG0niUp2yS4Zjv5u1fiZEQ6XZ0s04x7jnzUApTL5tSzy4MUuzIG9GN1cEshr+gwX
74
+dDBV9m4gmmweCbQMFUI=
75
+-----END CERTIFICATE-----
0 76
new file mode 100644
... ...
@@ -0,0 +1,16 @@
0
+-----BEGIN PRIVATE KEY-----
1
+MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMH/fTBvZEqxkrFx
2
+0cF04h3bLREk4QDUAK5vyJ6uZ7NKvffmnldtGUw8I5QtPdZjhNj6dis4EsHtIJ0y
3
+4OjCv5p3cAQ/f8qMLILWPSVcAhpPZJMD3ZxCl14JSa/wwuEwCA4hRpXRE1nAyHa+
4
+lA2LQ2chM7IIYJ12qAUyHvmVCRR1AgMBAAECgYAmwckb9RUfSwyYgLm8IYLPHiuJ
5
+wkllZfVg5Bo7gXJcQnFjZmJ56uTj8xvUjZlODIHM63TSO5ibv6kFXtXKCqZGd2M+
6
+wGbhZ0f+2GvKcwMmJERnIQjuoNaYSQLT0tM0VB9Iz0rJlZC+tzPZ+5pPqEumRdsS
7
+IzWNXfF42AhcbwAQYQJBAPVXtMYIJc9EZsz86ZcQiMPWUpCX5vnRmtwL8kKyR8D5
8
+4KfYeiowyFffSRMMcclwNHq7TgSXN+nIXM9WyzyzwikCQQDKbNA28AgZp9aT54HP
9
+WnbeE2pmt+uk/zl/BtxJSoK6H+69Jec+lf7EgL7HgOWYRSNot4uQWu8IhsHLTiUq
10
++0FtAkEAqwlRxRy4/x24bP+D+QRV0/D97j93joFJbE4Hved7jlSlAV4xDGilwlyv
11
+HNB4Iu5OJ6Gcaibhm+FKkmD3noHSwQJBAIpu3fokLzX0bS+bDFBU6qO3HXX/47xj
12
++tsfQvkwZrSI8AkU6c8IX0HdVhsz0FBRQAT2ORDQz1XCarfxykNZrwUCQQCGCBIc
13
+BBCWzhHlswlGidWJg3HqqO6hPPClEr3B5G87oCsdeYwiO23XT6rUnoJXfJHp6oCW
14
+5nCwDu5ZTP+khltg
15
+-----END PRIVATE KEY-----
... ...
@@ -1,9 +1,15 @@
1 1
 package request
2 2
 
3 3
 import (
4
+	"net"
5
+	"net/http"
4 6
 	"testing"
7
+	"time"
5 8
 
9
+	"github.com/docker/docker/api"
6 10
 	"github.com/docker/docker/client"
11
+	"github.com/docker/go-connections/sockets"
12
+	"github.com/docker/go-connections/tlsconfig"
7 13
 	"github.com/stretchr/testify/require"
8 14
 )
9 15
 
... ...
@@ -13,3 +19,35 @@ func NewAPIClient(t *testing.T) client.APIClient {
13 13
 	require.NoError(t, err)
14 14
 	return clt
15 15
 }
16
+
17
+// NewTLSAPIClient returns a docker API client configured with the
18
+// provided TLS settings
19
+func NewTLSAPIClient(t *testing.T, host, cacertPath, certPath, keyPath string) (client.APIClient, error) {
20
+	opts := tlsconfig.Options{
21
+		CAFile:             cacertPath,
22
+		CertFile:           certPath,
23
+		KeyFile:            keyPath,
24
+		ExclusiveRootPools: true,
25
+	}
26
+	config, err := tlsconfig.Client(opts)
27
+	require.Nil(t, err)
28
+	tr := &http.Transport{
29
+		TLSClientConfig: config,
30
+		DialContext: (&net.Dialer{
31
+			KeepAlive: 30 * time.Second,
32
+			Timeout:   30 * time.Second,
33
+		}).DialContext,
34
+	}
35
+	proto, addr, _, err := client.ParseHost(host)
36
+	require.Nil(t, err)
37
+
38
+	sockets.ConfigureTransport(tr, proto, addr)
39
+
40
+	httpClient := &http.Client{
41
+		Transport:     tr,
42
+		CheckRedirect: client.CheckRedirect,
43
+	}
44
+	verStr := api.DefaultVersion
45
+	customHeaders := map[string]string{}
46
+	return client.NewClient(host, verStr, httpClient, customHeaders)
47
+}
16 48
new file mode 100644
... ...
@@ -0,0 +1,31 @@
0
+package requirement
1
+
2
+import (
3
+	"net/http"
4
+	"strings"
5
+	"testing"
6
+	"time"
7
+
8
+	"github.com/gotestyourself/gotestyourself/skip"
9
+)
10
+
11
+// HasHubConnectivity checks to see if https://hub.docker.com is
12
+// accessible from the present environment
13
+func HasHubConnectivity(t *testing.T) {
14
+	// Set a timeout on the GET at 15s
15
+	var timeout = 15 * time.Second
16
+	var url = "https://hub.docker.com"
17
+
18
+	client := http.Client{
19
+		Timeout: timeout,
20
+	}
21
+
22
+	resp, err := client.Get(url)
23
+	if err != nil && strings.Contains(err.Error(), "use of closed network connection") {
24
+		t.Fatalf("Timeout for GET request on %s", url)
25
+	}
26
+	if resp != nil {
27
+		resp.Body.Close()
28
+	}
29
+	skip.IfCondition(t, err != nil)
30
+}