Support for docker content trust for plugins

Add integration test for docker content trust

Signed-off-by: Derek McGowan <derek@mcgstyle.net> (github: dmcgowan)
This commit is contained in:
Derek McGowan 2016-12-27 12:51:00 -08:00
parent a9fa38b1ed
commit 14e8bba4f5
No known key found for this signature in database
GPG key ID: F58C5D0A4405ACDB
8 changed files with 179 additions and 10 deletions

View file

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

View file

@ -235,7 +235,7 @@ func runBuild(dockerCli *command.DockerCli, options buildOptions) error {
var resolvedTags []*resolvedTag
if command.IsTrusted() {
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
// Dockerfile which uses trusted pulls.

View file

@ -39,6 +39,11 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
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
// tag provided in the function and then do an AddTarget later.
target := &client.Target{}
@ -75,14 +80,14 @@ func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry
default:
// We want trust signatures to always take an explicit tag,
// 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
}
fmt.Fprintln(cli.Out(), "No tag specified, skipping trust metadata push")
return nil
}
if err = jsonmessage.DisplayJSONMessagesToStream(responseBody, cli.Out(), handleTarget); err != nil {
if err := jsonmessage.DisplayJSONMessagesToStream(in, cli.Out(), handleTarget); err != nil {
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
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 {
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)
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
// all other delegation roles

View file

@ -11,6 +11,7 @@ import (
registrytypes "github.com/docker/docker/api/types/registry"
"github.com/docker/docker/cli"
"github.com/docker/docker/cli/command"
"github.com/docker/docker/cli/command/image"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/reference"
"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.StringVar(&options.alias, "alias", "", "Local name for plugin")
command.AddTrustedFlags(flags, true)
return cmd
}
@ -63,6 +66,24 @@ func getRepoIndexFromUnnormalizedRef(ref distreference.Named) (*registrytypes.In
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 {
// Parse name using distribution reference package to support name
// 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()
}
ctx := context.Background()
index, err := getRepoIndexFromUnnormalizedRef(ref)
if err != nil {
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)
@ -104,7 +153,7 @@ func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error {
options := types.PluginInstallOptions{
RegistryAuth: encodedAuth,
RemoteRef: ref.String(),
RemoteRef: remote,
Disabled: opts.disable,
AcceptAllPermissions: opts.grantPerms,
AcceptPermissionsFunc: acceptPrivileges(dockerCli, opts.name),

View file

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

View file

@ -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}
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)
modifiers = append(modifiers, transport.RequestModifier(auth.NewAuthorizer(challengeManager, tokenHandler, basicHandler)))
tr := transport.NewTransport(base, modifiers...)

View file

@ -2,6 +2,7 @@ package main
import (
"fmt"
"os/exec"
"github.com/docker/docker/pkg/integration/checker"
"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(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))
}

View file

@ -211,6 +211,29 @@ func (s *DockerTrustSuite) setupTrustedImage(c *check.C, name string) string {
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) {
pwd := "12345678"
env := []string{