docker trust: view, revoke, sign subcommands (experimental)#472
Conversation
mdlinville
left a comment
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Does it show unsigned images? This kind of implies not.
There was a problem hiding this comment.
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. |
|
|
||
| ```bash | ||
| $ docker trust inspect alpine:latest | ||
| SIGNED TAG DIGEST SIGNERS |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
s/and then signs/before signing
| ## Building Notary | ||
|
|
||
| Prerequisites: | ||
| Note that our [latest stable release](https://github.com/docker/notary/releases) is at the head of the |
There was a problem hiding this comment.
s/Note that our/Notary's
There was a problem hiding this comment.
vendored code - we can address this in docker/notary
| 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: | ||
| ``` |
There was a problem hiding this comment.
vendored code - we can address this in docker/notary
| notary/ | ||
| ``` | ||
|
|
||
| To build the server and signer, please run `docker-compose build`. No newline at end of file |
There was a problem hiding this comment.
vendored code - we can address this in docker/notary
|
thank you @mstanleyjones for the review! Just updated with the fixes, and left a comment about one potential command output change. |
dnephin
left a comment
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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", |
There was a problem hiding this comment.
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 | ||
| } |
There was a problem hiding this comment.
minor: this if is unnecessary,
return cl.Clear("")|
|
||
| const releasedRoleName = "Repo Admin" | ||
|
|
||
| func checkLocalImageExistence(ctx context.Context, cli command.Cli, imageName string) error { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
| }, | ||
| } | ||
| flags := cmd.Flags() | ||
| flags.BoolVarP(&options.forceYes, "yes", "y", false, "Answer yes to the removal question (no confirmation)") |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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()) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
awesome, that would work. I'll implement this 👍
|
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. |
1f18ec9 to
ed1f469
Compare
|
@dnephin: thank you for the review! I've addressed all comments except for the following:
|
Sounds good. I think we can forget about the tag, I can't think of a good reason to hide the commands. |
efa876c to
272b5a2
Compare
|
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. |
272b5a2 to
58b0e75
Compare
|
@dnephin: I've rebased and vendored in the notary client interface and exposed it as |
| } | ||
|
|
||
| // ImageRefAndAuth contains all reference information and the auth config for an image request | ||
| type ImageRefAndAuth struct { |
There was a problem hiding this comment.
I think the cli/trust package might be a better place for this type
| } | ||
|
|
||
| // NotaryClient provides a Notary Repository to interact with signed metadata for an image | ||
| func (cli *DockerCli) NotaryClient(imgRefAndAuth ImageRefAndAuth, forWrite bool) (notaryclient.Repository, error) { |
There was a problem hiding this comment.
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.
58b0e75 to
75bc720
Compare
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
…ommand semantics Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
941f7cb to
e07f345
Compare
thaJeztah
left a comment
There was a problem hiding this comment.
changes and putting it as "experimental" LGTM - we can address tweaking the commands/UX in follow ups
|
I verified that signing and viewing works from Windows. |
This PR introduces the
docker trustsubcommand. The command has been fleshed out in detail in this design document.docker trustprovides a direct notary signing experience beyond what is provided with theDOCKER_CONTENT_TRUSTenvironment variable.In this PR, we introduce three subcommands:
docker trust view: displays signatures on image tags, and who signed themdocker 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 existcc @ashfall and @eiais who helped implement this feature