Browse Source

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 8 years ago
parent
commit
14e8bba4f5

+ 1 - 1
cli/command/container/create.go

@@ -170,7 +170,7 @@ func createContainer(ctx context.Context, dockerCli *command.DockerCli, config *
 
 
 		if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() {
 		if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() {
 			var err error
 			var err error
-			trustedRef, err = image.TrustedReference(ctx, dockerCli, ref)
+			trustedRef, err = image.TrustedReference(ctx, dockerCli, ref, nil)
 			if err != nil {
 			if err != nil {
 				return nil, err
 				return nil, err
 			}
 			}

+ 1 - 1
cli/command/image/build.go

@@ -235,7 +235,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
 	var resolvedTags []*resolvedTag
 	var resolvedTags []*resolvedTag
 	if command.IsTrusted() {
 	if command.IsTrusted() {
 		translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
 		translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) {
-			return TrustedReference(ctx, dockerCli, ref)
+			return TrustedReference(ctx, dockerCli, ref, nil)
 		}
 		}
 		// Wrap the tar archive to replace the Dockerfile entry with the rewritten
 		// Wrap the tar archive to replace the Dockerfile entry with the rewritten
 		// Dockerfile which uses trusted pulls.
 		// Dockerfile which uses trusted pulls.

+ 18 - 5
cli/command/image/trust.go

@@ -39,6 +39,11 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 
 
 	defer responseBody.Close()
 	defer responseBody.Close()
 
 
+	return PushTrustedReference(cli, repoInfo, ref, authConfig, responseBody)
+}
+
+// PushTrustedReference pushes a canonical reference to the trust server.
+func PushTrustedReference(cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, in io.Reader) error {
 	// If it is a trusted push we would like to find the target entry which match the
 	// If it is a trusted push we would like to find the target entry which match the
 	// tag provided in the function and then do an AddTarget later.
 	// tag provided in the function and then do an AddTarget later.
 	target := &client.Target{}
 	target := &client.Target{}
@@ -75,14 +80,14 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
 	default:
 	default:
 		// We want trust signatures to always take an explicit tag,
 		// We want trust signatures to always take an explicit tag,
 		// otherwise it will act as an untrusted push.
 		// otherwise it will act as an untrusted push.
-		if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), nil); err != nil {
+		if err := jsonmessage.DisplayJSONMessagesToStream(in, cli.Out(), nil); err != nil {
 			return err
 			return err
 		}
 		}
 		fmt.Fprintln(cli.Out(), "No tag specified, skipping trust metadata push")
 		fmt.Fprintln(cli.Out(), "No tag specified, skipping trust metadata push")
 		return nil
 		return nil
 	}
 	}
 
 
-	if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), handleTarget); err != nil {
+	if err := jsonmessage.DisplayJSONMessagesToStream(in, cli.Out(), handleTarget); err != nil {
 		return err
 		return err
 	}
 	}
 
 
@@ -315,8 +320,16 @@ func imagePullPrivileged(ctx context.Context, cli *command.DockerCli, authConfig
 }
 }
 
 
 // TrustedReference returns the canonical trusted reference for an image reference
 // TrustedReference returns the canonical trusted reference for an image reference
-func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (reference.Canonical, error) {
-	repoInfo, err := registry.ParseRepositoryInfo(ref)
+func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged, rs registry.Service) (reference.Canonical, error) {
+	var (
+		repoInfo *registry.RepositoryInfo
+		err      error
+	)
+	if rs != nil {
+		repoInfo, err = rs.ResolveRepository(ref)
+	} else {
+		repoInfo, err = registry.ParseRepositoryInfo(ref)
+	}
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}
@@ -332,7 +345,7 @@ func TrustedReference(ctx context.Context, cli *command.DockerCli, ref reference
 
 
 	t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
 	t, err := notaryRepo.GetTargetByName(ref.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole)
 	if err != nil {
 	if err != nil {
-		return nil, err
+		return nil, trust.NotaryError(repoInfo.FullName(), err)
 	}
 	}
 	// Only list tags in the top level targets role or the releases delegation role - ignore
 	// Only list tags in the top level targets role or the releases delegation role - ignore
 	// all other delegation roles
 	// all other delegation roles

+ 51 - 2
cli/command/plugin/install.go

@@ -11,6 +11,7 @@ import (
 	registrytypes "github.com/docker/docker/api/types/registry"
 	registrytypes "github.com/docker/docker/api/types/registry"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
+	"github.com/docker/docker/cli/command/image"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
@@ -46,6 +47,8 @@ func newInstallCommand(dockerCli *command.DockerCli) *cobra.Command {
 	flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
 	flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install")
 	flags.StringVar(&options.alias, "alias", "", "Local name for plugin")
 	flags.StringVar(&options.alias, "alias", "", "Local name for plugin")
 
 
+	command.AddTrustedFlags(flags, true)
+
 	return cmd
 	return cmd
 }
 }
 
 
@@ -63,6 +66,24 @@ func getRepoIndexFromUnnormalizedRef(ref distreference.Named) (*registrytypes.In
 	return repoInfo.Index, nil
 	return repoInfo.Index, nil
 }
 }
 
 
+type pluginRegistryService struct {
+	registry.Service
+}
+
+func (s pluginRegistryService) ResolveRepository(name reference.Named) (repoInfo *registry.RepositoryInfo, err error) {
+	repoInfo, err = s.Service.ResolveRepository(name)
+	if repoInfo != nil {
+		repoInfo.Class = "plugin"
+	}
+	return
+}
+
+func newRegistryService() registry.Service {
+	return pluginRegistryService{
+		Service: registry.NewService(registry.ServiceOptions{V2Only: true}),
+	}
+}
+
 func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
 func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
 	// Parse name using distribution reference package to support name
 	// Parse name using distribution reference package to support name
 	// containing both tag and digest. Names with both tag and digest
 	// containing both tag and digest. Names with both tag and digest
@@ -85,13 +106,41 @@ func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
 		}
 		}
 		alias = aref.String()
 		alias = aref.String()
 	}
 	}
+	ctx := context.Background()
 
 
 	index, err := getRepoIndexFromUnnormalizedRef(ref)
 	index, err := getRepoIndexFromUnnormalizedRef(ref)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
 	}
 	}
 
 
-	ctx := context.Background()
+	remote := ref.String()
+
+	_, isCanonical := ref.(distreference.Canonical)
+	if command.IsTrusted() && !isCanonical {
+		if alias == "" {
+			alias = ref.String()
+		}
+		var nt reference.NamedTagged
+		named, err := reference.ParseNamed(ref.Name())
+		if err != nil {
+			return err
+		}
+		if tagged, ok := ref.(distreference.Tagged); ok {
+			nt, err = reference.WithTag(named, tagged.Tag())
+			if err != nil {
+				return err
+			}
+		} else {
+			named = reference.WithDefaultTag(named)
+			nt = named.(reference.NamedTagged)
+		}
+
+		trusted, err := image.TrustedReference(ctx, dockerCli, nt, newRegistryService())
+		if err != nil {
+			return err
+		}
+		remote = trusted.String()
+	}
 
 
 	authConfig := command.ResolveAuthConfig(ctx, dockerCli, index)
 	authConfig := command.ResolveAuthConfig(ctx, dockerCli, index)
 
 
@@ -104,7 +153,7 @@ func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
 
 
 	options := types.PluginInstallOptions{
 	options := types.PluginInstallOptions{
 		RegistryAuth:          encodedAuth,
 		RegistryAuth:          encodedAuth,
-		RemoteRef:             ref.String(),
+		RemoteRef:             remote,
 		Disabled:              opts.disable,
 		Disabled:              opts.disable,
 		AcceptAllPermissions:  opts.grantPerms,
 		AcceptAllPermissions:  opts.grantPerms,
 		AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name),
 		AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name),

+ 12 - 0
cli/command/plugin/push.go

@@ -7,6 +7,7 @@ import (
 
 
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli"
 	"github.com/docker/docker/cli/command"
 	"github.com/docker/docker/cli/command"
+	"github.com/docker/docker/cli/command/image"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/pkg/jsonmessage"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/reference"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
@@ -22,6 +23,11 @@ func newPushCommand(dockerCli *command.DockerCli) *cobra.Command {
 			return runPush(dockerCli, args[0])
 			return runPush(dockerCli, args[0])
 		},
 		},
 	}
 	}
+
+	flags := cmd.Flags()
+
+	command.AddTrustedFlags(flags, true)
+
 	return cmd
 	return cmd
 }
 }
 
 
@@ -55,5 +61,11 @@ func runPush(dockerCli *command.DockerCli, name string) error {
 		return err
 		return err
 	}
 	}
 	defer responseBody.Close()
 	defer responseBody.Close()
+
+	if command.IsTrusted() {
+		repoInfo.Class = "plugin"
+		return image.PushTrustedReference(dockerCli, repoInfo, named, authConfig, responseBody)
+	}
+
 	return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
 	return jsonmessage.DisplayJSONMessagesToStream(responseBody, dockerCli.Out(), nil)
 }
 }

+ 12 - 1
cli/trust/trust.go

@@ -147,8 +147,19 @@ func GetNotaryRepository(streams command.Streams, repoInfo *registry.RepositoryI
 		}
 		}
 	}
 	}
 
 
+	scope := auth.RepositoryScope{
+		Repository: repoInfo.FullName(),
+		Actions:    actions,
+		Class:      repoInfo.Class,
+	}
 	creds := simpleCredentialStore{auth: authConfig}
 	creds := simpleCredentialStore{auth: authConfig}
-	tokenHandler := auth.NewTokenHandler(authTransport, creds, repoInfo.FullName(), actions...)
+	tokenHandlerOptions := auth.TokenHandlerOptions{
+		Transport:   authTransport,
+		Credentials: creds,
+		Scopes:      []auth.Scope{scope},
+		ClientID:    registry.AuthClientID,
+	}
+	tokenHandler := auth.NewTokenHandlerWithOptions(tokenHandlerOptions)
 	basicHandler := auth.NewBasicHandler(creds)
 	basicHandler := auth.NewBasicHandler(creds)
 	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
 	modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
 	tr := transport.NewTransport(base, modifiers...)
 	tr := transport.NewTransport(base, modifiers...)

+ 61 - 0
integration-cli/docker_cli_plugins_test.go

@@ -2,6 +2,7 @@ package main
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"os/exec"
 
 
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/docker/docker/pkg/integration/checker"
 	"github.com/go-check/check"
 	"github.com/go-check/check"
@@ -269,3 +270,63 @@ func (s *DockerSuite) TestPluginInspectOnWindows(c *check.C) {
 	c.Assert(out, checker.Contains, "plugins are not supported on this platform")
 	c.Assert(out, checker.Contains, "plugins are not supported on this platform")
 	c.Assert(err.Error(), checker.Contains, "plugins are not supported on this platform")
 	c.Assert(err.Error(), checker.Contains, "plugins are not supported on this platform")
 }
 }
+
+func (s *DockerTrustSuite) TestPluginTrustedInstall(c *check.C) {
+	testRequires(c, DaemonIsLinux, IsAmd64, Network)
+
+	trustedName := s.setupTrustedplugin(c, pNameWithTag, "trusted-plugin-install")
+
+	installCmd := exec.Command(dockerBinary, "plugin", "install", "--grant-all-permissions", trustedName)
+	s.trustedCmd(installCmd)
+	out, _, err := runCommandWithOutput(installCmd)
+
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
+
+	out, _, err = dockerCmdWithError("plugin", "ls")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, "true")
+
+	out, _, err = dockerCmdWithError("plugin", "disable", trustedName)
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
+
+	out, _, err = dockerCmdWithError("plugin", "enable", trustedName)
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
+
+	out, _, err = dockerCmdWithError("plugin", "rm", "-f", trustedName)
+	c.Assert(err, checker.IsNil)
+	c.Assert(strings.TrimSpace(out), checker.Contains, trustedName)
+
+	// Try untrusted pull to ensure we pushed the tag to the registry
+	installCmd = exec.Command(dockerBinary, "plugin", "install", "--disable-content-trust=true", "--grant-all-permissions", trustedName)
+	s.trustedCmd(installCmd)
+	out, _, err = runCommandWithOutput(installCmd)
+	c.Assert(err, check.IsNil, check.Commentf(out))
+	c.Assert(string(out), checker.Contains, "Status: Downloaded", check.Commentf(out))
+
+	out, _, err = dockerCmdWithError("plugin", "ls")
+	c.Assert(err, checker.IsNil)
+	c.Assert(out, checker.Contains, "true")
+
+}
+
+func (s *DockerTrustSuite) TestPluginUntrustedInstall(c *check.C) {
+	testRequires(c, DaemonIsLinux, IsAmd64, Network)
+
+	pluginName := fmt.Sprintf("%v/dockercliuntrusted/plugintest:latest", privateRegistryURL)
+	// install locally and push to private registry
+	dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--alias", pluginName, pNameWithTag)
+	dockerCmd(c, "plugin", "push", pluginName)
+	dockerCmd(c, "plugin", "rm", "-f", pluginName)
+
+	// Try trusted install on untrusted plugin
+	installCmd := exec.Command(dockerBinary, "plugin", "install", "--grant-all-permissions", pluginName)
+	s.trustedCmd(installCmd)
+	out, _, err := runCommandWithOutput(installCmd)
+
+	c.Assert(err, check.NotNil, check.Commentf(out))
+	c.Assert(string(out), checker.Contains, "Error: remote trust data does not exist", check.Commentf(out))
+}

+ 23 - 0
integration-cli/trust_server.go

@@ -211,6 +211,29 @@ func (s *DockerTrustSuite) setupTrustedImage(c *check.C, name string) string {
 	return repoName
 	return repoName
 }
 }
 
 
+func (s *DockerTrustSuite) setupTrustedplugin(c *check.C, source, name string) string {
+	repoName := fmt.Sprintf("%v/dockercli/%s:latest", privateRegistryURL, name)
+	// tag the image and upload it to the private registry
+	dockerCmd(c, "plugin", "install", "--grant-all-permissions", "--alias", repoName, source)
+
+	pushCmd := exec.Command(dockerBinary, "plugin", "push", repoName)
+	s.trustedCmd(pushCmd)
+	out, _, err := runCommandWithOutput(pushCmd)
+
+	if err != nil {
+		c.Fatalf("Error running trusted plugin push: %s\n%s", err, out)
+	}
+	if !strings.Contains(string(out), "Signing and pushing trust metadata") {
+		c.Fatalf("Missing expected output on trusted push:\n%s", out)
+	}
+
+	if out, status := dockerCmd(c, "plugin", "rm", "-f", repoName); status != 0 {
+		c.Fatalf("Error removing plugin %q\n%s", repoName, out)
+	}
+
+	return repoName
+}
+
 func notaryClientEnv(cmd *exec.Cmd) {
 func notaryClientEnv(cmd *exec.Cmd) {
 	pwd := "12345678"
 	pwd := "12345678"
 	env := []string{
 	env := []string{