diff --git a/cli/command/cli.go b/cli/command/cli.go index d6a8f3c5c3..62af0c30c3 100644 --- a/cli/command/cli.go +++ b/cli/command/cli.go @@ -14,16 +14,13 @@ import ( cliflags "github.com/docker/cli/cli/flags" "github.com/docker/cli/cli/trust" dopts "github.com/docker/cli/opts" - "github.com/docker/distribution/reference" "github.com/docker/docker/api" "github.com/docker/docker/client" - "github.com/docker/docker/registry" "github.com/docker/go-connections/sockets" "github.com/docker/go-connections/tlsconfig" "github.com/docker/notary" notaryclient "github.com/docker/notary/client" "github.com/docker/notary/passphrase" - digest "github.com/opencontainers/go-digest" "github.com/pkg/errors" "github.com/spf13/cobra" "golang.org/x/net/context" @@ -172,46 +169,6 @@ func (cli *DockerCli) NotaryClient(imgRefAndAuth trust.ImageRefAndAuth, actions return trust.GetNotaryRepository(cli.In(), cli.Out(), UserAgent(), imgRefAndAuth.RepoInfo(), imgRefAndAuth.AuthConfig(), actions...) } -// GetImageReferencesAndAuth retrieves the necessary reference and auth information for an image name -// as a ImageRefAndAuth struct -func GetImageReferencesAndAuth(ctx context.Context, cli Cli, imgName string) (*trust.ImageRefAndAuth, error) { - ref, err := reference.ParseNormalizedNamed(imgName) - if err != nil { - return nil, err - } - - // Resolve the Repository name from fqn to RepositoryInfo - repoInfo, err := registry.ParseRepositoryInfo(ref) - if err != nil { - return nil, err - } - - authConfig := ResolveAuthConfig(ctx, cli, repoInfo.Index) - return trust.NewImageRefAndAuth(&authConfig, ref, repoInfo, getTag(ref), getDigest(ref)), nil -} - -func getTag(ref reference.Named) string { - switch x := ref.(type) { - case reference.Canonical, reference.Digested: - return "" - case reference.NamedTagged: - return x.Tag() - default: - return "" - } -} - -func getDigest(ref reference.Named) digest.Digest { - switch x := ref.(type) { - case reference.Canonical: - return x.Digest() - case reference.Digested: - return x.Digest() - default: - return digest.Digest("") - } -} - // ServerInfo stores details about the supported features and platform of the // server type ServerInfo struct { diff --git a/cli/command/cli_test.go b/cli/command/cli_test.go index 9a0a6a0f87..16ba7c4ff9 100644 --- a/cli/command/cli_test.go +++ b/cli/command/cli_test.go @@ -9,7 +9,6 @@ import ( "github.com/docker/cli/cli/config/configfile" "github.com/docker/cli/cli/flags" "github.com/docker/cli/internal/test/testutil" - "github.com/docker/distribution/reference" "github.com/docker/docker/api" "github.com/docker/docker/api/types" "github.com/docker/docker/client" @@ -197,20 +196,3 @@ func TestGetClientWithPassword(t *testing.T) { }) } } - -func TestGetTag(t *testing.T) { - ref, err := reference.ParseNormalizedNamed("ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2") - assert.NoError(t, err) - tag := getTag(ref) - assert.Equal(t, "", tag) - - ref, err = reference.ParseNormalizedNamed("alpine:latest") - assert.NoError(t, err) - tag = getTag(ref) - assert.Equal(t, tag, "latest") - - ref, err = reference.ParseNormalizedNamed("alpine") - assert.NoError(t, err) - tag = getTag(ref) - assert.Equal(t, tag, "") -} diff --git a/cli/command/trust/client_test.go b/cli/command/trust/client_test.go index 4a995157ca..3343209673 100644 --- a/cli/command/trust/client_test.go +++ b/cli/command/trust/client_test.go @@ -299,6 +299,26 @@ var loadedTargets = []client.TargetSignedStruct{ {Target: loadedGreenTarget, Role: loadedReleasesRole}, } +func (l LoadedNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { + rootRole := data.Role{ + RootRole: data.RootRole{ + KeyIDs: []string{"rootID"}, + Threshold: 1, + }, + Name: data.CanonicalRootRole, + } + + targetsRole := data.Role{ + RootRole: data.RootRole{ + KeyIDs: []string{"targetsID"}, + Threshold: 1, + }, + Name: data.CanonicalTargetsRole, + } + + return []client.RoleWithSignatures{{Role: rootRole}, {Role: targetsRole}}, nil +} + func (l LoadedNotaryRepository) ListTargets(roles ...data.RoleName) ([]*client.TargetWithRole, error) { filteredTargets := []*client.TargetWithRole{} for _, tgt := range loadedTargets { diff --git a/cli/command/trust/inspect.go b/cli/command/trust/inspect.go index a4590a0dcf..f634d75970 100644 --- a/cli/command/trust/inspect.go +++ b/cli/command/trust/inspect.go @@ -12,6 +12,8 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/trust" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/notary" "github.com/docker/notary/client" "github.com/docker/notary/tuf/data" @@ -59,7 +61,10 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command { func lookupTrustInfo(cli command.Cli, remote string) error { ctx := context.Background() - imgRefAndAuth, err := command.GetImageReferencesAndAuth(ctx, cli, remote) + authResolver := func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig { + return command.ResolveAuthConfig(ctx, cli, index) + } + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver, remote) if err != nil { return err } diff --git a/cli/command/trust/inspect_test.go b/cli/command/trust/inspect_test.go index 1ea15551e8..97705ecd9b 100644 --- a/cli/command/trust/inspect_test.go +++ b/cli/command/trust/inspect_test.go @@ -12,6 +12,7 @@ import ( "github.com/docker/notary" "github.com/docker/notary/client" "github.com/docker/notary/tuf/data" + "github.com/gotestyourself/gotestyourself/golden" "github.com/stretchr/testify/assert" ) @@ -113,15 +114,7 @@ func TestTrustInspectCommandFullRepoWithoutSigners(t *testing.T) { cmd.SetArgs([]string{"signed-repo"}) assert.NoError(t, cmd.Execute()) - // Check for the signed tag headers - assert.Contains(t, cli.OutBuffer().String(), "SIGNED TAG") - assert.Contains(t, cli.OutBuffer().String(), "DIGEST") - assert.Contains(t, cli.OutBuffer().String(), "SIGNERS") - // Check for the signer headers - assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:") - assert.Contains(t, cli.OutBuffer().String(), "(Repo Admin)") - // no delegations on this repo - assert.NotContains(t, cli.OutBuffer().String(), "List of signers and their keys:") + golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-full-repo-no-signers.golden") } func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) { @@ -130,19 +123,8 @@ func TestTrustInspectCommandOneTagWithoutSigners(t *testing.T) { cmd := newInspectCommand(cli) cmd.SetArgs([]string{"signed-repo:green"}) assert.NoError(t, cmd.Execute()) - assert.Contains(t, cli.OutBuffer().String(), "SIGNED TAG") - assert.Contains(t, cli.OutBuffer().String(), "DIGEST") - assert.Contains(t, cli.OutBuffer().String(), "SIGNERS") - // Check for the signer headers - assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:") - // make sure the tag isn't included - assert.NotContains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:green") - assert.Contains(t, cli.OutBuffer().String(), "green") - assert.Contains(t, cli.OutBuffer().String(), "(Repo Admin)") - // no delegations on this repo - assert.NotContains(t, cli.OutBuffer().String(), "alice") - assert.NotContains(t, cli.OutBuffer().String(), "bob") - assert.NotContains(t, cli.OutBuffer().String(), "List of signers and their keys:") + + golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-one-tag-no-signers.golden") } func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) { @@ -152,21 +134,7 @@ func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) { cmd.SetArgs([]string{"signed-repo"}) assert.NoError(t, cmd.Execute()) - // Check for the signed tag headers and contents - assert.Contains(t, cli.OutBuffer().String(), "SIGNED TAG") - assert.Contains(t, cli.OutBuffer().String(), "DIGEST") - assert.Contains(t, cli.OutBuffer().String(), "SIGNERS") - assert.Contains(t, cli.OutBuffer().String(), "blue 626c75652d646967657374 alice") - assert.Contains(t, cli.OutBuffer().String(), "green 677265656e2d646967657374 (Repo Admin)") - assert.Contains(t, cli.OutBuffer().String(), "red 7265642d646967657374 alice, bob") - - // Check for the signer headers and contents - assert.Contains(t, cli.OutBuffer().String(), "List of signers and their keys:") - assert.Contains(t, cli.OutBuffer().String(), "SIGNER") - assert.Contains(t, cli.OutBuffer().String(), "KEYS") - assert.Contains(t, cli.OutBuffer().String(), "alice A") - assert.Contains(t, cli.OutBuffer().String(), "bob B") - assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:") + golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-full-repo-with-signers.golden") } func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) { @@ -176,20 +144,7 @@ func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) { cmd.SetArgs([]string{"signed-repo:unsigned"}) assert.NoError(t, cmd.Execute()) - // Check that the signatures table does not show up, and instead we get the message - assert.Contains(t, cli.OutBuffer().String(), "No signatures for signed-repo:unsigned") - assert.NotContains(t, cli.OutBuffer().String(), "SIGNED TAG") - assert.NotContains(t, cli.OutBuffer().String(), "DIGEST") - assert.NotContains(t, cli.OutBuffer().String(), "SIGNERS") - - // Check for the signer headers and contents - assert.Contains(t, cli.OutBuffer().String(), "List of signers and their keys:") - assert.Contains(t, cli.OutBuffer().String(), "SIGNER") - assert.Contains(t, cli.OutBuffer().String(), "KEYS") - assert.Contains(t, cli.OutBuffer().String(), "alice A") - assert.Contains(t, cli.OutBuffer().String(), "bob B") - assert.Contains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:") - assert.NotContains(t, cli.OutBuffer().String(), "Administrative keys for signed-repo:unsigned") + golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-unsigned-tag-with-signers.golden") } func TestNotaryRoleToSigner(t *testing.T) { diff --git a/cli/command/trust/revoke.go b/cli/command/trust/revoke.go index f53b2f8d97..76f1ae1432 100644 --- a/cli/command/trust/revoke.go +++ b/cli/command/trust/revoke.go @@ -5,6 +5,8 @@ import ( "fmt" "os" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/notary/client" "github.com/docker/notary/tuf/data" "github.com/pkg/errors" @@ -36,7 +38,10 @@ func newRevokeCommand(dockerCli command.Cli) *cobra.Command { func revokeTrust(cli command.Cli, remote string, options revokeOptions) error { ctx := context.Background() - imgRefAndAuth, err := command.GetImageReferencesAndAuth(ctx, cli, remote) + authResolver := func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig { + return command.ResolveAuthConfig(ctx, cli, index) + } + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver, remote) if err != nil { return err } diff --git a/cli/command/trust/sign.go b/cli/command/trust/sign.go index d791b96517..68598b9878 100644 --- a/cli/command/trust/sign.go +++ b/cli/command/trust/sign.go @@ -11,6 +11,8 @@ import ( "github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command/image" "github.com/docker/cli/cli/trust" + "github.com/docker/docker/api/types" + registrytypes "github.com/docker/docker/api/types/registry" "github.com/docker/notary/client" "github.com/docker/notary/tuf/data" "github.com/pkg/errors" @@ -31,7 +33,10 @@ func newSignCommand(dockerCli command.Cli) *cobra.Command { func signImage(cli command.Cli, imageName string) error { ctx := context.Background() - imgRefAndAuth, err := command.GetImageReferencesAndAuth(ctx, cli, imageName) + authResolver := func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig { + return command.ResolveAuthConfig(ctx, cli, index) + } + imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, authResolver, imageName) if err != nil { return err } diff --git a/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden b/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden new file mode 100644 index 0000000000..6fb3b5b34b --- /dev/null +++ b/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden @@ -0,0 +1,6 @@ +SIGNED TAG DIGEST SIGNERS +green 677265656e2d646967657374 (Repo Admin) + +Administrative keys for signed-repo: +Repository Key: targetsID +Root Key: rootID diff --git a/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden b/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden new file mode 100644 index 0000000000..2486f02660 --- /dev/null +++ b/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden @@ -0,0 +1,14 @@ +SIGNED TAG DIGEST SIGNERS +blue 626c75652d646967657374 alice +green 677265656e2d646967657374 (Repo Admin) +red 7265642d646967657374 alice, bob + +List of signers and their keys: + +SIGNER KEYS +alice A +bob B + +Administrative keys for signed-repo: +Repository Key: targetsID +Root Key: rootID diff --git a/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden b/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden new file mode 100644 index 0000000000..6fb3b5b34b --- /dev/null +++ b/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden @@ -0,0 +1,6 @@ +SIGNED TAG DIGEST SIGNERS +green 677265656e2d646967657374 (Repo Admin) + +Administrative keys for signed-repo: +Repository Key: targetsID +Root Key: rootID diff --git a/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden b/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden new file mode 100644 index 0000000000..25732066e5 --- /dev/null +++ b/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden @@ -0,0 +1,13 @@ + +No signatures for signed-repo:unsigned + + +List of signers and their keys: + +SIGNER KEYS +alice A +bob B + +Administrative keys for signed-repo: +Repository Key: targetsID +Root Key: rootID diff --git a/cli/trust/trust.go b/cli/trust/trust.go index 8d27594b9a..90809db2a9 100644 --- a/cli/trust/trust.go +++ b/cli/trust/trust.go @@ -1,6 +1,7 @@ package trust import ( + "context" "encoding/json" "io" "net" @@ -299,6 +300,46 @@ func NewImageRefAndAuth(authConfig *types.AuthConfig, reference reference.Named, return &ImageRefAndAuth{authConfig, reference, repoInfo, tag, digest} } +// GetImageReferencesAndAuth retrieves the necessary reference and auth information for an image name +// as a ImageRefAndAuth struct +func GetImageReferencesAndAuth(ctx context.Context, authResolver func(ctx context.Context, index *registrytypes.IndexInfo) types.AuthConfig, imgName string) (*ImageRefAndAuth, error) { + ref, err := reference.ParseNormalizedNamed(imgName) + if err != nil { + return nil, err + } + + // Resolve the Repository name from fqn to RepositoryInfo + repoInfo, err := registry.ParseRepositoryInfo(ref) + if err != nil { + return nil, err + } + + authConfig := authResolver(ctx, repoInfo.Index) + return NewImageRefAndAuth(&authConfig, ref, repoInfo, getTag(ref), getDigest(ref)), nil +} + +func getTag(ref reference.Named) string { + switch x := ref.(type) { + case reference.Canonical, reference.Digested: + return "" + case reference.NamedTagged: + return x.Tag() + default: + return "" + } +} + +func getDigest(ref reference.Named) digest.Digest { + switch x := ref.(type) { + case reference.Canonical: + return x.Digest() + case reference.Digested: + return x.Digest() + default: + return digest.Digest("") + } +} + // AuthConfig returns the auth information (username, etc) for a given ImageRefAndAuth func (imgRefAuth *ImageRefAndAuth) AuthConfig() *types.AuthConfig { return imgRefAuth.authConfig diff --git a/cli/trust/trust_test.go b/cli/trust/trust_test.go new file mode 100644 index 0000000000..50bcc046fa --- /dev/null +++ b/cli/trust/trust_test.go @@ -0,0 +1,43 @@ +package trust + +import ( + "testing" + + "github.com/docker/distribution/reference" + digest "github.com/opencontainers/go-digest" + "github.com/stretchr/testify/assert" +) + +func TestGetTag(t *testing.T) { + ref, err := reference.ParseNormalizedNamed("ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2") + assert.NoError(t, err) + tag := getTag(ref) + assert.Equal(t, "", tag) + + ref, err = reference.ParseNormalizedNamed("alpine:latest") + assert.NoError(t, err) + tag = getTag(ref) + assert.Equal(t, tag, "latest") + + ref, err = reference.ParseNormalizedNamed("alpine") + assert.NoError(t, err) + tag = getTag(ref) + assert.Equal(t, tag, "") +} + +func TestGetDigest(t *testing.T) { + ref, err := reference.ParseNormalizedNamed("ubuntu@sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2") + assert.NoError(t, err) + d := getDigest(ref) + assert.Equal(t, digest.Digest("sha256:45b23dee08af5e43a7fea6c4cf9c25ccf269ee113168c19722f87876677c5cb2"), d) + + ref, err = reference.ParseNormalizedNamed("alpine:latest") + assert.NoError(t, err) + d = getDigest(ref) + assert.Equal(t, digest.Digest(""), d) + + ref, err = reference.ParseNormalizedNamed("alpine") + assert.NoError(t, err) + d = getDigest(ref) + assert.Equal(t, digest.Digest(""), d) +}