Browse code

Support for docker content trust for plugins

Add integration test for docker content trust

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)

Derek McGowan authored on 2016/12/28 05:51:00
Showing 8 changed files
... ...
@@ -170,7 +170,7 @@ func createContainer(ctx context.Context, dockerCli *command.DockerCli, config *
170 170
 
171 171
 		if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() {
172 172
 			var err error
173
-			trustedRef, err = image.TrustedReference(ctx, dockerCli, ref)
173
+			trustedRef, err = image.TrustedReference(ctx, dockerCli, ref, nil)
174 174
 			if err != nil {
175 175
 				return nil, err
176 176
 			}
... ...
@@ -235,7 +235,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
235 235
 	var resolvedTags []*resolvedTag
236 236
 	if command.IsTrusted() {
237 237
 		translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
238
-			return TrustedReference(ctx, dockerCli, ref)
238
+			return TrustedReference(ctx, dockerCli, ref, nil)
239 239
 		}
240 240
 		// Wrap the tar archive to replace the Dockerfile entry with the rewritten
241 241
 		// Dockerfile which uses trusted pulls.
... ...
@@ -39,6 +39,11 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
39 39
 
40 40
 	defer responseBody.Close()
41 41
 
42
+	return PushTrustedReference(cli, repoInfo, ref, authConfig, responseBody)
43
+}
44
+
45
+// PushTrustedReference pushes a canonical reference to the trust server.
46
+func PushTrustedReference(cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, in io.Reader) error {
42 47
 	// If it is a trusted push we would like to find the target entry which match the
43 48
 	// tag provided in the function and then do an AddTarget later.
44 49
 	target := &client.Target{}
... ...
@@ -75,14 +80,14 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
75 75
 	default:
76 76
 		// We want trust signatures to always take an explicit tag,
77 77
 		// otherwise it will act as an untrusted push.
78
-		if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil); err != nil {
78
+		if err := jsonmessage.DisplayJSONMessagesToStream(in, cli.Out(), nil); err != nil {
79 79
 			return err
80 80
 		}
81 81
 		fmt.Fprintln(cli.Out(), "No tag specified, skipping trust metadata push")
82 82
 		return nil
83 83
 	}
84 84
 
85
-	if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), handleTarget); err != nil {
85
+	if err := jsonmessage.DisplayJSONMessagesToStream(in, cli.Out(), handleTarget); err != nil {
86 86
 		return err
87 87
 	}
88 88
 
... ...
@@ -315,8 +320,16 @@ func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
315 315
 }
316 316
 
317 317
 // TrustedReference returns the canonical trusted reference for an image reference
318
-func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (reference.Canonical, error) {
319
-	repoInfo, err := registry.ParseRepositoryInfo(ref)
318
+func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) {
319
+	var (
320
+		repoInfo *registry.RepositoryInfo
321
+		err      error
322
+	)
323
+	if rs != nil {
324
+		repoInfo, err = rs.ResolveRepository(ref)
325
+	} else {
326
+		repoInfo, err = registry.ParseRepositoryInfo(ref)
327
+	}
320 328
 	if err != nil {
321 329
 		return nil, err
322 330
 	}
... ...
@@ -332,7 +345,7 @@ func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference
332 332
 
333 333
 	t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
334 334
 	if err != nil {
335
-		return nil, err
335
+		return nil, trust.NotaryError(repoInfo.FullName(), err)
336 336
 	}
337 337
 	// Only list tags in the top level targets role or the releases delegation role - ignore
338 338
 	// all other delegation roles
... ...
@@ -11,6 +11,7 @@ import (
11 11
 	registrytypes "github.com/docker/docker/api/types/registry"
12 12
 	"github.com/docker/docker/cli"
13 13
 	"github.com/docker/docker/cli/command"
14
+	"github.com/docker/docker/cli/command/image"
14 15
 	"github.com/docker/docker/pkg/jsonmessage"
15 16
 	"github.com/docker/docker/reference"
16 17
 	"github.com/docker/docker/registry"
... ...
@@ -46,6 +47,8 @@ func newInstallCommand(dockerCli *command.DockerCli) *cobra.Command {
46 46
 	flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
47 47
 	flags.StringVar(&options.alias, "alias", "", "Local name for plugin")
48 48
 
49
+	command.AddTrustedFlags(flags, true)
50
+
49 51
 	return cmd
50 52
 }
51 53
 
... ...
@@ -63,6 +66,24 @@ func getRepoIndexFromUnnormalizedRef(ref distreference.Named) (*registrytypes.In
63 63
 	return repoInfo.Index, nil
64 64
 }
65 65
 
66
+type pluginRegistryService struct {
67
+	registry.Service
68
+}
69
+
70
+func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo *registry.RepositoryInfo, err error) {
71
+	repoInfo, err = s.Service.ResolveRepository(name)
72
+	if repoInfo != nil {
73
+		repoInfo.Class = "plugin"
74
+	}
75
+	return
76
+}
77
+
78
+func newRegistryService() registry.Service {
79
+	return pluginRegistryService{
80
+		Service: registry.NewService(registry.ServiceOptions{V2Only: true}),
81
+	}
82
+}
83
+
66 84
 func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
67 85
 	// Parse name using distribution reference package to support name
68 86
 	// containing both tag and digest. Names with both tag and digest
... ...
@@ -85,13 +106,41 @@ func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
85 85
 		}
86 86
 		alias = aref.String()
87 87
 	}
88
+	ctx := context.Background()
88 89
 
89 90
 	index, err := getRepoIndexFromUnnormalizedRef(ref)
90 91
 	if err != nil {
91 92
 		return err
92 93
 	}
93 94
 
94
-	ctx := context.Background()
95
+	remote := ref.String()
96
+
97
+	_, isCanonical := ref.(distreference.Canonical)
98
+	if command.IsTrusted() && !isCanonical {
99
+		if alias == "" {
100
+			alias = ref.String()
101
+		}
102
+		var nt reference.NamedTagged
103
+		named, err := reference.ParseNamed(ref.Name())
104
+		if err != nil {
105
+			return err
106
+		}
107
+		if tagged, ok := ref.(distreference.Tagged); ok {
108
+			nt, err = reference.WithTag(named, tagged.Tag())
109
+			if err != nil {
110
+				return err
111
+			}
112
+		} else {
113
+			named = reference.WithDefaultTag(named)
114
+			nt = named.(reference.NamedTagged)
115
+		}
116
+
117
+		trusted, err := image.TrustedReference(ctx, dockerCli, nt, newRegistryService())
118
+		if err != nil {
119
+			return err
120
+		}
121
+		remote = trusted.String()
122
+	}
95 123
 
96 124
 	authConfig := command.ResolveAuthConfig(ctx, dockerCli, index)
97 125
 
... ...
@@ -104,7 +153,7 @@ func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
104 104
 
105 105
 	options := types.PluginInstallOptions{
106 106
 		RegistryAuth:          encodedAuth,
107
-		RemoteRef:             ref.String(),
107
+		RemoteRef:             remote,
108 108
 		Disabled:              opts.disable,
109 109
 		AcceptAllPermissions:  opts.grantPerms,
110 110
 		AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name),
... ...
@@ -7,6 +7,7 @@ import (
7 7
 
8 8
 	"github.com/docker/docker/cli"
9 9
 	"github.com/docker/docker/cli/command"
10
+	"github.com/docker/docker/cli/command/image"
10 11
 	"github.com/docker/docker/pkg/jsonmessage"
11 12
 	"github.com/docker/docker/reference"
12 13
 	"github.com/docker/docker/registry"
... ...
@@ -22,6 +23,11 @@ func newPushCommand(dockerCli *command.DockerCli) *cobra.Command {
22 22
 			return runPush(dockerCli, args[0])
23 23
 		},
24 24
 	}
25
+
26
+	flags := cmd.Flags()
27
+
28
+	command.AddTrustedFlags(flags, true)
29
+
25 30
 	return cmd
26 31
 }
27 32
 
... ...
@@ -55,5 +61,11 @@ func runPush(dockerCli *command.DockerCli, name string) error {
55 55
 		return err
56 56
 	}
57 57
 	defer responseBody.Close()
58
+
59
+	if command.IsTrusted() {
60
+		repoInfo.Class = "plugin"
61
+		return image.PushTrustedReference(dockerCli, repoInfo, named, authConfig, responseBody)
62
+	}
63
+
58 64
 	return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
59 65
 }
... ...
@@ -147,8 +147,19 @@ func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryI
147 147
 		}
148 148
 	}
149 149
 
150
+	scope := auth.RepositoryScope{
151
+		Repository: repoInfo.FullName(),
152
+		Actions:    actions,
153
+		Class:      repoInfo.Class,
154
+	}
150 155
 	creds := simpleCredentialStore{auth: authConfig}
151
-	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...)
156
+	tokenHandlerOptions := auth.TokenHandlerOptions{
157
+		Transport:   authTransport,
158
+		Credentials: creds,
159
+		Scopes:      []auth.Scope{scope},
160
+		ClientID:    registry.AuthClientID,
161
+	}
162
+	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
152 163
 	basicHandler := auth.NewBasicHandler(creds)
153 164
 	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
154 165
 	tr := transport.NewTransport(base, modifiers...)
... ...
@@ -2,6 +2,7 @@ package main
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"os/exec"
5 6
 
6 7
 	"github.com/docker/docker/pkg/integration/checker"
7 8
 	"github.com/go-check/check"
... ...
@@ -269,3 +270,63 @@ func (s *DockerSuite) TestPluginInspectOnWindows(c *check.C) {
269 269
 	c.Assert(out, checker.Contains, "plugins are not supported on this platform")
270 270
 	c.Assert(err.Error(), checker.Contains, "plugins are not supported on this platform")
271 271
 }
272
+
273
+func (s *DockerTrustSuite) TestPluginTrustedInstall(c *check.C) {
274
+	testRequires(c, DaemonIsLinux, IsAmd64, Network)
275
+
276
+	trustedName := s.setupTrustedplugin(c, pNameWithTag, "trusted-plugin-install")
277
+
278
+	installCmd := exec.Command(dockerBinary, "plugin", "install", "--grant-all-permissions", trustedName)
279
+	s.trustedCmd(installCmd)
280
+	out, _, err := runCommandWithOutput(installCmd)
281
+
282
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
283
+	c.Assert(err, checker.IsNil)
284
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
285
+
286
+	out, _, err = dockerCmdWithError("plugin", "ls")
287
+	c.Assert(err, checker.IsNil)
288
+	c.Assert(out, checker.Contains, "true")
289
+
290
+	out, _, err = dockerCmdWithError("plugin", "disable", trustedName)
291
+	c.Assert(err, checker.IsNil)
292
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
293
+
294
+	out, _, err = dockerCmdWithError("plugin", "enable", trustedName)
295
+	c.Assert(err, checker.IsNil)
296
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
297
+
298
+	out, _, err = dockerCmdWithError("plugin", "rm", "-f", trustedName)
299
+	c.Assert(err, checker.IsNil)
300
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
301
+
302
+	// Try untrusted pull to ensure we pushed the tag to the registry
303
+	installCmd = exec.Command(dockerBinary, "plugin", "install", "--disable-content-trust=true", "--grant-all-permissions", trustedName)
304
+	s.trustedCmd(installCmd)
305
+	out, _, err = runCommandWithOutput(installCmd)
306
+	c.Assert(err, check.IsNil, check.Commentf(out))
307
+	c.Assert(string(out), checker.Contains, "Status: Downloaded", check.Commentf(out))
308
+
309
+	out, _, err = dockerCmdWithError("plugin", "ls")
310
+	c.Assert(err, checker.IsNil)
311
+	c.Assert(out, checker.Contains, "true")
312
+
313
+}
314
+
315
+func (s *DockerTrustSuite) TestPluginUntrustedInstall(c *check.C) {
316
+	testRequires(c, DaemonIsLinux, IsAmd64, Network)
317
+
318
+	pluginName := fmt.Sprintf("%v/dockercliuntrusted/plugintest:latest", privateRegistryURL)
319
+	// install locally and push to private registry
320
+	dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--alias", pluginName, pNameWithTag)
321
+	dockerCmd(c, "plugin", "push", pluginName)
322
+	dockerCmd(c, "plugin", "rm", "-f", pluginName)
323
+
324
+	// Try trusted install on untrusted plugin
325
+	installCmd := exec.Command(dockerBinary, "plugin", "install", "--grant-all-permissions", pluginName)
326
+	s.trustedCmd(installCmd)
327
+	out, _, err := runCommandWithOutput(installCmd)
328
+
329
+	c.Assert(err, check.NotNil, check.Commentf(out))
330
+	c.Assert(string(out), checker.Contains, "Error: remote trust data does not exist", check.Commentf(out))
331
+}
... ...
@@ -211,6 +211,29 @@ func (s *DockerTrustSuite) setupTrustedImage(c *check.C, name string) string {
211 211
 	return repoName
212 212
 }
213 213
 
214
+func (s *DockerTrustSuite) setupTrustedplugin(c *check.C, source, name string) string {
215
+	repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, name)
216
+	// tag the image and upload it to the private registry
217
+	dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--alias", repoName, source)
218
+
219
+	pushCmd := exec.Command(dockerBinary, "plugin", "push", repoName)
220
+	s.trustedCmd(pushCmd)
221
+	out, _, err := runCommandWithOutput(pushCmd)
222
+
223
+	if err != nil {
224
+		c.Fatalf("Error running trusted plugin push: %s\n%s", err, out)
225
+	}
226
+	if !strings.Contains(string(out), "Signing and pushing trust metadata") {
227
+		c.Fatalf("Missing expected output on trusted push:\n%s", out)
228
+	}
229
+
230
+	if out, status := dockerCmd(c, "plugin", "rm", "-f", repoName); status != 0 {
231
+		c.Fatalf("Error removing plugin %q\n%s", repoName, out)
232
+	}
233
+
234
+	return repoName
235
+}
236
+
214 237
 func notaryClientEnv(cmd *exec.Cmd) {
215 238
 	pwd := "12345678"
216 239
 	env := []string{