From 6e21829af477c1d925545ddfc1b9943ead5c9655 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Thu, 8 Mar 2018 14:35:17 +0100 Subject: [PATCH 1/2] Refactor content_trust cli/flags handling Remove the global variable used. Allows easier unit testing. Signed-off-by: Vincent Demeester --- cli/command/cli.go | 11 ++- cli/command/container/create.go | 21 ++-- cli/command/container/create_test.go | 5 +- cli/command/container/run.go | 7 +- cli/command/image/build.go | 20 ++-- cli/command/image/pull.go | 11 ++- cli/command/image/pull_test.go | 42 ++++++++ cli/command/image/push.go | 18 +++- cli/command/image/push_test.go | 5 - cli/command/plugin/install.go | 9 +- cli/command/plugin/push.go | 19 ++-- cli/command/plugin/upgrade.go | 2 +- cli/command/service/trust.go | 2 +- cli/command/trust.go | 40 +------- cli/command/trust/inspect_test.go | 21 ++-- cli/command/trust/revoke_test.go | 21 ++-- cli/command/trust/sign_test.go | 7 +- cli/command/trust/signer_add_test.go | 9 +- cli/command/trust/signer_remove_test.go | 9 +- cli/command/trust/view_test.go | 21 ++-- cmd/docker/docker.go | 13 ++- cmd/docker/docker_test.go | 2 +- docs/yaml/generate.go | 2 +- internal/test/cli.go | 26 ++++- .../test/notary/client.go | 97 +++++++++++++++++-- man/generate.go | 2 +- 26 files changed, 296 insertions(+), 146 deletions(-) rename cli/command/trust/client_test.go => internal/test/notary/client.go (68%) diff --git a/cli/command/cli.go b/cli/command/cli.go index 88c0a085e6..2fc1e7cccb 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -52,6 +52,7 @@ type Cli interface { DefaultVersion() string ManifestStore() manifeststore.Store RegistryClient(bool) registryclient.RegistryClient + IsTrusted() bool } // DockerCli is an instance the docker command line client. @@ -64,6 +65,7 @@ type DockerCli struct { client client.APIClient serverInfo ServerInfo clientInfo ClientInfo + isTrusted bool } // DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified. @@ -121,6 +123,11 @@ func (cli *DockerCli) ClientInfo() ClientInfo { return cli.clientInfo } +// IsTrusted returns if content trust is enabled for the cli +func (cli *DockerCli) IsTrusted() bool { + return cli.isTrusted +} + // ManifestStore returns a store for local manifests func (cli *DockerCli) ManifestStore() manifeststore.Store { // TODO: support override default location from config file @@ -237,8 +244,8 @@ func (c ClientInfo) HasKubernetes() bool { } // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. -func NewDockerCli(in io.ReadCloser, out, err io.Writer) *DockerCli { - return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err} +func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool) *DockerCli { + return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err, isTrusted: isTrusted} } // NewAPIClientFromFlags creates a new APIClient from command line flags diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 099184d940..1358372ac9 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -21,8 +21,9 @@ import ( ) type createOptions struct { - name string - platform string + name string + platform string + untrusted bool } // NewCreateCommand creates a new cobra.Command for `docker create` @@ -53,7 +54,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { flags.Bool("help", false, "Print usage") command.AddPlatformFlag(flags, &opts.platform) - command.AddTrustVerificationFlags(flags) + command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) copts = addFlags(flags) return cmd } @@ -64,7 +65,7 @@ func runCreate(dockerCli command.Cli, flags *pflag.FlagSet, opts *createOptions, reportError(dockerCli.Err(), "create", err.Error(), true) return cli.StatusError{StatusCode: 125} } - response, err := createContainer(context.Background(), dockerCli, containerConfig, opts.name, opts.platform) + response, err := createContainer(context.Background(), dockerCli, containerConfig, opts) if err != nil { return err } @@ -158,7 +159,8 @@ func newCIDFile(path string) (*cidFile, error) { return &cidFile{path: path, file: f}, nil } -func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, name string, platform string) (*container.ContainerCreateCreatedBody, error) { +// nolint: gocyclo +func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, opts *createOptions) (*container.ContainerCreateCreatedBody, error) { config := containerConfig.Config hostConfig := containerConfig.HostConfig networkingConfig := containerConfig.NetworkingConfig @@ -182,7 +184,8 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig if named, ok := ref.(reference.Named); ok { namedRef = reference.TagNameOnly(named) - if taggedRef, ok := namedRef.(reference.NamedTagged); ok && command.IsTrusted() { + isContentTrustEnabled := !opts.untrusted && dockerCli.IsTrusted() + if taggedRef, ok := namedRef.(reference.NamedTagged); ok && isContentTrustEnabled { var err error trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil) if err != nil { @@ -193,7 +196,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig } //create the container - response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name) + response, err := dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name) //if image not found try to pull it if err != nil { @@ -201,7 +204,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef)) // we don't want to write to stdout anything apart from container.ID - if err := pullImage(ctx, dockerCli, config.Image, platform, stderr); err != nil { + if err := pullImage(ctx, dockerCli, config.Image, opts.platform, stderr); err != nil { return nil, err } if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil { @@ -211,7 +214,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig } // Retry var retryErr error - response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, name) + response, retryErr = dockerCli.Client().ContainerCreate(ctx, config, hostConfig, networkingConfig, opts.name) if retryErr != nil { return nil, retryErr } diff --git a/cli/command/container/create_test.go b/cli/command/container/create_test.go index aa20678eeb..1df75b50bc 100644 --- a/cli/command/container/create_test.go +++ b/cli/command/container/create_test.go @@ -107,7 +107,10 @@ func TestCreateContainerPullsImageIfMissing(t *testing.T) { }, HostConfig: &container.HostConfig{}, } - body, err := createContainer(context.Background(), cli, config, "name", runtime.GOOS) + body, err := createContainer(context.Background(), cli, config, &createOptions{ + name: "name", + platform: runtime.GOOS, + }) assert.NilError(t, err) expected := container.ContainerCreateCreatedBody{ID: containerID} assert.Check(t, is.DeepEqual(expected, *body)) diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 40fecdc717..1a1a98515e 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -25,11 +25,10 @@ import ( ) type runOptions struct { + createOptions detach bool sigProxy bool - name string detachKeys string - platform string } // NewRunCommand create a new `docker run` command @@ -64,7 +63,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { flags.Bool("help", false, "Print usage") command.AddPlatformFlag(flags, &opts.platform) - command.AddTrustVerificationFlags(flags) + command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) copts = addFlags(flags) return cmd } @@ -162,7 +161,7 @@ func runContainer(dockerCli command.Cli, opts *runOptions, copts *containerOptio ctx, cancelFun := context.WithCancel(context.Background()) - createResponse, err := createContainer(ctx, dockerCli, containerConfig, opts.name, opts.platform) + createResponse, err := createContainer(ctx, dockerCli, containerConfig, &opts.createOptions) if err != nil { reportError(stderr, cmdPath, err.Error(), true) return runStartContainerErr(err) diff --git a/cli/command/image/build.go b/cli/command/image/build.go index dd901bd8ae..c812009970 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -67,6 +67,7 @@ type buildOptions struct { imageIDFile string stream bool platform string + untrusted bool } // dockerfileFromStdin returns true when the user specified that the Dockerfile @@ -137,7 +138,7 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command { flags.StringVar(&options.target, "target", "", "Set the target build stage to build.") flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file") - command.AddTrustVerificationFlags(flags) + command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.IsTrusted()) command.AddPlatformFlag(flags, &options.platform) flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer") @@ -285,7 +286,8 @@ func runBuild(dockerCli command.Cli, options buildOptions) error { defer cancel() var resolvedTags []*resolvedTag - if command.IsTrusted() { + isContentTrustEnabled := !options.untrusted && dockerCli.IsTrusted() + if isContentTrustEnabled { translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) { return TrustedReference(ctx, dockerCli, ref, nil) } @@ -293,10 +295,10 @@ func runBuild(dockerCli command.Cli, options buildOptions) error { if buildCtx != nil { // Wrap the tar archive to replace the Dockerfile entry with the rewritten // Dockerfile which uses trusted pulls. - buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags) + buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags, isContentTrustEnabled) } else if dockerfileCtx != nil { // if there was not archive context still do the possible replacements in Dockerfile - newDockerfile, _, err := rewriteDockerfileFrom(ctx, dockerfileCtx, translator) + newDockerfile, _, err := rewriteDockerfileFrom(ctx, dockerfileCtx, translator, isContentTrustEnabled) if err != nil { return err } @@ -460,7 +462,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error { return err } } - if command.IsTrusted() { + if !options.untrusted && dockerCli.IsTrusted() { // Since the build was successful, now we must tag any of the resolved // images from the above Dockerfile rewrite. for _, resolved := range resolvedTags { @@ -503,7 +505,7 @@ type resolvedTag struct { // "FROM " instructions to a digest reference. `translator` is a // function that takes a repository name and tag reference and returns a // trusted digest reference. -func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator translatorFunc) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) { +func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator translatorFunc, istrusted bool) (newDockerfile []byte, resolvedTags []*resolvedTag, err error) { scanner := bufio.NewScanner(dockerfile) buf := bytes.NewBuffer(nil) @@ -520,7 +522,7 @@ func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator return nil, nil, err } ref = reference.TagNameOnly(ref) - if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() { + if ref, ok := ref.(reference.NamedTagged); ok && istrusted { trustedRef, err := translator(ctx, ref) if err != nil { return nil, nil, err @@ -547,7 +549,7 @@ func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator // replaces the entry with the given Dockerfile name with the contents of the // new Dockerfile. Returns a new tar archive stream with the replaced // Dockerfile. -func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser { +func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag, istrusted bool) io.ReadCloser { pipeReader, pipeWriter := io.Pipe() go func() { tarReader := tar.NewReader(inputTarStream) @@ -574,7 +576,7 @@ func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadClos // generated from a directory on the local filesystem, the // Dockerfile will only appear once in the archive. var newDockerfile []byte - newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator) + newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator, istrusted) if err != nil { pipeWriter.CloseWithError(err) return diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index be653ec926..e0723151a6 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -14,9 +14,10 @@ import ( ) type pullOptions struct { - remote string - all bool - platform string + remote string + all bool + platform string + untrusted bool } // NewPullCommand creates a new `docker pull` command @@ -38,7 +39,7 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command { flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository") command.AddPlatformFlag(flags, &opts.platform) - command.AddTrustVerificationFlags(flags) + command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) return cmd } @@ -65,7 +66,7 @@ func runPull(cli command.Cli, opts pullOptions) error { // Check if reference has a digest _, isCanonical := distributionRef.(reference.Canonical) - if command.IsTrusted() && !isCanonical { + if !opts.untrusted && cli.IsTrusted() && !isCanonical { err = trustedPull(ctx, cli, imgRefAndAuth, opts.platform) } else { err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all, opts.platform) diff --git a/cli/command/image/pull_test.go b/cli/command/image/pull_test.go index 7770e32952..551a296479 100644 --- a/cli/command/image/pull_test.go +++ b/cli/command/image/pull_test.go @@ -8,6 +8,7 @@ import ( "testing" "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/notary" "github.com/docker/docker/api/types" "github.com/gotestyourself/gotestyourself/assert" is "github.com/gotestyourself/gotestyourself/assert/cmp" @@ -77,3 +78,44 @@ func TestNewPullCommandSuccess(t *testing.T) { golden.Assert(t, cli.OutBuffer().String(), fmt.Sprintf("pull-command-success.%s.golden", tc.name)) } } + +func TestNewPullCommandWithContentTrustErrors(t *testing.T) { + testCases := []struct { + name string + args []string + expectedError string + notaryFunc test.NotaryClientFuncType + }{ + { + name: "offline-notary-server", + notaryFunc: notary.GetOfflineNotaryRepository, + expectedError: "client is offline", + args: []string{"image:tag"}, + }, + { + name: "empty-notary-server", + notaryFunc: notary.GetUninitializedNotaryRepository, + expectedError: "remote trust data does not exist", + args: []string{"image:tag"}, + }, + { + name: "empty-notary-server", + notaryFunc: notary.GetEmptyTargetsNotaryRepository, + expectedError: "No valid trust data for tag", + args: []string{"image:tag"}, + }, + } + for _, tc := range testCases { + cli := test.NewFakeCli(&fakeClient{ + imagePullFunc: func(ref string, options types.ImagePullOptions) (io.ReadCloser, error) { + return ioutil.NopCloser(strings.NewReader("")), fmt.Errorf("shouldn't try to pull image") + }, + }, test.IsTrusted) + cli.SetNotaryClient(tc.notaryFunc) + cmd := NewPullCommand(cli) + cmd.SetOutput(ioutil.Discard) + cmd.SetArgs(tc.args) + err := cmd.Execute() + assert.ErrorContains(t, err, tc.expectedError) + } +} diff --git a/cli/command/image/push.go b/cli/command/image/push.go index 741ef67c59..2760e047a1 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -11,26 +11,34 @@ import ( "github.com/spf13/cobra" ) +type pushOptions struct { + remote string + untrusted bool +} + // NewPushCommand creates a new `docker push` command func NewPushCommand(dockerCli command.Cli) *cobra.Command { + var opts pushOptions + cmd := &cobra.Command{ Use: "push [OPTIONS] NAME[:TAG]", Short: "Push an image or a repository to a registry", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return runPush(dockerCli, args[0]) + opts.remote = args[0] + return runPush(dockerCli, opts) }, } flags := cmd.Flags() - command.AddTrustSigningFlags(flags) + command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) return cmd } -func runPush(dockerCli command.Cli, remote string) error { - ref, err := reference.ParseNormalizedNamed(remote) +func runPush(dockerCli command.Cli, opts pushOptions) error { + ref, err := reference.ParseNormalizedNamed(opts.remote) if err != nil { return err } @@ -47,7 +55,7 @@ func runPush(dockerCli command.Cli, remote string) error { authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index) requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push") - if command.IsTrusted() { + if !opts.untrusted && dockerCli.IsTrusted() { return TrustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege) } diff --git a/cli/command/image/push_test.go b/cli/command/image/push_test.go index 72fefc0bde..10d5cb3a81 100644 --- a/cli/command/image/push_test.go +++ b/cli/command/image/push_test.go @@ -37,11 +37,6 @@ func TestNewPushCommandErrors(t *testing.T) { return ioutil.NopCloser(strings.NewReader("")), errors.Errorf("Failed to push") }, }, - { - name: "trust-error", - args: []string{"--disable-content-trust=false", "image:repo"}, - expectedError: "you are not authorized to perform this operation: server returned 401.", - }, } for _, tc := range testCases { cli := test.NewFakeCli(&fakeClient{imagePushFunc: tc.imagePushFunc}) diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index f7537670eb..20119edef3 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -24,11 +24,12 @@ type pluginOptions struct { disable bool args []string skipRemoteCheck bool + untrusted bool } -func loadPullFlags(opts *pluginOptions, flags *pflag.FlagSet) { +func loadPullFlags(dockerCli command.Cli, opts *pluginOptions, flags *pflag.FlagSet) { flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin") - command.AddTrustVerificationFlags(flags) + command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) } func newInstallCommand(dockerCli command.Cli) *cobra.Command { @@ -47,7 +48,7 @@ func newInstallCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() - loadPullFlags(&options, flags) + loadPullFlags(dockerCli, &options, flags) flags.BoolVar(&options.disable, "disable", false, "Do not enable the plugin on install") flags.StringVar(&options.localName, "alias", "", "Local name for plugin") return cmd @@ -90,7 +91,7 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti remote := ref.String() _, isCanonical := ref.(reference.Canonical) - if command.IsTrusted() && !isCanonical { + if !opts.untrusted && dockerCli.IsTrusted() && !isCanonical { ref = reference.TagNameOnly(ref) nt, ok := ref.(reference.NamedTagged) if !ok { diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index 68d25157c6..8e6872fd30 100644 --- a/cli/command/plugin/push.go +++ b/cli/command/plugin/push.go @@ -13,30 +13,37 @@ import ( "github.com/spf13/cobra" ) +type pushOptions struct { + name string + untrusted bool +} + func newPushCommand(dockerCli command.Cli) *cobra.Command { + var opts pushOptions cmd := &cobra.Command{ Use: "push [OPTIONS] PLUGIN[:TAG]", Short: "Push a plugin to a registry", Args: cli.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return runPush(dockerCli, args[0]) + opts.name = args[0] + return runPush(dockerCli, opts) }, } flags := cmd.Flags() - command.AddTrustSigningFlags(flags) + command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) return cmd } -func runPush(dockerCli command.Cli, name string) error { - named, err := reference.ParseNormalizedNamed(name) +func runPush(dockerCli command.Cli, opts pushOptions) error { + named, err := reference.ParseNormalizedNamed(opts.name) if err != nil { return err } if _, ok := named.(reference.Canonical); ok { - return errors.Errorf("invalid name: %s", name) + return errors.Errorf("invalid name: %s", opts.name) } named = reference.TagNameOnly(named) @@ -60,7 +67,7 @@ func runPush(dockerCli command.Cli, name string) error { } defer responseBody.Close() - if command.IsTrusted() { + if !opts.untrusted && dockerCli.IsTrusted() { repoInfo.Class = "plugin" return image.PushTrustedReference(dockerCli, repoInfo, named, authConfig, responseBody) } diff --git a/cli/command/plugin/upgrade.go b/cli/command/plugin/upgrade.go index f1a67edc40..f5afb509d5 100644 --- a/cli/command/plugin/upgrade.go +++ b/cli/command/plugin/upgrade.go @@ -30,7 +30,7 @@ func newUpgradeCommand(dockerCli command.Cli) *cobra.Command { } flags := cmd.Flags() - loadPullFlags(&options, flags) + loadPullFlags(dockerCli, &options, flags) flags.BoolVar(&options.skipRemoteCheck, "skip-remote-check", false, "Do not check if specified remote plugin matches existing plugin image") return cmd } diff --git a/cli/command/service/trust.go b/cli/command/service/trust.go index 8bf9149764..c82d718730 100644 --- a/cli/command/service/trust.go +++ b/cli/command/service/trust.go @@ -16,7 +16,7 @@ import ( ) func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm.ServiceSpec) error { - if !command.IsTrusted() { + if !dockerCli.IsTrusted() { // When not using content trust, digest resolution happens later when // contacting the registry to retrieve image information. return nil diff --git a/cli/command/trust.go b/cli/command/trust.go index 51f760ac5b..65f2408585 100644 --- a/cli/command/trust.go +++ b/cli/command/trust.go @@ -1,47 +1,15 @@ package command import ( - "os" - "strconv" - "github.com/spf13/pflag" ) -var ( - // TODO: make this not global - untrusted bool -) - -func init() { - untrusted = !getDefaultTrustState() -} - // AddTrustVerificationFlags adds content trust flags to the provided flagset -func AddTrustVerificationFlags(fs *pflag.FlagSet) { - trusted := getDefaultTrustState() - fs.BoolVar(&untrusted, "disable-content-trust", !trusted, "Skip image verification") +func AddTrustVerificationFlags(fs *pflag.FlagSet, v *bool, trusted bool) { + fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image verification") } // AddTrustSigningFlags adds "signing" flags to the provided flagset -func AddTrustSigningFlags(fs *pflag.FlagSet) { - trusted := getDefaultTrustState() - fs.BoolVar(&untrusted, "disable-content-trust", !trusted, "Skip image signing") -} - -// getDefaultTrustState returns true if content trust is enabled through the $DOCKER_CONTENT_TRUST environment variable. -func getDefaultTrustState() bool { - var trusted bool - if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" { - if t, err := strconv.ParseBool(e); t || err != nil { - // treat any other value as true - trusted = true - } - } - return trusted -} - -// IsTrusted returns true if content trust is enabled, either through the $DOCKER_CONTENT_TRUST environment variable, -// or through `--disabled-content-trust=false` on a command. -func IsTrusted() bool { - return !untrusted +func AddTrustSigningFlags(fs *pflag.FlagSet, v *bool, trusted bool) { + fs.BoolVar(v, "disable-content-trust", !trusted, "Skip image signing") } diff --git a/cli/command/trust/inspect_test.go b/cli/command/trust/inspect_test.go index 5bf41f5487..f72f84374c 100644 --- a/cli/command/trust/inspect_test.go +++ b/cli/command/trust/inspect_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/notary" "github.com/gotestyourself/gotestyourself/assert" "github.com/gotestyourself/gotestyourself/golden" ) @@ -46,14 +47,14 @@ func TestTrustInspectCommandErrors(t *testing.T) { func TestTrustInspectCommandOfflineErrors(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notary.GetOfflineNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"nonexistent-reg-name.io/image"}) cmd.SetOutput(ioutil.Discard) assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image") cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notary.GetOfflineNotaryRepository) cmd = newInspectCommand(cli) cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -62,7 +63,7 @@ func TestTrustInspectCommandOfflineErrors(t *testing.T) { func TestTrustInspectCommandUninitializedErrors(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getUninitializedNotaryRepository) + cli.SetNotaryClient(notary.GetUninitializedNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"reg/unsigned-img"}) cmd.SetOutput(ioutil.Discard) @@ -70,7 +71,7 @@ func TestTrustInspectCommandUninitializedErrors(t *testing.T) { golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-uninitialized.golden") cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getUninitializedNotaryRepository) + cli.SetNotaryClient(notary.GetUninitializedNotaryRepository) cmd = newInspectCommand(cli) cmd.SetArgs([]string{"reg/unsigned-img:tag"}) cmd.SetOutput(ioutil.Discard) @@ -80,7 +81,7 @@ func TestTrustInspectCommandUninitializedErrors(t *testing.T) { func TestTrustInspectCommandEmptyNotaryRepo(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"reg/img:unsigned-tag"}) cmd.SetOutput(ioutil.Discard) @@ -90,7 +91,7 @@ func TestTrustInspectCommandEmptyNotaryRepo(t *testing.T) { func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) + cli.SetNotaryClient(notary.GetLoadedWithNoSignersNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"signed-repo"}) assert.NilError(t, cmd.Execute()) @@ -99,7 +100,7 @@ func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) { func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) + cli.SetNotaryClient(notary.GetLoadedWithNoSignersNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"signed-repo:green"}) assert.NilError(t, cmd.Execute()) @@ -108,7 +109,7 @@ func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) { func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedNotaryRepository) + cli.SetNotaryClient(notary.GetLoadedNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"signed-repo"}) assert.NilError(t, cmd.Execute()) @@ -117,7 +118,7 @@ func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) { func TestTrustInspectCommandMultipleFullReposWithSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedNotaryRepository) + cli.SetNotaryClient(notary.GetLoadedNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"signed-repo", "signed-repo"}) assert.NilError(t, cmd.Execute()) @@ -126,7 +127,7 @@ func TestTrustInspectCommandMultipleFullReposWithSigners(t *testing.T) { func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedNotaryRepository) + cli.SetNotaryClient(notary.GetLoadedNotaryRepository) cmd := newInspectCommand(cli) cmd.SetArgs([]string{"signed-repo:unsigned"}) assert.NilError(t, cmd.Execute()) diff --git a/cli/command/trust/revoke_test.go b/cli/command/trust/revoke_test.go index adc20f5074..289fd3f173 100644 --- a/cli/command/trust/revoke_test.go +++ b/cli/command/trust/revoke_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/docker/cli/internal/test" + "github.com/docker/cli/internal/test/notary" "github.com/gotestyourself/gotestyourself/assert" is "github.com/gotestyourself/gotestyourself/assert/cmp" "github.com/theupdateframework/notary/client" @@ -55,7 +56,7 @@ func TestTrustRevokeCommandErrors(t *testing.T) { func TestTrustRevokeCommandOfflineErrors(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notary.GetOfflineNotaryRepository) cmd := newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image"}) cmd.SetOutput(ioutil.Discard) @@ -63,13 +64,13 @@ func TestTrustRevokeCommandOfflineErrors(t *testing.T) { assert.Check(t, is.Contains(cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.")) cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notary.GetOfflineNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image", "-y"}) cmd.SetOutput(ioutil.Discard) cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notary.GetOfflineNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -78,7 +79,7 @@ func TestTrustRevokeCommandOfflineErrors(t *testing.T) { func TestTrustRevokeCommandUninitializedErrors(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getUninitializedNotaryRepository) + cli.SetNotaryClient(notary.GetUninitializedNotaryRepository) cmd := newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image"}) cmd.SetOutput(ioutil.Discard) @@ -86,14 +87,14 @@ func TestTrustRevokeCommandUninitializedErrors(t *testing.T) { assert.Check(t, is.Contains(cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.")) cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getUninitializedNotaryRepository) + cli.SetNotaryClient(notary.GetUninitializedNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image", "-y"}) cmd.SetOutput(ioutil.Discard) assert.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: does not have trust data for") cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getUninitializedNotaryRepository) + cli.SetNotaryClient(notary.GetUninitializedNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -102,7 +103,7 @@ func TestTrustRevokeCommandUninitializedErrors(t *testing.T) { func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository) cmd := newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image"}) cmd.SetOutput(ioutil.Discard) @@ -110,14 +111,14 @@ func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) { assert.Check(t, is.Contains(cli.OutBuffer().String(), "Please confirm you would like to delete all signature data for reg-name.io/image? [y/N] \nAborting action.")) cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image", "-y"}) cmd.SetOutput(ioutil.Discard) assert.ErrorContains(t, cmd.Execute(), "could not remove signature for reg-name.io/image: no signed tags to remove") cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository) cmd = newRevokeCommand(cli) cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -126,7 +127,7 @@ func TestTrustRevokeCommandEmptyNotaryRepo(t *testing.T) { func TestNewRevokeTrustAllSigConfirmation(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notary.GetEmptyTargetsNotaryRepository) cmd := newRevokeCommand(cli) cmd.SetArgs([]string{"alpine"}) assert.NilError(t, cmd.Execute()) diff --git a/cli/command/trust/sign_test.go b/cli/command/trust/sign_test.go index 002cbd5fe8..e441f7bef2 100644 --- a/cli/command/trust/sign_test.go +++ b/cli/command/trust/sign_test.go @@ -11,6 +11,7 @@ import ( "github.com/docker/cli/cli/config" "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/test" + notaryfake "github.com/docker/cli/internal/test/notary" "github.com/gotestyourself/gotestyourself/assert" is "github.com/gotestyourself/gotestyourself/assert/cmp" "github.com/gotestyourself/gotestyourself/skip" @@ -76,7 +77,7 @@ func TestTrustSignCommandErrors(t *testing.T) { func TestTrustSignCommandOfflineErrors(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository) cmd := newSignCommand(cli) cmd.SetArgs([]string{"reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -282,7 +283,7 @@ func TestSignCommandChangeListIsCleanedOnError(t *testing.T) { config.SetDir(tmpDir) cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedNotaryRepository) + cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository) cmd := newSignCommand(cli) cmd.SetArgs([]string{"ubuntu:latest"}) cmd.SetOutput(ioutil.Discard) @@ -299,7 +300,7 @@ func TestSignCommandChangeListIsCleanedOnError(t *testing.T) { func TestSignCommandLocalFlag(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository) cmd := newSignCommand(cli) cmd.SetArgs([]string{"--local", "reg-name.io/image:red"}) cmd.SetOutput(ioutil.Discard) diff --git a/cli/command/trust/signer_add_test.go b/cli/command/trust/signer_add_test.go index 0f0c404d24..643a31b75a 100644 --- a/cli/command/trust/signer_add_test.go +++ b/cli/command/trust/signer_add_test.go @@ -10,6 +10,7 @@ import ( "github.com/docker/cli/cli/config" "github.com/docker/cli/internal/test" + notaryfake "github.com/docker/cli/internal/test/notary" "github.com/gotestyourself/gotestyourself/assert" is "github.com/gotestyourself/gotestyourself/assert/cmp" "github.com/theupdateframework/notary" @@ -58,7 +59,7 @@ func TestTrustSignerAddErrors(t *testing.T) { for _, tc := range testCases { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository) cmd := newSignerAddCommand(cli) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) @@ -77,7 +78,7 @@ func TestSignerAddCommandNoTargetsKey(t *testing.T) { defer os.Remove(tmpfile.Name()) cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository) cmd := newSignerAddCommand(cli) cmd.SetArgs([]string{"--key", tmpfile.Name(), "alice", "alpine", "linuxkit/alpine"}) @@ -92,7 +93,7 @@ func TestSignerAddCommandBadKeyPath(t *testing.T) { config.SetDir(tmpDir) cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository) cmd := newSignerAddCommand(cli) cmd.SetArgs([]string{"--key", "/path/to/key.pem", "alice", "alpine"}) @@ -117,7 +118,7 @@ func TestSignerAddCommandInvalidRepoName(t *testing.T) { assert.NilError(t, ioutil.WriteFile(pubKeyFilepath, pubKeyFixture, notary.PrivNoExecPerms)) cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getUninitializedNotaryRepository) + cli.SetNotaryClient(notaryfake.GetUninitializedNotaryRepository) cmd := newSignerAddCommand(cli) imageName := "870d292919d01a0af7e7f056271dc78792c05f55f49b9b9012b6d89725bd9abd" cmd.SetArgs([]string{"--key", pubKeyFilepath, "alice", imageName}) diff --git a/cli/command/trust/signer_remove_test.go b/cli/command/trust/signer_remove_test.go index 660d7264b5..4f11634be2 100644 --- a/cli/command/trust/signer_remove_test.go +++ b/cli/command/trust/signer_remove_test.go @@ -5,6 +5,7 @@ import ( "testing" "github.com/docker/cli/internal/test" + notaryfake "github.com/docker/cli/internal/test/notary" "github.com/gotestyourself/gotestyourself/assert" is "github.com/gotestyourself/gotestyourself/assert/cmp" "github.com/theupdateframework/notary/client" @@ -57,7 +58,7 @@ func TestTrustSignerRemoveErrors(t *testing.T) { } for _, tc := range testCasesWithOutput { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository) cmd := newSignerRemoveCommand(cli) cmd.SetArgs(tc.args) cmd.SetOutput(ioutil.Discard) @@ -69,7 +70,7 @@ func TestTrustSignerRemoveErrors(t *testing.T) { func TestRemoveSingleSigner(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedNotaryRepository) + cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository) err := removeSingleSigner(cli, "signed-repo", "test", true) assert.Error(t, err, "No signer test for repository signed-repo") err = removeSingleSigner(cli, "signed-repo", "releases", true) @@ -78,7 +79,7 @@ func TestRemoveSingleSigner(t *testing.T) { func TestRemoveMultipleSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedNotaryRepository) + cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository) err := removeSigner(cli, signerRemoveOptions{signer: "test", repos: []string{"signed-repo", "signed-repo"}, forceYes: true}) assert.Error(t, err, "Error removing signer from: signed-repo, signed-repo") assert.Check(t, is.Contains(cli.ErrBuffer().String(), @@ -87,7 +88,7 @@ func TestRemoveMultipleSigners(t *testing.T) { } func TestRemoveLastSignerWarning(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedNotaryRepository) + cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository) err := removeSigner(cli, signerRemoveOptions{signer: "alice", repos: []string{"signed-repo"}, forceYes: false}) assert.NilError(t, err) diff --git a/cli/command/trust/view_test.go b/cli/command/trust/view_test.go index 535ce4fd55..73dfec6215 100644 --- a/cli/command/trust/view_test.go +++ b/cli/command/trust/view_test.go @@ -7,6 +7,7 @@ import ( "github.com/docker/cli/cli/trust" "github.com/docker/cli/internal/test" + notaryfake "github.com/docker/cli/internal/test/notary" dockerClient "github.com/docker/docker/client" "github.com/gotestyourself/gotestyourself/assert" is "github.com/gotestyourself/gotestyourself/assert/cmp" @@ -57,14 +58,14 @@ func TestTrustViewCommandErrors(t *testing.T) { func TestTrustViewCommandOfflineErrors(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository) cmd := newViewCommand(cli) cmd.SetArgs([]string{"nonexistent-reg-name.io/image"}) cmd.SetOutput(ioutil.Discard) assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access nonexistent-reg-name.io/image") cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getOfflineNotaryRepository) + cli.SetNotaryClient(notaryfake.GetOfflineNotaryRepository) cmd = newViewCommand(cli) cmd.SetArgs([]string{"nonexistent-reg-name.io/image:tag"}) cmd.SetOutput(ioutil.Discard) @@ -73,14 +74,14 @@ func TestTrustViewCommandOfflineErrors(t *testing.T) { func TestTrustViewCommandUninitializedErrors(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getUninitializedNotaryRepository) + cli.SetNotaryClient(notaryfake.GetUninitializedNotaryRepository) cmd := newViewCommand(cli) cmd.SetArgs([]string{"reg/unsigned-img"}) cmd.SetOutput(ioutil.Discard) assert.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img") cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getUninitializedNotaryRepository) + cli.SetNotaryClient(notaryfake.GetUninitializedNotaryRepository) cmd = newViewCommand(cli) cmd.SetArgs([]string{"reg/unsigned-img:tag"}) cmd.SetOutput(ioutil.Discard) @@ -89,7 +90,7 @@ func TestTrustViewCommandUninitializedErrors(t *testing.T) { func TestTrustViewCommandEmptyNotaryRepoErrors(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository) cmd := newViewCommand(cli) cmd.SetArgs([]string{"reg/img:unsigned-tag"}) cmd.SetOutput(ioutil.Discard) @@ -98,7 +99,7 @@ func TestTrustViewCommandEmptyNotaryRepoErrors(t *testing.T) { assert.Check(t, is.Contains(cli.OutBuffer().String(), "Administrative keys for reg/img:")) cli = test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getEmptyTargetsNotaryRepository) + cli.SetNotaryClient(notaryfake.GetEmptyTargetsNotaryRepository) cmd = newViewCommand(cli) cmd.SetArgs([]string{"reg/img"}) cmd.SetOutput(ioutil.Discard) @@ -109,7 +110,7 @@ func TestTrustViewCommandEmptyNotaryRepoErrors(t *testing.T) { func TestTrustViewCommandFullRepoWithoutSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) + cli.SetNotaryClient(notaryfake.GetLoadedWithNoSignersNotaryRepository) cmd := newViewCommand(cli) cmd.SetArgs([]string{"signed-repo"}) assert.NilError(t, cmd.Execute()) @@ -119,7 +120,7 @@ func TestTrustViewCommandFullRepoWithoutSigners(t *testing.T) { func TestTrustViewCommandOneTagWithoutSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedWithNoSignersNotaryRepository) + cli.SetNotaryClient(notaryfake.GetLoadedWithNoSignersNotaryRepository) cmd := newViewCommand(cli) cmd.SetArgs([]string{"signed-repo:green"}) assert.NilError(t, cmd.Execute()) @@ -129,7 +130,7 @@ func TestTrustViewCommandOneTagWithoutSigners(t *testing.T) { func TestTrustViewCommandFullRepoWithSigners(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedNotaryRepository) + cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository) cmd := newViewCommand(cli) cmd.SetArgs([]string{"signed-repo"}) assert.NilError(t, cmd.Execute()) @@ -139,7 +140,7 @@ func TestTrustViewCommandFullRepoWithSigners(t *testing.T) { func TestTrustViewCommandUnsignedTagInSignedRepo(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) - cli.SetNotaryClient(getLoadedNotaryRepository) + cli.SetNotaryClient(notaryfake.GetLoadedNotaryRepository) cmd := newViewCommand(cli) cmd.SetArgs([]string{"signed-repo:unsigned"}) assert.NilError(t, cmd.Execute()) diff --git a/cmd/docker/docker.go b/cmd/docker/docker.go index 18d7698650..57c52f381b 100644 --- a/cmd/docker/docker.go +++ b/cmd/docker/docker.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "strconv" "strings" "github.com/docker/cli/cli" @@ -155,7 +156,7 @@ func main() { stdin, stdout, stderr := term.StdStreams() logrus.SetOutput(stderr) - dockerCli := command.NewDockerCli(stdin, stdout, stderr) + dockerCli := command.NewDockerCli(stdin, stdout, stderr, isContentTrustEnabled()) cmd := newDockerCommand(dockerCli) if err := cmd.Execute(); err != nil { @@ -175,6 +176,16 @@ func main() { } } +func isContentTrustEnabled() bool { + if e := os.Getenv("DOCKER_CONTENT_TRUST"); e != "" { + if t, err := strconv.ParseBool(e); t || err != nil { + // treat any other value as true + return true + } + } + return false +} + func showVersion() { fmt.Printf("Docker version %s, build %s\n", cli.Version, cli.GitCommit) } diff --git a/cmd/docker/docker_test.go b/cmd/docker/docker_test.go index 22a157a8be..1bf30bf7cb 100644 --- a/cmd/docker/docker_test.go +++ b/cmd/docker/docker_test.go @@ -26,7 +26,7 @@ func TestClientDebugEnabled(t *testing.T) { func TestExitStatusForInvalidSubcommandWithHelpFlag(t *testing.T) { discard := ioutil.Discard - cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard)) + cmd := newDockerCommand(command.NewDockerCli(os.Stdin, discard, discard, false)) cmd.SetArgs([]string{"help", "invalid"}) err := cmd.Execute() assert.Error(t, err, "unknown help topic: invalid") diff --git a/docs/yaml/generate.go b/docs/yaml/generate.go index fd227eb6e9..3603c97c4a 100644 --- a/docs/yaml/generate.go +++ b/docs/yaml/generate.go @@ -19,7 +19,7 @@ const descriptionSourcePath = "docs/reference/commandline/" func generateCliYaml(opts *options) error { stdin, stdout, stderr := term.StdStreams() - dockerCli := command.NewDockerCli(stdin, stdout, stderr) + dockerCli := command.NewDockerCli(stdin, stdout, stderr, false) cmd := &cobra.Command{Use: "docker"} commands.AddCommands(cmd, dockerCli) source := filepath.Join(opts.source, descriptionSourcePath) diff --git a/internal/test/cli.go b/internal/test/cli.go index cac803712c..352a0cb7df 100644 --- a/internal/test/cli.go +++ b/internal/test/cli.go @@ -16,7 +16,8 @@ import ( notaryclient "github.com/theupdateframework/notary/client" ) -type notaryClientFuncType func(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) +// NotaryClientFuncType defines a function that returns a fake notary client +type NotaryClientFuncType func(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (notaryclient.Repository, error) type clientInfoFuncType func() command.ClientInfo // FakeCli emulates the default DockerCli @@ -30,16 +31,17 @@ type FakeCli struct { in *command.InStream server command.ServerInfo clientInfoFunc clientInfoFuncType - notaryClientFunc notaryClientFuncType + notaryClientFunc NotaryClientFuncType manifestStore manifeststore.Store registryClient registryclient.RegistryClient + isTrusted bool } // NewFakeCli returns a fake for the command.Cli interface -func NewFakeCli(client client.APIClient) *FakeCli { +func NewFakeCli(client client.APIClient, opts ...func(*FakeCli)) *FakeCli { outBuffer := new(bytes.Buffer) errBuffer := new(bytes.Buffer) - return &FakeCli{ + c := &FakeCli{ client: client, out: command.NewOutStream(outBuffer), outBuffer: outBuffer, @@ -49,6 +51,10 @@ func NewFakeCli(client client.APIClient) *FakeCli { // Set cli.ConfigFile().Filename to a tempfile to support Save. configfile: configfile.New(""), } + for _, opt := range opts { + opt(c) + } + return c } // SetIn sets the input of the cli to the specified ReadCloser @@ -120,7 +126,7 @@ func (c *FakeCli) ErrBuffer() *bytes.Buffer { } // SetNotaryClient sets the internal getter for retrieving a NotaryClient -func (c *FakeCli) SetNotaryClient(notaryClientFunc notaryClientFuncType) { +func (c *FakeCli) SetNotaryClient(notaryClientFunc NotaryClientFuncType) { c.notaryClientFunc = notaryClientFunc } @@ -151,3 +157,13 @@ func (c *FakeCli) SetManifestStore(store manifeststore.Store) { func (c *FakeCli) SetRegistryClient(client registryclient.RegistryClient) { c.registryClient = client } + +// IsTrusted on the fake cli +func (c *FakeCli) IsTrusted() bool { + return c.isTrusted +} + +// IsTrusted sets "enables" content trust on the fake cli +func IsTrusted(c *FakeCli) { + c.isTrusted = true +} diff --git a/cli/command/trust/client_test.go b/internal/test/notary/client.go similarity index 68% rename from cli/command/trust/client_test.go rename to internal/test/notary/client.go index 726dd89761..efaf8a7894 100644 --- a/cli/command/trust/client_test.go +++ b/internal/test/notary/client.go @@ -1,4 +1,4 @@ -package trust +package notary import ( "github.com/docker/cli/cli/trust" @@ -12,107 +12,142 @@ import ( "github.com/theupdateframework/notary/tuf/signed" ) -// Sample mock CLI interfaces - -func getOfflineNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { +// GetOfflineNotaryRepository returns a OfflineNotaryRepository +func GetOfflineNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { return OfflineNotaryRepository{}, nil } // OfflineNotaryRepository is a mock Notary repository that is offline type OfflineNotaryRepository struct{} +// Initialize creates a new repository by using rootKey as the root Key for the +// TUF repository. func (o OfflineNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { return storage.ErrOffline{} } +// InitializeWithCertificate initializes the repository with root keys and their corresponding certificates func (o OfflineNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { return storage.ErrOffline{} } + +// Publish pushes the local changes in signed material to the remote notary-server +// Conceptually it performs an operation similar to a `git rebase` func (o OfflineNotaryRepository) Publish() error { return storage.ErrOffline{} } +// AddTarget creates new changelist entries to add a target to the given roles +// in the repository when the changelist gets applied at publish time. func (o OfflineNotaryRepository) AddTarget(target *client.Target, roles ...data.RoleName) error { return nil } + +// RemoveTarget creates new changelist entries to remove a target from the given +// roles in the repository when the changelist gets applied at publish time. func (o OfflineNotaryRepository) RemoveTarget(targetName string, roles ...data.RoleName) error { return nil } + +// ListTargets lists all targets for the current repository. The list of +// roles should be passed in order from highest to lowest priority. func (o OfflineNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { return nil, storage.ErrOffline{} } +// GetTargetByName returns a target by the given name. func (o OfflineNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { return nil, storage.ErrOffline{} } +// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all +// roles, and returns a list of TargetSignedStructs for each time it finds the specified target. func (o OfflineNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { return nil, storage.ErrOffline{} } +// GetChangelist returns the list of the repository's unpublished changes func (o OfflineNotaryRepository) GetChangelist() (changelist.Changelist, error) { return changelist.NewMemChangelist(), nil } +// ListRoles returns a list of RoleWithSignatures objects for this repo func (o OfflineNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { return nil, storage.ErrOffline{} } +// GetDelegationRoles returns the keys and roles of the repository's delegations func (o OfflineNotaryRepository) GetDelegationRoles() ([]data.Role, error) { return nil, storage.ErrOffline{} } +// AddDelegation creates changelist entries to add provided delegation public keys and paths. func (o OfflineNotaryRepository) AddDelegation(name data.RoleName, delegationKeys []data.PublicKey, paths []string) error { return nil } +// AddDelegationRoleAndKeys creates a changelist entry to add provided delegation public keys. func (o OfflineNotaryRepository) AddDelegationRoleAndKeys(name data.RoleName, delegationKeys []data.PublicKey) error { return nil } +// AddDelegationPaths creates a changelist entry to add provided paths to an existing delegation. func (o OfflineNotaryRepository) AddDelegationPaths(name data.RoleName, paths []string) error { return nil } +// RemoveDelegationKeysAndPaths creates changelist entries to remove provided delegation key IDs and paths. func (o OfflineNotaryRepository) RemoveDelegationKeysAndPaths(name data.RoleName, keyIDs, paths []string) error { return nil } +// RemoveDelegationRole creates a changelist to remove all paths and keys from a role, and delete the role in its entirety. func (o OfflineNotaryRepository) RemoveDelegationRole(name data.RoleName) error { return nil } +// RemoveDelegationPaths creates a changelist entry to remove provided paths from an existing delegation. func (o OfflineNotaryRepository) RemoveDelegationPaths(name data.RoleName, paths []string) error { return nil } +// RemoveDelegationKeys creates a changelist entry to remove provided keys from an existing delegation. func (o OfflineNotaryRepository) RemoveDelegationKeys(name data.RoleName, keyIDs []string) error { return nil } +// ClearDelegationPaths creates a changelist entry to remove all paths from an existing delegation. func (o OfflineNotaryRepository) ClearDelegationPaths(name data.RoleName) error { return nil } +// Witness creates change objects to witness (i.e. re-sign) the given +// roles on the next publish. One change is created per role func (o OfflineNotaryRepository) Witness(roles ...data.RoleName) ([]data.RoleName, error) { return nil, nil } +// RotateKey rotates a private key and returns the public component from the remote server func (o OfflineNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { return storage.ErrOffline{} } +// GetCryptoService is the getter for the repository's CryptoService func (o OfflineNotaryRepository) GetCryptoService() signed.CryptoService { return nil } +// SetLegacyVersions allows the number of legacy versions of the root +// to be inspected for old signing keys to be configured. func (o OfflineNotaryRepository) SetLegacyVersions(version int) {} +// GetGUN is a getter for the GUN object from a Repository func (o OfflineNotaryRepository) GetGUN() data.GUN { return data.GUN("gun") } -func getUninitializedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { +// GetUninitializedNotaryRepository returns an UninitializedNotaryRepository +func GetUninitializedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { return UninitializedNotaryRepository{}, nil } @@ -123,42 +158,57 @@ type UninitializedNotaryRepository struct { OfflineNotaryRepository } +// Initialize creates a new repository by using rootKey as the root Key for the +// TUF repository. func (u UninitializedNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { return client.ErrRepositoryNotExist{} } +// InitializeWithCertificate initializes the repository with root keys and their corresponding certificates func (u UninitializedNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { return client.ErrRepositoryNotExist{} } + +// Publish pushes the local changes in signed material to the remote notary-server +// Conceptually it performs an operation similar to a `git rebase` func (u UninitializedNotaryRepository) Publish() error { return client.ErrRepositoryNotExist{} } +// ListTargets lists all targets for the current repository. The list of +// roles should be passed in order from highest to lowest priority. func (u UninitializedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { return nil, client.ErrRepositoryNotExist{} } +// GetTargetByName returns a target by the given name. func (u UninitializedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { return nil, client.ErrRepositoryNotExist{} } +// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all +// roles, and returns a list of TargetSignedStructs for each time it finds the specified target. func (u UninitializedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { return nil, client.ErrRepositoryNotExist{} } +// ListRoles returns a list of RoleWithSignatures objects for this repo func (u UninitializedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { return nil, client.ErrRepositoryNotExist{} } +// GetDelegationRoles returns the keys and roles of the repository's delegations func (u UninitializedNotaryRepository) GetDelegationRoles() ([]data.Role, error) { return nil, client.ErrRepositoryNotExist{} } +// RotateKey rotates a private key and returns the public component from the remote server func (u UninitializedNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { return client.ErrRepositoryNotExist{} } -func getEmptyTargetsNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { +// GetEmptyTargetsNotaryRepository returns an EmptyTargetsNotaryRepository +func GetEmptyTargetsNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { return EmptyTargetsNotaryRepository{}, nil } @@ -168,29 +218,41 @@ type EmptyTargetsNotaryRepository struct { OfflineNotaryRepository } +// Initialize creates a new repository by using rootKey as the root Key for the +// TUF repository. func (e EmptyTargetsNotaryRepository) Initialize(rootKeyIDs []string, serverManagedRoles ...data.RoleName) error { return nil } +// InitializeWithCertificate initializes the repository with root keys and their corresponding certificates func (e EmptyTargetsNotaryRepository) InitializeWithCertificate(rootKeyIDs []string, rootCerts []data.PublicKey, serverManagedRoles ...data.RoleName) error { return nil } + +// Publish pushes the local changes in signed material to the remote notary-server +// Conceptually it performs an operation similar to a `git rebase` func (e EmptyTargetsNotaryRepository) Publish() error { return nil } +// ListTargets lists all targets for the current repository. The list of +// roles should be passed in order from highest to lowest priority. func (e EmptyTargetsNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { return []*client.TargetWithRole{}, nil } +// GetTargetByName returns a target by the given name. func (e EmptyTargetsNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { return nil, client.ErrNoSuchTarget(name) } +// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all +// roles, and returns a list of TargetSignedStructs for each time it finds the specified target. func (e EmptyTargetsNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { return nil, client.ErrNoSuchTarget(name) } +// ListRoles returns a list of RoleWithSignatures objects for this repo func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { rootRole := data.Role{ RootRole: data.RootRole{ @@ -212,15 +274,18 @@ func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, {Role: targetsRole}}, nil } +// GetDelegationRoles returns the keys and roles of the repository's delegations func (e EmptyTargetsNotaryRepository) GetDelegationRoles() ([]data.Role, error) { return []data.Role{}, nil } +// RotateKey rotates a private key and returns the public component from the remote server func (e EmptyTargetsNotaryRepository) RotateKey(role data.RoleName, serverManagesKey bool, keyList []string) error { return nil } -func getLoadedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { +// GetLoadedNotaryRepository returns a LoadedNotaryRepository +func GetLoadedNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { return LoadedNotaryRepository{}, nil } @@ -316,6 +381,7 @@ var loadedTargets = []client.TargetSignedStruct{ {Target: loadedGreenTarget, Role: loadedReleasesRole}, } +// ListRoles returns a list of RoleWithSignatures objects for this repo func (l LoadedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { rootRole := data.Role{ RootRole: data.RootRole{ @@ -368,6 +434,8 @@ func (l LoadedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) }, nil } +// ListTargets lists all targets for the current repository. The list of +// roles should be passed in order from highest to lowest priority. func (l LoadedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { filteredTargets := []*client.TargetWithRole{} for _, tgt := range loadedTargets { @@ -378,6 +446,7 @@ func (l LoadedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.T return filteredTargets, nil } +// GetTargetByName returns a target by the given name. func (l LoadedNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { for _, tgt := range loadedTargets { if name == tgt.Target.Name { @@ -389,6 +458,8 @@ func (l LoadedNotaryRepository) GetTargetByName(name string, roles ...data.RoleN return nil, client.ErrNoSuchTarget(name) } +// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all +// roles, and returns a list of TargetSignedStructs for each time it finds the specified target. func (l LoadedNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { if name == "" { return loadedTargets, nil @@ -405,14 +476,17 @@ func (l LoadedNotaryRepository) GetAllTargetMetadataByName(name string) ([]clien return filteredTargets, nil } +// GetGUN is a getter for the GUN object from a Repository func (l LoadedNotaryRepository) GetGUN() data.GUN { return data.GUN("signed-repo") } +// GetDelegationRoles returns the keys and roles of the repository's delegations func (l LoadedNotaryRepository) GetDelegationRoles() ([]data.Role, error) { return loadedDelegationRoles, nil } +// GetCryptoService is the getter for the repository's CryptoService func (l LoadedNotaryRepository) GetCryptoService() signed.CryptoService { if l.statefulCryptoService == nil { // give it an in-memory cryptoservice with a root key and targets key @@ -423,7 +497,8 @@ func (l LoadedNotaryRepository) GetCryptoService() signed.CryptoService { return l.statefulCryptoService } -func getLoadedWithNoSignersNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { +// GetLoadedWithNoSignersNotaryRepository returns a LoadedWithNoSignersNotaryRepository +func GetLoadedWithNoSignersNotaryRepository(imgRefAndAuth trust.ImageRefAndAuth, actions []string) (client.Repository, error) { return LoadedWithNoSignersNotaryRepository{}, nil } @@ -433,6 +508,8 @@ type LoadedWithNoSignersNotaryRepository struct { LoadedNotaryRepository } +// ListTargets lists all targets for the current repository. The list of +// roles should be passed in order from highest to lowest priority. func (l LoadedWithNoSignersNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { filteredTargets := []*client.TargetWithRole{} for _, tgt := range loadedTargets { @@ -443,6 +520,7 @@ func (l LoadedWithNoSignersNotaryRepository) ListTargets(roles ...data.RoleName) return filteredTargets, nil } +// GetTargetByName returns a target by the given name. func (l LoadedWithNoSignersNotaryRepository) GetTargetByName(name string, roles ...data.RoleName) (*client.TargetWithRole, error) { if name == "" || name == loadedGreenTarget.Name { return &client.TargetWithRole{Target: loadedGreenTarget, Role: data.CanonicalTargetsRole}, nil @@ -450,6 +528,8 @@ func (l LoadedWithNoSignersNotaryRepository) GetTargetByName(name string, roles return nil, client.ErrNoSuchTarget(name) } +// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all +// roles, and returns a list of TargetSignedStructs for each time it finds the specified target. func (l LoadedWithNoSignersNotaryRepository) GetAllTargetMetadataByName(name string) ([]client.TargetSignedStruct, error) { if name == "" || name == loadedGreenTarget.Name { return []client.TargetSignedStruct{{Target: loadedGreenTarget, Role: loadedTargetsRole}}, nil @@ -457,6 +537,7 @@ func (l LoadedWithNoSignersNotaryRepository) GetAllTargetMetadataByName(name str return nil, client.ErrNoSuchTarget(name) } +// GetDelegationRoles returns the keys and roles of the repository's delegations func (l LoadedWithNoSignersNotaryRepository) GetDelegationRoles() ([]data.Role, error) { return []data.Role{}, nil } diff --git a/man/generate.go b/man/generate.go index 605e601ffa..4197558a22 100644 --- a/man/generate.go +++ b/man/generate.go @@ -25,7 +25,7 @@ func generateManPages(opts *options) error { } stdin, stdout, stderr := term.StdStreams() - dockerCli := command.NewDockerCli(stdin, stdout, stderr) + dockerCli := command.NewDockerCli(stdin, stdout, stderr, false) cmd := &cobra.Command{Use: "docker"} commands.AddCommands(cmd, dockerCli) source := filepath.Join(opts.source, descriptionSourcePath) From feae0e9756db152712473d61245546767b6a5282 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Thu, 8 Mar 2018 14:56:56 -0500 Subject: [PATCH 2/2] Only read trust setting from options Rename IsTrusted to ContentTrustEnabled Signed-off-by: Daniel Nephin --- cli/command/cli.go | 27 ++++++++++++++------------- cli/command/container/create.go | 6 ++---- cli/command/container/create_test.go | 5 +++-- cli/command/container/run.go | 2 +- cli/command/image/build.go | 22 ++++++++++------------ cli/command/image/build_test.go | 1 + cli/command/image/pull.go | 4 ++-- cli/command/image/pull_test.go | 2 +- cli/command/image/push.go | 4 ++-- cli/command/plugin/install.go | 4 ++-- cli/command/plugin/push.go | 4 ++-- cli/command/service/trust.go | 2 +- internal/test/cli.go | 14 +++++++------- 13 files changed, 48 insertions(+), 49 deletions(-) diff --git a/cli/command/cli.go b/cli/command/cli.go index 2fc1e7cccb..68ac0e6c91 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -52,20 +52,20 @@ type Cli interface { DefaultVersion() string ManifestStore() manifeststore.Store RegistryClient(bool) registryclient.RegistryClient - IsTrusted() bool + ContentTrustEnabled() bool } // DockerCli is an instance the docker command line client. // Instances of the client can be returned from NewDockerCli. type DockerCli struct { - configFile *configfile.ConfigFile - in *InStream - out *OutStream - err io.Writer - client client.APIClient - serverInfo ServerInfo - clientInfo ClientInfo - isTrusted bool + configFile *configfile.ConfigFile + in *InStream + out *OutStream + err io.Writer + client client.APIClient + serverInfo ServerInfo + clientInfo ClientInfo + contentTrust bool } // DefaultVersion returns api.defaultVersion or DOCKER_API_VERSION if specified. @@ -123,9 +123,10 @@ func (cli *DockerCli) ClientInfo() ClientInfo { return cli.clientInfo } -// IsTrusted returns if content trust is enabled for the cli -func (cli *DockerCli) IsTrusted() bool { - return cli.isTrusted +// ContentTrustEnabled returns if content trust has been enabled by an +// environment variable. +func (cli *DockerCli) ContentTrustEnabled() bool { + return cli.contentTrust } // ManifestStore returns a store for local manifests @@ -245,7 +246,7 @@ func (c ClientInfo) HasKubernetes() bool { // NewDockerCli returns a DockerCli instance with IO output and error streams set by in, out and err. func NewDockerCli(in io.ReadCloser, out, err io.Writer, isTrusted bool) *DockerCli { - return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err, isTrusted: isTrusted} + return &DockerCli{in: NewInStream(in), out: NewOutStream(out), err: err, contentTrust: isTrusted} } // NewAPIClientFromFlags creates a new APIClient from command line flags diff --git a/cli/command/container/create.go b/cli/command/container/create.go index 1358372ac9..7fe4c8f260 100644 --- a/cli/command/container/create.go +++ b/cli/command/container/create.go @@ -54,7 +54,7 @@ func NewCreateCommand(dockerCli command.Cli) *cobra.Command { flags.Bool("help", false, "Print usage") command.AddPlatformFlag(flags, &opts.platform) - command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) + command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) copts = addFlags(flags) return cmd } @@ -159,7 +159,6 @@ func newCIDFile(path string) (*cidFile, error) { return &cidFile{path: path, file: f}, nil } -// nolint: gocyclo func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig *containerConfig, opts *createOptions) (*container.ContainerCreateCreatedBody, error) { config := containerConfig.Config hostConfig := containerConfig.HostConfig @@ -184,8 +183,7 @@ func createContainer(ctx context.Context, dockerCli command.Cli, containerConfig if named, ok := ref.(reference.Named); ok { namedRef = reference.TagNameOnly(named) - isContentTrustEnabled := !opts.untrusted && dockerCli.IsTrusted() - if taggedRef, ok := namedRef.(reference.NamedTagged); ok && isContentTrustEnabled { + if taggedRef, ok := namedRef.(reference.NamedTagged); ok && !opts.untrusted { var err error trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil) if err != nil { diff --git a/cli/command/container/create_test.go b/cli/command/container/create_test.go index 1df75b50bc..b786b1f0cc 100644 --- a/cli/command/container/create_test.go +++ b/cli/command/container/create_test.go @@ -108,8 +108,9 @@ func TestCreateContainerPullsImageIfMissing(t *testing.T) { HostConfig: &container.HostConfig{}, } body, err := createContainer(context.Background(), cli, config, &createOptions{ - name: "name", - platform: runtime.GOOS, + name: "name", + platform: runtime.GOOS, + untrusted: true, }) assert.NilError(t, err) expected := container.ContainerCreateCreatedBody{ID: containerID} diff --git a/cli/command/container/run.go b/cli/command/container/run.go index 1a1a98515e..10b7ab8d9a 100644 --- a/cli/command/container/run.go +++ b/cli/command/container/run.go @@ -63,7 +63,7 @@ func NewRunCommand(dockerCli command.Cli) *cobra.Command { flags.Bool("help", false, "Print usage") command.AddPlatformFlag(flags, &opts.platform) - command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) + command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) copts = addFlags(flags) return cmd } diff --git a/cli/command/image/build.go b/cli/command/image/build.go index c812009970..15da4d4318 100644 --- a/cli/command/image/build.go +++ b/cli/command/image/build.go @@ -138,7 +138,7 @@ func NewBuildCommand(dockerCli command.Cli) *cobra.Command { flags.StringVar(&options.target, "target", "", "Set the target build stage to build.") flags.StringVar(&options.imageIDFile, "iidfile", "", "Write the image ID to the file") - command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.IsTrusted()) + command.AddTrustVerificationFlags(flags, &options.untrusted, dockerCli.ContentTrustEnabled()) command.AddPlatformFlag(flags, &options.platform) flags.BoolVar(&options.squash, "squash", false, "Squash newly built layers into a single new layer") @@ -286,8 +286,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error { defer cancel() var resolvedTags []*resolvedTag - isContentTrustEnabled := !options.untrusted && dockerCli.IsTrusted() - if isContentTrustEnabled { + if !options.untrusted { translator := func(ctx context.Context, ref reference.NamedTagged) (reference.Canonical, error) { return TrustedReference(ctx, dockerCli, ref, nil) } @@ -295,10 +294,10 @@ func runBuild(dockerCli command.Cli, options buildOptions) error { if buildCtx != nil { // Wrap the tar archive to replace the Dockerfile entry with the rewritten // Dockerfile which uses trusted pulls. - buildCtx = replaceDockerfileTarWrapper(ctx, buildCtx, relDockerfile, translator, &resolvedTags, isContentTrustEnabled) + buildCtx = replaceDockerfileForContentTrust(ctx, buildCtx, relDockerfile, translator, &resolvedTags) } else if dockerfileCtx != nil { // if there was not archive context still do the possible replacements in Dockerfile - newDockerfile, _, err := rewriteDockerfileFrom(ctx, dockerfileCtx, translator, isContentTrustEnabled) + newDockerfile, _, err := rewriteDockerfileFrom(ctx, dockerfileCtx, translator, !options.untrusted) if err != nil { return err } @@ -462,7 +461,7 @@ func runBuild(dockerCli command.Cli, options buildOptions) error { return err } } - if !options.untrusted && dockerCli.IsTrusted() { + if !options.untrusted { // Since the build was successful, now we must tag any of the resolved // images from the above Dockerfile rewrite. for _, resolved := range resolvedTags { @@ -545,11 +544,10 @@ func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator return buf.Bytes(), resolvedTags, scanner.Err() } -// replaceDockerfileTarWrapper wraps the given input tar archive stream and -// replaces the entry with the given Dockerfile name with the contents of the -// new Dockerfile. Returns a new tar archive stream with the replaced -// Dockerfile. -func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag, istrusted bool) io.ReadCloser { +// replaceDockerfileForContentTrust wraps the given input tar archive stream and +// uses the translator to replace the Dockerfile which uses a trusted reference. +// Returns a new tar archive stream with the replaced Dockerfile. +func replaceDockerfileForContentTrust(ctx context.Context, inputTarStream io.ReadCloser, dockerfileName string, translator translatorFunc, resolvedTags *[]*resolvedTag) io.ReadCloser { pipeReader, pipeWriter := io.Pipe() go func() { tarReader := tar.NewReader(inputTarStream) @@ -576,7 +574,7 @@ func replaceDockerfileTarWrapper(ctx context.Context, inputTarStream io.ReadClos // generated from a directory on the local filesystem, the // Dockerfile will only appear once in the archive. var newDockerfile []byte - newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator, istrusted) + newDockerfile, *resolvedTags, err = rewriteDockerfileFrom(ctx, content, translator, true) if err != nil { pipeWriter.CloseWithError(err) return diff --git a/cli/command/image/build_test.go b/cli/command/image/build_test.go index 83a3b61610..fb370f99f5 100644 --- a/cli/command/image/build_test.go +++ b/cli/command/image/build_test.go @@ -107,6 +107,7 @@ COPY data /data options := newBuildOptions() options.context = dir.Path() options.dockerfileName = df.Path() + options.untrusted = true err = runBuild(cli, options) assert.NilError(t, err) diff --git a/cli/command/image/pull.go b/cli/command/image/pull.go index e0723151a6..4f6d4f830d 100644 --- a/cli/command/image/pull.go +++ b/cli/command/image/pull.go @@ -39,7 +39,7 @@ func NewPullCommand(dockerCli command.Cli) *cobra.Command { flags.BoolVarP(&opts.all, "all-tags", "a", false, "Download all tagged images in the repository") command.AddPlatformFlag(flags, &opts.platform) - command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) + command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) return cmd } @@ -66,7 +66,7 @@ func runPull(cli command.Cli, opts pullOptions) error { // Check if reference has a digest _, isCanonical := distributionRef.(reference.Canonical) - if !opts.untrusted && cli.IsTrusted() && !isCanonical { + if !opts.untrusted && !isCanonical { err = trustedPull(ctx, cli, imgRefAndAuth, opts.platform) } else { err = imagePullPrivileged(ctx, cli, imgRefAndAuth, opts.all, opts.platform) diff --git a/cli/command/image/pull_test.go b/cli/command/image/pull_test.go index 551a296479..6ccbb91a05 100644 --- a/cli/command/image/pull_test.go +++ b/cli/command/image/pull_test.go @@ -110,7 +110,7 @@ func TestNewPullCommandWithContentTrustErrors(t *testing.T) { imagePullFunc: func(ref string, options types.ImagePullOptions) (io.ReadCloser, error) { return ioutil.NopCloser(strings.NewReader("")), fmt.Errorf("shouldn't try to pull image") }, - }, test.IsTrusted) + }, test.EnableContentTrust) cli.SetNotaryClient(tc.notaryFunc) cmd := NewPullCommand(cli) cmd.SetOutput(ioutil.Discard) diff --git a/cli/command/image/push.go b/cli/command/image/push.go index 2760e047a1..61e1b09d62 100644 --- a/cli/command/image/push.go +++ b/cli/command/image/push.go @@ -32,7 +32,7 @@ func NewPushCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() - command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) + command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) return cmd } @@ -55,7 +55,7 @@ func runPush(dockerCli command.Cli, opts pushOptions) error { authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index) requestPrivilege := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "push") - if !opts.untrusted && dockerCli.IsTrusted() { + if !opts.untrusted { return TrustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege) } diff --git a/cli/command/plugin/install.go b/cli/command/plugin/install.go index 20119edef3..40be773bc3 100644 --- a/cli/command/plugin/install.go +++ b/cli/command/plugin/install.go @@ -29,7 +29,7 @@ type pluginOptions struct { func loadPullFlags(dockerCli command.Cli, opts *pluginOptions, flags *pflag.FlagSet) { flags.BoolVar(&opts.grantPerms, "grant-all-permissions", false, "Grant all permissions necessary to run the plugin") - command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) + command.AddTrustVerificationFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) } func newInstallCommand(dockerCli command.Cli) *cobra.Command { @@ -91,7 +91,7 @@ func buildPullConfig(ctx context.Context, dockerCli command.Cli, opts pluginOpti remote := ref.String() _, isCanonical := ref.(reference.Canonical) - if !opts.untrusted && dockerCli.IsTrusted() && !isCanonical { + if !opts.untrusted && !isCanonical { ref = reference.TagNameOnly(ref) nt, ok := ref.(reference.NamedTagged) if !ok { diff --git a/cli/command/plugin/push.go b/cli/command/plugin/push.go index 8e6872fd30..5dd039f8bf 100644 --- a/cli/command/plugin/push.go +++ b/cli/command/plugin/push.go @@ -32,7 +32,7 @@ func newPushCommand(dockerCli command.Cli) *cobra.Command { flags := cmd.Flags() - command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.IsTrusted()) + command.AddTrustSigningFlags(flags, &opts.untrusted, dockerCli.ContentTrustEnabled()) return cmd } @@ -67,7 +67,7 @@ func runPush(dockerCli command.Cli, opts pushOptions) error { } defer responseBody.Close() - if !opts.untrusted && dockerCli.IsTrusted() { + if !opts.untrusted { repoInfo.Class = "plugin" return image.PushTrustedReference(dockerCli, repoInfo, named, authConfig, responseBody) } diff --git a/cli/command/service/trust.go b/cli/command/service/trust.go index c82d718730..c304a99124 100644 --- a/cli/command/service/trust.go +++ b/cli/command/service/trust.go @@ -16,7 +16,7 @@ import ( ) func resolveServiceImageDigestContentTrust(dockerCli command.Cli, service *swarm.ServiceSpec) error { - if !dockerCli.IsTrusted() { + if !dockerCli.ContentTrustEnabled() { // When not using content trust, digest resolution happens later when // contacting the registry to retrieve image information. return nil diff --git a/internal/test/cli.go b/internal/test/cli.go index 352a0cb7df..17fab645ea 100644 --- a/internal/test/cli.go +++ b/internal/test/cli.go @@ -34,7 +34,7 @@ type FakeCli struct { notaryClientFunc NotaryClientFuncType manifestStore manifeststore.Store registryClient registryclient.RegistryClient - isTrusted bool + contentTrust bool } // NewFakeCli returns a fake for the command.Cli interface @@ -158,12 +158,12 @@ func (c *FakeCli) SetRegistryClient(client registryclient.RegistryClient) { c.registryClient = client } -// IsTrusted on the fake cli -func (c *FakeCli) IsTrusted() bool { - return c.isTrusted +// ContentTrustEnabled on the fake cli +func (c *FakeCli) ContentTrustEnabled() bool { + return c.contentTrust } -// IsTrusted sets "enables" content trust on the fake cli -func IsTrusted(c *FakeCli) { - c.isTrusted = true +// EnableContentTrust on the fake cli +func EnableContentTrust(c *FakeCli) { + c.contentTrust = true }