Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docker trust: view, revoke, sign subcommands (experimental) #472

Merged
merged 20 commits into from
Sep 26, 2017

Conversation

riyazdf
Copy link

@riyazdf riyazdf commented Aug 24, 2017

This PR introduces the docker trust subcommand. The command has been fleshed out in detail in this design document. docker trust provides a direct notary signing experience beyond what is provided with the DOCKER_CONTENT_TRUST environment variable.

In this PR, we introduce three subcommands:

  • docker trust view: displays signatures on image tags, and who signed them
  • docker trust revoke: revokes signatures from signed image tag(s)
  • docker trust sign: adds a signature to an image tag, pushing the image if it doesn't already exist

cc @ashfall and @eiais who helped implement this feature

Copy link
Contributor

@mdlinville mdlinville left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several copyedit-style comments. In addition, scrub all occurrences of "like so", "please", and future-facing prose like "will sign" or "will create". In docs, we live in the eternal present and we don't say "please". :) Likewise, the phrase "note that".


## Description

Docker trust inspect provides detailed information on signed repositories.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mark this up: docker trust inspect. If you don't feel comfortable starting the sentence with lowercase mono text, then The docker trust inspect` command...."

## Description

Docker trust inspect provides detailed information on signed repositories.
This includes all image tags that are signed, who signed them, and who can sign
Copy link
Contributor

@mdlinville mdlinville Aug 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it show unsigned images? This kind of implies not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not display unsigned images.

This includes all image tags that are signed, who signed them, and who can sign
new tags.

By default, `docker trust inspect` will render results in a table.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/will render/renders


```bash
$ docker trust inspect alpine:latest
SIGNED TAG DIGEST SIGNERS
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a newline between input and output.

Root Key: a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce
```

Note that the `SIGNED TAG` maps to the image tag itself, and associates to given image `DIGEST`. `SIGNERS` lists all entities who have signed.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Note that the/The

"and associates to given image" this phrase isn't parsing right for me. Maybe a rewrite?

Successfully signed "docker.io/example/trust-demo":v1
```

`docker trust inspect` should now list the new signature:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/should now list/lists


## Initialize a new repo and sign a tag

When signing an image on a repo for the first time, `docker trust sign` sets up new keys and then signs the image.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/and then signs/before signing

@@ -78,18 +78,21 @@ to use `notary` with Docker images.

## Building Notary

Prerequisites:
Note that our [latest stable release](https://github.com/docker/notary/releases) is at the head of the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Note that our/Notary's

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vendored code - we can address this in docker/notary

Run `make binaries`, which creates the Notary Client CLI binary at `bin/notary`.
Note that `make binaries` assumes a standard Go directory structure, in which
Run `make client`, which creates the Notary Client CLI binary at `bin/notary`.
Note that `make client` assumes a standard Go directory structure, in which
Notary is checked out to the `src` directory in your `GOPATH`. For example:
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Newlines

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vendored code - we can address this in docker/notary

@@ -98,3 +101,5 @@ $GOPATH/
docker/
notary/
```

To build the server and signer, please run `docker-compose build`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/please//

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vendored code - we can address this in docker/notary

@riyazdf
Copy link
Author

riyazdf commented Aug 25, 2017

thank you @mstanleyjones for the review! Just updated with the fixes, and left a comment about one potential command output change.

Copy link
Contributor

@dnephin dnephin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool!

This is a big change so I looked over the first two commands for now.

Re: testing, I think the tests you have look great, but we need to fake the notary client so we're not actually making HTTP requests in unit tests.

An end-to-end test for each of the commands (inspect, revoke, sign) would be great as well. We'll want to add a notary service to compose-env.yaml so we're testing against an isolated service. I think that can be done in a separate follow up PR.

We just merged https://github.com/docker/cli/blob/master/TESTING.md which has a bit more information as well

// trustedPush handles content trust pushing of an image
func trustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
// TrustedPush handles content trust pushing of an image
func TrustedPush(ctx context.Context, cli command.Cli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have a package at cli/trust. Do you think it would make sense to move these functions (this one and the few below that are either already exported, or were changed to exported) into the cli/trust package, now that they are being shared between multiple commands?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we certainly can for the GetSignableRoles function and so I've moved over.

This one is a little more tricky because it calls imagePushPrivileged - can we export that function?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, I think it makes sense to export it. There's nothing in there that's really specific to the command routing. The only change I would make is to accept a client.ImageAPIClient instead of the full command.Cli

func NewTrustCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "trust",
Short: "Sign images to establish trust",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current convention for these commands is "Manage x" (see docker --help). So maybe something like: "Manage trust on Docker images" ?

}
if err = cl.Clear(""); err != nil {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor: this if is unnecessary,

return cl.Clear("")


const releasedRoleName = "Repo Admin"

func checkLocalImageExistence(ctx context.Context, cli command.Cli, imageName string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is only used in sign.go so would be more appropriate immediately following the function that calls it.

return err
}

func getImageReferencesAndAuth(cli command.Cli, imgName string) (context.Context, reference.Named, *registry.RepositoryInfo, *types.AuthConfig, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning a context.Context is a bit unconventional. I think this function should accept the context as the first arg.

Maybe as a follow up, it would be nice to have a type for reference.Named, *registry.RepositoryInfo, *types.AuthConfig. It seems they are all directly related, so I think having a type would reduce the number of args sent to a couple of functions.

I also notice that getTag() is always called after this. With a type we could parse out the tag immediately in this single step, and access the tag from .Tag().

assert.NotContains(t, cli.OutBuffer().String(), "(Repo Admin)")
}

func TestTUFToSigner(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TestNotaryRoleToSigner

},
}
flags := cmd.Flags()
flags.BoolVarP(&options.forceYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be consistent with other commands, I believe we use the text Do not prompt for confirmation

return nil
}

func revokeTestHelper(notaryRepo *client.NotaryRepository, tag string) error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does TestHelper refer to here? Could this be revokeSignature() ?

}
defer clearChangeList(notaryRepo)
if err := revokeTestHelper(notaryRepo, tag); err != nil {
return fmt.Errorf("could not remove signature for %s: %s", remote, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errors.Wrapf(err, "could not remove...")

from pkg/errors

There's a couple other instances of these as well

cli := test.NewFakeCli(&fakeClient{})
cmd := newInspectCommand(cli)
cmd.SetArgs([]string{"alpine"})
assert.NoError(t, cmd.Execute())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this contact the "real" notary server? Is that what you were referring to by fixture images in the description?

I think maybe trust.GetNotaryRepository() should be changed to Cli.GetNotaryClient() (or something like that) which would return an interface instead of a client.NotaryRepostiory struct . Once it's on the command.Cli interface it makes it really easy to fake it in tests.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it does contact a real notary server. Is the idea that Cli.GetNotaryClient would wrap a notary repo with a subset of the API? It seems that could certainly work

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't necessary have to wrap it with new structs, it could just return an Interface type instead of a struct, so that we can use a fake during testing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been trying to wrap my head around this but I'm not sure if it makes sense since a NotaryRepository is custom per-image. Is that OK in the context of command.Cli, or are assuming that a command.Cli may persist for longer than a single image command?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That does make it more difficult. We ran into this problem in #138 with the distribution client. In that case I wrapped the whole thing in an interface that was more appropriate for our use case in the cli (https://github.com/docker/cli/pull/138/files#diff-cb6e6f1ab385cb866626ee63bfa1a273R29).

It sounds like notary is using a similar pattern.

I think something like this in cli/command/cli.go should work:

func (cli *DockerCli) NotaryClient(ref trust.ImageRefAndAuth) trust.Repository {
    ...
}

where trust.Repository is the local interface we define (maybe in cli/trust).

We can still have multiple instances per command.Cli. In tests we can return a fakeTrustRepository.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

awesome, that would work. I'll implement this 👍

@dnephin
Copy link
Contributor

dnephin commented Aug 25, 2017

I think the design looks good. It sounds like you've already done a lot of work on the design before opening this PR so I mostly focused on the code.

@riyazdf riyazdf force-pushed the docker-trust branch 6 times, most recently from 1f18ec9 to ed1f469 Compare August 26, 2017 00:53
@riyazdf
Copy link
Author

riyazdf commented Aug 26, 2017

@dnephin: thank you for the review! I've addressed all comments except for the following:

  • for the notary tag you suggest: there isn't a "canonical" notary server or a way to currently pre-configure one per-image (you could in theory contact a different one for different images) - so I'm inclined to add this later if you think that makes sense
  • I've flattened the map for the admin keys. Haven't done so for signers and the signatures because I want to make sure I don't mess up the table printing 😅
  • totally agree for mocking out the notary client behind the CLI so we can mock it. Will get to this.

@dnephin
Copy link
Contributor

dnephin commented Aug 26, 2017

so I'm inclined to add [the notary tag] later if you think that makes sense

Sounds good. I think we can forget about the tag, I can't think of a good reason to hide the commands.

@riyazdf riyazdf force-pushed the docker-trust branch 2 times, most recently from efa876c to 272b5a2 Compare September 1, 2017 22:08
@riyazdf
Copy link
Author

riyazdf commented Sep 1, 2017

Some small updates: I've made a couple of tweaks to address small discrepancies in CLI output and we also have an interface for a trust repository incoming from Notary notaryproject/notary#1220 that will help provide testable mocks for this PR. I'll update here once that is merged and vendored.

@riyazdf
Copy link
Author

riyazdf commented Sep 12, 2017

@dnephin: I've rebased and vendored in the notary client interface and exposed it as cli.NotaryClient. I'm rewriting the tests now to use mocks of that, but figured I'd push the update so you can see how the cli changes look

Copy link
Contributor

@dnephin dnephin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, looks good

}

// ImageRefAndAuth contains all reference information and the auth config for an image request
type ImageRefAndAuth struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the cli/trust package might be a better place for this type

@@ -151,6 +158,89 @@ func (cli *DockerCli) Initialize(opts *cliflags.ClientOptions) error {
return nil
}

// NotaryClient provides a Notary Repository to interact with signed metadata for an image
func (cli *DockerCli) NotaryClient(imgRefAndAuth ImageRefAndAuth, forWrite bool) (notaryclient.Repository, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always try to avoid boolean arguments because from the caller it's not really clear what true or false means. Maybe this could takes a []string of actions, and there could be two predefined slices in cli/trust

var (
  ActionsPullOnly = []string{"pull"}
  ActionsPushAndPull = []string{"pull", "push"}
)

So from the caller it will be obvious what is being requested.

Signed-off-by: Riyaz Faizullabhoy <[email protected]>
Signed-off-by: Riyaz Faizullabhoy <[email protected]>
Signed-off-by: Riyaz Faizullabhoy <[email protected]>
Signed-off-by: Riyaz Faizullabhoy <[email protected]>
Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changes and putting it as "experimental" LGTM - we can address tweaking the commands/UX in follow ups

@johnstep
Copy link
Contributor

I verified that signing and viewing works from Windows.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants