diff --git a/cli/command/trust/client_test.go b/cli/command/trust/client_test.go index cc4fec5074..726dd89761 100644 --- a/cli/command/trust/client_test.go +++ b/cli/command/trust/client_test.go @@ -192,7 +192,24 @@ func (e EmptyTargetsNotaryRepository) GetAllTargetMetadataByName(name string) ([ } func (e EmptyTargetsNotaryRepository) ListRoles() ([]client.RoleWithSignatures, error) { - return []client.RoleWithSignatures{}, nil + 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 (e EmptyTargetsNotaryRepository) GetDelegationRoles() ([]data.Role, error) { diff --git a/cli/command/trust/inspect.go b/cli/command/trust/inspect.go index 4f343b5ac1..efb6604fc5 100644 --- a/cli/command/trust/inspect.go +++ b/cli/command/trust/inspect.go @@ -14,20 +14,40 @@ import ( func newInspectCommand(dockerCli command.Cli) *cobra.Command { cmd := &cobra.Command{ - Use: "inspect IMAGE[:TAG]", + Use: "inspect IMAGE[:TAG] [IMAGE[:TAG]...]", Short: "Return low-level information about keys and signatures", - Args: cli.ExactArgs(1), + Args: cli.RequiresMinArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return inspectTrustInfo(dockerCli, args[0]) + return inspectTrustInfo(dockerCli, args) }, } return cmd } -func inspectTrustInfo(cli command.Cli, remote string) error { +func inspectTrustInfo(cli command.Cli, remotes []string) error { + trustRepoInfoList := []trustRepo{} + for _, remote := range remotes { + trustInfo, err := getRepoTrustInfo(cli, remote) + if err != nil { + return err + } + if trustInfo == nil { + continue + } + trustRepoInfoList = append(trustRepoInfoList, *trustInfo) + } + trustInspectJSON, err := json.Marshal(trustRepoInfoList) + if err != nil { + return errors.Wrap(err, "error while serializing trusted repository info") + } + fmt.Fprintf(cli.Out(), string(trustInspectJSON)) + return nil +} + +func getRepoTrustInfo(cli command.Cli, remote string) (*trustRepo, error) { signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(cli, remote) if err != nil { - return err + return nil, err } // process the signatures to include repo admin if signed by the base targets role for idx, sig := range signatureRows { @@ -55,17 +75,11 @@ func inspectTrustInfo(cli command.Cli, remote string) error { } sort.Slice(adminList, func(i, j int) bool { return adminList[i].Name > adminList[j].Name }) - trustRepoInfo := &trustRepo{ + return &trustRepo{ SignedTags: signatureRows, Signers: signerList, AdminstrativeKeys: adminList, - } - trustInspectJSON, err := json.Marshal(trustRepoInfo) - if err != nil { - return errors.Wrap(err, "error while serializing trusted repository info") - } - fmt.Fprintf(cli.Out(), string(trustInspectJSON)) - return nil + }, nil } // trustRepo represents consumable information about a trusted repository diff --git a/cli/command/trust/inspect_test.go b/cli/command/trust/inspect_test.go index ac2a819139..aa0051283f 100644 --- a/cli/command/trust/inspect_test.go +++ b/cli/command/trust/inspect_test.go @@ -114,6 +114,15 @@ func TestTrustInspectCommandFullRepoWithSigners(t *testing.T) { golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-full-repo-with-signers.golden") } +func TestTrustInspectCommandMultipleFullReposWithSigners(t *testing.T) { + cli := test.NewFakeCli(&fakeClient{}) + cli.SetNotaryClient(getLoadedNotaryRepository) + cmd := newInspectCommand(cli) + cmd.SetArgs([]string{"signed-repo", "signed-repo"}) + assert.NoError(t, cmd.Execute()) + golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-multiple-repos-with-signers.golden") +} + func TestTrustInspectCommandUnsignedTagInSignedRepo(t *testing.T) { cli := test.NewFakeCli(&fakeClient{}) cli.SetNotaryClient(getLoadedNotaryRepository) diff --git a/cli/command/trust/testdata/trust-inspect-empty-repo.golden b/cli/command/trust/testdata/trust-inspect-empty-repo.golden index 9e26dfeeb6..25a4eba17c 100644 --- a/cli/command/trust/testdata/trust-inspect-empty-repo.golden +++ b/cli/command/trust/testdata/trust-inspect-empty-repo.golden @@ -1 +1 @@ -{} \ No newline at end of file +[{"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] \ No newline at end of file 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 index a19aa01715..c55881c624 100644 --- a/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden +++ b/cli/command/trust/testdata/trust-inspect-full-repo-no-signers.golden @@ -1 +1 @@ -{"SignedTags":[{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]} \ No newline at end of file +[{"SignedTags":[{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] \ No newline at end of file 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 index 4b1571d085..ce0550dc48 100644 --- a/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden +++ b/cli/command/trust/testdata/trust-inspect-full-repo-with-signers.golden @@ -1 +1 @@ -{"SignedTags":[{"SignedTag":"blue","Digest":"626c75652d646967657374","Signers":["alice"]},{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]},{"SignedTag":"red","Digest":"7265642d646967657374","Signers":["alice","bob"]}],"Signers":[{"Name":"bob","Keys":["B"]},{"Name":"alice","Keys":["A"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]} \ No newline at end of file +[{"SignedTags":[{"SignedTag":"blue","Digest":"626c75652d646967657374","Signers":["alice"]},{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]},{"SignedTag":"red","Digest":"7265642d646967657374","Signers":["alice","bob"]}],"Signers":[{"Name":"bob","Keys":["B"]},{"Name":"alice","Keys":["A"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] \ No newline at end of file diff --git a/cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden b/cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden new file mode 100644 index 0000000000..73e211cc65 --- /dev/null +++ b/cli/command/trust/testdata/trust-inspect-multiple-repos-with-signers.golden @@ -0,0 +1 @@ +[{"SignedTags":[{"SignedTag":"blue","Digest":"626c75652d646967657374","Signers":["alice"]},{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]},{"SignedTag":"red","Digest":"7265642d646967657374","Signers":["alice","bob"]}],"Signers":[{"Name":"bob","Keys":["B"]},{"Name":"alice","Keys":["A"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]},{"SignedTags":[{"SignedTag":"blue","Digest":"626c75652d646967657374","Signers":["alice"]},{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]},{"SignedTag":"red","Digest":"7265642d646967657374","Signers":["alice","bob"]}],"Signers":[{"Name":"bob","Keys":["B"]},{"Name":"alice","Keys":["A"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] \ No newline at end of file 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 index a19aa01715..c55881c624 100644 --- a/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden +++ b/cli/command/trust/testdata/trust-inspect-one-tag-no-signers.golden @@ -1 +1 @@ -{"SignedTags":[{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]} \ No newline at end of file +[{"SignedTags":[{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] \ No newline at end of file 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 index d72e709538..94d83d7627 100644 --- a/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden +++ b/cli/command/trust/testdata/trust-inspect-unsigned-tag-with-signers.golden @@ -1 +1 @@ -{"Signers":[{"Name":"bob","Keys":["B"]},{"Name":"alice","Keys":["A"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]} \ No newline at end of file +[{"Signers":[{"Name":"bob","Keys":["B"]},{"Name":"alice","Keys":["A"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] \ No newline at end of file diff --git a/docs/reference/commandline/trust_inspect.md b/docs/reference/commandline/trust_inspect.md index 8e3cf51f3a..66dd1e86b7 100644 --- a/docs/reference/commandline/trust_inspect.md +++ b/docs/reference/commandline/trust_inspect.md @@ -16,7 +16,7 @@ keywords: "view, notary, trust" # trust inspect ```markdown -Usage: docker trust inspect IMAGE[:TAG] +Usage: docker trust inspect IMAGE[:TAG] [IMAGE[:TAG]...] Return low-level information about keys and signatures @@ -40,31 +40,33 @@ new tags. ```bash $ docker trust inspect alpine:latest | jq -{ - "SignedTags": [ - { - "SignedTag": "latest", - "Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478", - "Signers": [ - "Repo Admin" - ] - } - ], - "AdminstrativeKeys": [ - { - "Name": "Repository", - "Keys": [ - "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd" - ] - }, - { - "Name": "Root", - "Keys": [ - "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce" - ] - } - ] -} +[ + { + "SignedTags": [ + { + "SignedTag": "latest", + "Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478", + "Signers": [ + "Repo Admin" + ] + } + ], + "AdminstrativeKeys": [ + { + "Name": "Repository", + "Keys": [ + "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd" + ] + }, + { + "Name": "Root", + "Keys": [ + "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce" + ] + } + ] + } +] ``` The `SignedTags` key will list the `SignedTag` name, its `Digest`, and the `Signers` responsible for the signature. @@ -78,55 +80,57 @@ If signers are set up for the repository via other `docker trust` commands, `doc ```bash $ docker trust inspect my-image:purple | jq -{ - "SignedTags": [ - { - "SignedTag": "purple", - "Digest": "941d3dba358621ce3c41ef67b47cf80f701ff80cdf46b5cc86587eaebfe45557", - "Signers": [ - "alice", - "bob", - "carol" - ] - } - ], - "Signers": [ - { - "Name": "alice", - "Keys": [ - "04dd031411ed671ae1e12f47ddc8646d98f135090b01e54c3561e843084484a3", - "6a11e4898a4014d400332ab0e096308c844584ff70943cdd1d6628d577f45fd8" - ] - }, - { - "Name": "bob", - "Keys": [ - "433e245c656ae9733cdcc504bfa560f90950104442c4528c9616daa45824ccba" - ] - }, - { - "Name": "carol", - "Keys": [ - "d32fa8b5ca08273a2880f455fcb318da3dc80aeae1a30610815140deef8f30d9", - "9a8bbec6ba2af88a5fad6047d428d17e6d05dbdd03d15b4fc8a9a0e8049cd606" - ] - } - ], - "AdminstrativeKeys": [ - { - "Name": "Repository", - "Keys": [ - "27df2c8187e7543345c2e0bf3a1262e0bc63a72754e9a7395eac3f747ec23a44" - ] - }, - { - "Name": "Root", - "Keys": [ - "40b66ccc8b176be8c7d365a17f3e046d1c3494e053dd57cfeacfe2e19c4f8e8f" - ] - } - ] -} +[ + { + "SignedTags": [ + { + "SignedTag": "purple", + "Digest": "941d3dba358621ce3c41ef67b47cf80f701ff80cdf46b5cc86587eaebfe45557", + "Signers": [ + "alice", + "bob", + "carol" + ] + } + ], + "Signers": [ + { + "Name": "alice", + "Keys": [ + "04dd031411ed671ae1e12f47ddc8646d98f135090b01e54c3561e843084484a3", + "6a11e4898a4014d400332ab0e096308c844584ff70943cdd1d6628d577f45fd8" + ] + }, + { + "Name": "bob", + "Keys": [ + "433e245c656ae9733cdcc504bfa560f90950104442c4528c9616daa45824ccba" + ] + }, + { + "Name": "carol", + "Keys": [ + "d32fa8b5ca08273a2880f455fcb318da3dc80aeae1a30610815140deef8f30d9", + "9a8bbec6ba2af88a5fad6047d428d17e6d05dbdd03d15b4fc8a9a0e8049cd606" + ] + } + ], + "AdminstrativeKeys": [ + { + "Name": "Repository", + "Keys": [ + "27df2c8187e7543345c2e0bf3a1262e0bc63a72754e9a7395eac3f747ec23a44" + ] + }, + { + "Name": "Root", + "Keys": [ + "40b66ccc8b176be8c7d365a17f3e046d1c3494e053dd57cfeacfe2e19c4f8e8f" + ] + } + ] + } +] ``` If the image tag is unsigned or unavailable, `docker trust inspect` does not display any signed tags. @@ -140,114 +144,36 @@ However, if other tags are signed in the same image repository, `docker trust in ```bash $ docker trust inspect alpine:unsigned | jq -{ - "AdminstrativeKeys": [ - { - "Name": "Repository", - "Keys": [ - "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd" - ] - }, - { - "Name": "Root", - "Keys": [ - "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce" - ] - } - ] -} +[ + { + "AdminstrativeKeys": [ + { + "Name": "Repository", + "Keys": [ + "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd" + ] + }, + { + "Name": "Root", + "Keys": [ + "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce" + ] + } + ] + } +] ``` ### Get details about signatures for all image tags in a repository ```bash $ docker trust inspect alpine | jq -{ - "SignedTags": [ - { - "SignedTag": "2.6", - "Digest": "9ace551613070689a12857d62c30ef0daa9a376107ec0fff0e34786cedb3399b", - "Signers": [ - "Repo Admin" - ] - }, - { - "SignedTag": "2.7", - "Digest": "9f08005dff552038f0ad2f46b8e65ff3d25641747d3912e3ea8da6785046561a", - "Signers": [ - "Repo Admin" - ] - }, - { - "SignedTag": "3.1", - "Digest": "2d74cbc2fbe3d261fdcca45d493ce1e3f3efd270114a62e383a8e45caeb48788", - "Signers": [ - "Repo Admin" - ] - }, - { - "SignedTag": "3.2", - "Digest": "8565a58be8238ef688dbd90e43ec8e080114f1e1db846399116543eb8ef7d7b7", - "Signers": [ - "Repo Admin" - ] - }, - { - "SignedTag": "3.3", - "Digest": "06fa785d55c35050241c60274e24ad57025683d5e939b3a31cc94193ca24740b", - "Signers": [ - "Repo Admin" - ] - }, - { - "SignedTag": "3.4", - "Digest": "915b0ffca1d76ac57d83f28d568bcb516b6c274843ea8df7fac4b247440f796b", - "Signers": [ - "Repo Admin" - ] - }, - { - "SignedTag": "3.5", - "Digest": "b007a354427e1880de9cdba533e8e57382b7f2853a68a478a17d447b302c219c", - "Signers": [ - "Repo Admin" - ] - }, - { - "SignedTag": "3.6", - "Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478", - "Signers": [ - "Repo Admin" - ] - }, - { - "SignedTag": "edge", - "Digest": "23e7d843e63a3eee29b6b8cfcd10e23dd1ef28f47251a985606a31040bf8e096", - "Signers": [ - "Repo Admin" - ] - }, - { - "SignedTag": "latest", - "Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478", - "Signers": [ - "Repo Admin" - ] - } - ], - "AdminstrativeKeys": [ - { - "Name": "Repository", - "Keys": [ - "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd" - ] - }, - { - "Name": "Root", - "Keys": [ - "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce" - ] - } - ] -} + +``` + + +### Get details about signatures for multiple images + +```bash +$ docker trust inspect alpine ubuntu | jq ```