Use default inspect formatting, remove omitempty, update docs

Signed-off-by: Riyaz Faizullabhoy <riyaz.faizullabhoy@docker.com>
This commit is contained in:
Riyaz Faizullabhoy 2017-11-17 12:04:51 -08:00
parent 1eb87cc096
commit a9428285f0
12 changed files with 728 additions and 204 deletions

167
cli/command/trust/common.go Normal file
View File

@ -0,0 +1,167 @@
package trust
import (
"context"
"encoding/hex"
"fmt"
"sort"
"strings"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/trust"
"github.com/sirupsen/logrus"
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/client"
"github.com/theupdateframework/notary/tuf/data"
)
// trustTagKey represents a unique signed tag and hex-encoded hash pair
type trustTagKey struct {
SignedTag string
Digest string
}
// trustTagRow encodes all human-consumable information for a signed tag, including signers
type trustTagRow struct {
trustTagKey
Signers []string
}
type trustTagRowList []trustTagRow
func (tagComparator trustTagRowList) Len() int {
return len(tagComparator)
}
func (tagComparator trustTagRowList) Less(i, j int) bool {
return tagComparator[i].SignedTag < tagComparator[j].SignedTag
}
func (tagComparator trustTagRowList) Swap(i, j int) {
tagComparator[i], tagComparator[j] = tagComparator[j], tagComparator[i]
}
// trustRepo represents consumable information about a trusted repository
type trustRepo struct {
Name string
SignedTags trustTagRowList
Signers []trustSigner
AdminstrativeKeys []trustSigner
}
// trustSigner represents a trusted signer in a trusted repository
// a signer is defined by a name and list of trustKeys
type trustSigner struct {
Name string `json:",omitempty"`
Keys []trustKey `json:",omitempty"`
}
// trustKey contains information about trusted keys
type trustKey struct {
ID string `json:",omitempty"`
}
// lookupTrustInfo returns processed signature and role information about a notary repository.
// This information is to be pretty printed or serialized into a machine-readable format.
func lookupTrustInfo(cli command.Cli, remote string) (trustTagRowList, []client.RoleWithSignatures, []data.Role, error) {
ctx := context.Background()
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote)
if err != nil {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, err
}
tag := imgRefAndAuth.Tag()
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
if err != nil {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
}
if err = clearChangeList(notaryRepo); err != nil {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, err
}
defer clearChangeList(notaryRepo)
// Retrieve all released signatures, match them, and pretty print them
allSignedTargets, err := notaryRepo.GetAllTargetMetadataByName(tag)
if err != nil {
logrus.Debug(trust.NotaryError(remote, err))
// print an empty table if we don't have signed targets, but have an initialized notary repo
if _, ok := err.(client.ErrNoSuchTarget); !ok {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signatures or cannot access %s", remote)
}
}
signatureRows := matchReleasedSignatures(allSignedTargets)
// get the administrative roles
adminRolesWithSigs, err := notaryRepo.ListRoles()
if err != nil {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signers for %s", remote)
}
// get delegation roles with the canonical key IDs
delegationRoles, err := notaryRepo.GetDelegationRoles()
if err != nil {
logrus.Debugf("no delegation roles found, or error fetching them for %s: %v", remote, err)
}
return signatureRows, adminRolesWithSigs, delegationRoles, nil
}
func formatAdminRole(roleWithSigs client.RoleWithSignatures) string {
adminKeyList := roleWithSigs.KeyIDs
sort.Strings(adminKeyList)
var role string
switch roleWithSigs.Name {
case data.CanonicalTargetsRole:
role = "Repository Key"
case data.CanonicalRootRole:
role = "Root Key"
default:
return ""
}
return fmt.Sprintf("%s:\t%s\n", role, strings.Join(adminKeyList, ", "))
}
func getDelegationRoleToKeyMap(rawDelegationRoles []data.Role) map[string][]string {
signerRoleToKeyIDs := make(map[string][]string)
for _, delRole := range rawDelegationRoles {
switch delRole.Name {
case trust.ReleasesRole, data.CanonicalRootRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole, data.CanonicalTimestampRole:
continue
default:
signerRoleToKeyIDs[notaryRoleToSigner(delRole.Name)] = delRole.KeyIDs
}
}
return signerRoleToKeyIDs
}
// aggregate all signers for a "released" hash+tagname pair. To be "released," the tag must have been
// signed into the "targets" or "targets/releases" role. Output is sorted by tag name
func matchReleasedSignatures(allTargets []client.TargetSignedStruct) trustTagRowList {
signatureRows := trustTagRowList{}
// do a first pass to get filter on tags signed into "targets" or "targets/releases"
releasedTargetRows := map[trustTagKey][]string{}
for _, tgt := range allTargets {
if isReleasedTarget(tgt.Role.Name) {
releasedKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
releasedTargetRows[releasedKey] = []string{}
}
}
// now fill out all signers on released keys
for _, tgt := range allTargets {
targetKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
// only considered released targets
if _, ok := releasedTargetRows[targetKey]; ok && !isReleasedTarget(tgt.Role.Name) {
releasedTargetRows[targetKey] = append(releasedTargetRows[targetKey], notaryRoleToSigner(tgt.Role.Name))
}
}
// compile the final output as a sorted slice
for targetKey, signers := range releasedTargetRows {
signatureRows = append(signatureRows, trustTagRow{targetKey, signers})
}
sort.Sort(signatureRows)
return signatureRows
}

View File

@ -2,12 +2,11 @@ package trust
import ( import (
"encoding/json" "encoding/json"
"fmt"
"sort" "sort"
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/pkg/errors" "github.com/docker/cli/cli/command/inspect"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/theupdateframework/notary/tuf/data" "github.com/theupdateframework/notary/tuf/data"
) )
@ -18,36 +17,24 @@ func newInspectCommand(dockerCli command.Cli) *cobra.Command {
Short: "Return low-level information about keys and signatures", Short: "Return low-level information about keys and signatures",
Args: cli.RequiresMinArgs(1), Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return inspectTrustInfo(dockerCli, args) return runInspect(dockerCli, args)
}, },
} }
return cmd return cmd
} }
func inspectTrustInfo(cli command.Cli, remotes []string) error { func runInspect(dockerCli command.Cli, remotes []string) error {
trustRepoInfoList := []trustRepo{} getRefFunc := func(ref string) (interface{}, []byte, error) {
for _, remote := range remotes { i, err := getRepoTrustInfo(dockerCli, ref)
trustInfo, err := getRepoTrustInfo(cli, remote) return nil, i, err
if err != nil {
return err
} }
if trustInfo == nil { return inspect.Inspect(dockerCli.Out(), remotes, "", getRefFunc)
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) { func getRepoTrustInfo(cli command.Cli, remote string) ([]byte, error) {
signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(cli, remote) signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(cli, remote)
if err != nil { if err != nil {
return nil, err return []byte{}, err
} }
// process the signatures to include repo admin if signed by the base targets role // process the signatures to include repo admin if signed by the base targets role
for idx, sig := range signatureRows { for idx, sig := range signatureRows {
@ -61,37 +48,36 @@ func getRepoTrustInfo(cli command.Cli, remote string) (*trustRepo, error) {
signerRoleToKeyIDs := getDelegationRoleToKeyMap(delegationRoles) signerRoleToKeyIDs := getDelegationRoleToKeyMap(delegationRoles)
for signerName, signerKeys := range signerRoleToKeyIDs { for signerName, signerKeys := range signerRoleToKeyIDs {
signerList = append(signerList, trustSigner{signerName, signerKeys}) signerKeyList := []trustKey{}
for _, keyID := range signerKeys {
signerKeyList = append(signerKeyList, trustKey{ID: keyID})
}
signerList = append(signerList, trustSigner{signerName, signerKeyList})
} }
sort.Slice(signerList, func(i, j int) bool { return signerList[i].Name > signerList[j].Name }) sort.Slice(signerList, func(i, j int) bool { return signerList[i].Name > signerList[j].Name })
for _, adminRole := range adminRolesWithSigs { for _, adminRole := range adminRolesWithSigs {
switch adminRole.Name { switch adminRole.Name {
case data.CanonicalRootRole: case data.CanonicalRootRole:
adminList = append(adminList, trustSigner{"Root", adminRole.KeyIDs}) rootKeys := []trustKey{}
for _, keyID := range adminRole.KeyIDs {
rootKeys = append(rootKeys, trustKey{ID: keyID})
}
adminList = append(adminList, trustSigner{"Root", rootKeys})
case data.CanonicalTargetsRole: case data.CanonicalTargetsRole:
adminList = append(adminList, trustSigner{"Repository", adminRole.KeyIDs}) targetKeys := []trustKey{}
for _, keyID := range adminRole.KeyIDs {
targetKeys = append(targetKeys, trustKey{ID: keyID})
}
adminList = append(adminList, trustSigner{"Repository", targetKeys})
} }
} }
sort.Slice(adminList, func(i, j int) bool { return adminList[i].Name > adminList[j].Name }) sort.Slice(adminList, func(i, j int) bool { return adminList[i].Name > adminList[j].Name })
return &trustRepo{ return json.Marshal(trustRepo{
Name: remote,
SignedTags: signatureRows, SignedTags: signatureRows,
Signers: signerList, Signers: signerList,
AdminstrativeKeys: adminList, AdminstrativeKeys: adminList,
}, nil })
}
// trustRepo represents consumable information about a trusted repository
type trustRepo struct {
SignedTags trustTagRowList `json:",omitempty"`
Signers []trustSigner `json:",omitempty"`
AdminstrativeKeys []trustSigner `json:",omitempty"`
}
// trustSigner represents a trusted signer in a trusted repository
// a signer is defined by a name and list of key IDs
type trustSigner struct {
Name string `json:",omitempty"`
Keys []string `json:",omitempty"`
} }

View File

@ -68,6 +68,7 @@ func TestTrustInspectCommandUninitializedErrors(t *testing.T) {
cmd.SetArgs([]string{"reg/unsigned-img"}) cmd.SetArgs([]string{"reg/unsigned-img"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img") testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img")
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-uninitialized.golden")
cli = test.NewFakeCli(&fakeClient{}) cli = test.NewFakeCli(&fakeClient{})
cli.SetNotaryClient(getUninitializedNotaryRepository) cli.SetNotaryClient(getUninitializedNotaryRepository)
@ -75,6 +76,7 @@ func TestTrustInspectCommandUninitializedErrors(t *testing.T) {
cmd.SetArgs([]string{"reg/unsigned-img:tag"}) cmd.SetArgs([]string{"reg/unsigned-img:tag"})
cmd.SetOutput(ioutil.Discard) cmd.SetOutput(ioutil.Discard)
testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img:tag") testutil.ErrorContains(t, cmd.Execute(), "No signatures or cannot access reg/unsigned-img:tag")
golden.Assert(t, cli.OutBuffer().String(), "trust-inspect-uninitialized.golden")
} }
func TestTrustInspectCommandEmptyNotaryRepo(t *testing.T) { func TestTrustInspectCommandEmptyNotaryRepo(t *testing.T) {

View File

@ -1 +1,25 @@
[{"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] [
{
"Name": "reg/img:unsigned-tag",
"SignedTags": [],
"Signers": [],
"AdminstrativeKeys": [
{
"Name": "Root",
"Keys": [
{
"ID": "rootID"
}
]
},
{
"Name": "Repository",
"Keys": [
{
"ID": "targetsID"
}
]
}
]
}
]

View File

@ -1 +1,33 @@
[{"SignedTags":[{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] [
{
"Name": "signed-repo",
"SignedTags": [
{
"SignedTag": "green",
"Digest": "677265656e2d646967657374",
"Signers": [
"Repo Admin"
]
}
],
"Signers": [],
"AdminstrativeKeys": [
{
"Name": "Root",
"Keys": [
{
"ID": "rootID"
}
]
},
{
"Name": "Repository",
"Keys": [
{
"ID": "targetsID"
}
]
}
]
}
]

View File

@ -1 +1,65 @@
[{"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"]}]}] [
{
"Name": "signed-repo",
"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": [
{
"ID": "B"
}
]
},
{
"Name": "alice",
"Keys": [
{
"ID": "A"
}
]
}
],
"AdminstrativeKeys": [
{
"Name": "Root",
"Keys": [
{
"ID": "rootID"
}
]
},
{
"Name": "Repository",
"Keys": [
{
"ID": "targetsID"
}
]
}
]
}
]

View File

@ -1 +1,128 @@
[{"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"]}]}] [
{
"Name": "signed-repo",
"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": [
{
"ID": "B"
}
]
},
{
"Name": "alice",
"Keys": [
{
"ID": "A"
}
]
}
],
"AdminstrativeKeys": [
{
"Name": "Root",
"Keys": [
{
"ID": "rootID"
}
]
},
{
"Name": "Repository",
"Keys": [
{
"ID": "targetsID"
}
]
}
]
},
{
"Name": "signed-repo",
"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": [
{
"ID": "B"
}
]
},
{
"Name": "alice",
"Keys": [
{
"ID": "A"
}
]
}
],
"AdminstrativeKeys": [
{
"Name": "Root",
"Keys": [
{
"ID": "rootID"
}
]
},
{
"Name": "Repository",
"Keys": [
{
"ID": "targetsID"
}
]
}
]
}
]

View File

@ -1 +1,33 @@
[{"SignedTags":[{"SignedTag":"green","Digest":"677265656e2d646967657374","Signers":["Repo Admin"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] [
{
"Name": "signed-repo:green",
"SignedTags": [
{
"SignedTag": "green",
"Digest": "677265656e2d646967657374",
"Signers": [
"Repo Admin"
]
}
],
"Signers": [],
"AdminstrativeKeys": [
{
"Name": "Root",
"Keys": [
{
"ID": "rootID"
}
]
},
{
"Name": "Repository",
"Keys": [
{
"ID": "targetsID"
}
]
}
]
}
]

View File

@ -0,0 +1 @@
[]

View File

@ -1 +1,42 @@
[{"Signers":[{"Name":"bob","Keys":["B"]},{"Name":"alice","Keys":["A"]}],"AdminstrativeKeys":[{"Name":"Root","Keys":["rootID"]},{"Name":"Repository","Keys":["targetsID"]}]}] [
{
"Name": "signed-repo:unsigned",
"SignedTags": [],
"Signers": [
{
"Name": "bob",
"Keys": [
{
"ID": "B"
}
]
},
{
"Name": "alice",
"Keys": [
{
"ID": "A"
}
]
}
],
"AdminstrativeKeys": [
{
"Name": "Root",
"Keys": [
{
"ID": "rootID"
}
]
},
{
"Name": "Repository",
"Keys": [
{
"ID": "targetsID"
}
]
}
]
}
]

View File

@ -1,8 +1,6 @@
package trust package trust
import ( import (
"context"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"sort" "sort"
@ -11,41 +9,10 @@ import (
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/command/formatter" "github.com/docker/cli/cli/command/formatter"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/trust"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/theupdateframework/notary"
"github.com/theupdateframework/notary/client" "github.com/theupdateframework/notary/client"
"github.com/theupdateframework/notary/tuf/data"
) )
// trustTagKey represents a unique signed tag and hex-encoded hash pair
type trustTagKey struct {
SignedTag string
Digest string
}
// trustTagRow encodes all human-consumable information for a signed tag, including signers
type trustTagRow struct {
trustTagKey
Signers []string
}
type trustTagRowList []trustTagRow
func (tagComparator trustTagRowList) Len() int {
return len(tagComparator)
}
func (tagComparator trustTagRowList) Less(i, j int) bool {
return tagComparator[i].SignedTag < tagComparator[j].SignedTag
}
func (tagComparator trustTagRowList) Swap(i, j int) {
tagComparator[i], tagComparator[j] = tagComparator[j], tagComparator[i]
}
func newViewCommand(dockerCli command.Cli) *cobra.Command { func newViewCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "view IMAGE[:TAG]", Use: "view IMAGE[:TAG]",
@ -87,51 +54,6 @@ func viewTrustInfo(cli command.Cli, remote string) error {
return nil return nil
} }
// lookupTrustInfo returns processed signature and role information about a notary repository.
// This information is to be pretty printed or serialized into a machine-readable format.
func lookupTrustInfo(cli command.Cli, remote string) (trustTagRowList, []client.RoleWithSignatures, []data.Role, error) {
ctx := context.Background()
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote)
if err != nil {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, err
}
tag := imgRefAndAuth.Tag()
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
if err != nil {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
}
if err = clearChangeList(notaryRepo); err != nil {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, err
}
defer clearChangeList(notaryRepo)
// Retrieve all released signatures, match them, and pretty print them
allSignedTargets, err := notaryRepo.GetAllTargetMetadataByName(tag)
if err != nil {
logrus.Debug(trust.NotaryError(remote, err))
// print an empty table if we don't have signed targets, but have an initialized notary repo
if _, ok := err.(client.ErrNoSuchTarget); !ok {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signatures or cannot access %s", remote)
}
}
signatureRows := matchReleasedSignatures(allSignedTargets)
// get the administrative roles
adminRolesWithSigs, err := notaryRepo.ListRoles()
if err != nil {
return trustTagRowList{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signers for %s", remote)
}
// get delegation roles with the canonical key IDs
delegationRoles, err := notaryRepo.GetDelegationRoles()
if err != nil {
logrus.Debugf("no delegation roles found, or error fetching them for %s: %v", remote, err)
}
return signatureRows, adminRolesWithSigs, delegationRoles, nil
}
func printSortedAdminKeys(out io.Writer, adminRoles []client.RoleWithSignatures) { func printSortedAdminKeys(out io.Writer, adminRoles []client.RoleWithSignatures) {
sort.Slice(adminRoles, func(i, j int) bool { return adminRoles[i].Name > adminRoles[j].Name }) sort.Slice(adminRoles, func(i, j int) bool { return adminRoles[i].Name > adminRoles[j].Name })
for _, adminRole := range adminRoles { for _, adminRole := range adminRoles {
@ -139,65 +61,6 @@ func printSortedAdminKeys(out io.Writer, adminRoles []client.RoleWithSignatures)
} }
} }
func formatAdminRole(roleWithSigs client.RoleWithSignatures) string {
adminKeyList := roleWithSigs.KeyIDs
sort.Strings(adminKeyList)
var role string
switch roleWithSigs.Name {
case data.CanonicalTargetsRole:
role = "Repository Key"
case data.CanonicalRootRole:
role = "Root Key"
default:
return ""
}
return fmt.Sprintf("%s:\t%s\n", role, strings.Join(adminKeyList, ", "))
}
func getDelegationRoleToKeyMap(rawDelegationRoles []data.Role) map[string][]string {
signerRoleToKeyIDs := make(map[string][]string)
for _, delRole := range rawDelegationRoles {
switch delRole.Name {
case trust.ReleasesRole, data.CanonicalRootRole, data.CanonicalSnapshotRole, data.CanonicalTargetsRole, data.CanonicalTimestampRole:
continue
default:
signerRoleToKeyIDs[notaryRoleToSigner(delRole.Name)] = delRole.KeyIDs
}
}
return signerRoleToKeyIDs
}
// aggregate all signers for a "released" hash+tagname pair. To be "released," the tag must have been
// signed into the "targets" or "targets/releases" role. Output is sorted by tag name
func matchReleasedSignatures(allTargets []client.TargetSignedStruct) trustTagRowList {
signatureRows := trustTagRowList{}
// do a first pass to get filter on tags signed into "targets" or "targets/releases"
releasedTargetRows := map[trustTagKey][]string{}
for _, tgt := range allTargets {
if isReleasedTarget(tgt.Role.Name) {
releasedKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
releasedTargetRows[releasedKey] = []string{}
}
}
// now fill out all signers on released keys
for _, tgt := range allTargets {
targetKey := trustTagKey{tgt.Target.Name, hex.EncodeToString(tgt.Target.Hashes[notary.SHA256])}
// only considered released targets
if _, ok := releasedTargetRows[targetKey]; ok && !isReleasedTarget(tgt.Role.Name) {
releasedTargetRows[targetKey] = append(releasedTargetRows[targetKey], notaryRoleToSigner(tgt.Role.Name))
}
}
// compile the final output as a sorted slice
for targetKey, signers := range releasedTargetRows {
signatureRows = append(signatureRows, trustTagRow{targetKey, signers})
}
sort.Sort(signatureRows)
return signatureRows
}
// pretty print with ordered rows // pretty print with ordered rows
func printSignatures(out io.Writer, signatureRows trustTagRowList) error { func printSignatures(out io.Writer, signatureRows trustTagRowList) error {
trustTagCtx := formatter.Context{ trustTagCtx := formatter.Context{

View File

@ -28,7 +28,8 @@ Return low-level information about keys and signatures
This includes all image tags that are signed, who signed them, and who can sign This includes all image tags that are signed, who signed them, and who can sign
new tags. new tags.
`docker trust inspect` is intended to be used for integrations into other systems, whereas `docker trust view` provides human-friendly output. `docker trust inspect` prints the trust information in a machine-readable format. Refer to
[`docker trust view`](trust_view.md) for a human-friendly output.
`docker trust inspect` is currently experimental. `docker trust inspect` is currently experimental.
@ -37,11 +38,14 @@ new tags.
### Get low-level details about signatures for a single image tag ### Get low-level details about signatures for a single image tag
Use the `docker trust inspect` to get trust information about an image. The
following example prints trust information for the `alpine:latest` image:
```bash ```bash
$ docker trust inspect alpine:latest | jq $ docker trust inspect alpine:latest
[ [
{ {
"Name": "alpine:latest",
"SignedTags": [ "SignedTags": [
{ {
"SignedTag": "latest", "SignedTag": "latest",
@ -51,17 +55,22 @@ $ docker trust inspect alpine:latest | jq
] ]
} }
], ],
"Signers": [],
"AdminstrativeKeys": [ "AdminstrativeKeys": [
{ {
"Name": "Repository", "Name": "Repository",
"Keys": [ "Keys": [
"5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd" {
"ID": "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd"
}
] ]
}, },
{ {
"Name": "Root", "Name": "Root",
"Keys": [ "Keys": [
"a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce" {
"ID": "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce"
}
] ]
} }
] ]
@ -78,10 +87,10 @@ This format mirrors the output of `docker trust view`
If signers are set up for the repository via other `docker trust` commands, `docker trust inspect` includes a `Signers` key: If signers are set up for the repository via other `docker trust` commands, `docker trust inspect` includes a `Signers` key:
```bash ```bash
$ docker trust inspect my-image:purple
$ docker trust inspect my-image:purple | jq
[ [
{ {
"Name": "my-image:purple",
"SignedTags": [ "SignedTags": [
{ {
"SignedTag": "purple", "SignedTag": "purple",
@ -97,21 +106,31 @@ $ docker trust inspect my-image:purple | jq
{ {
"Name": "alice", "Name": "alice",
"Keys": [ "Keys": [
"04dd031411ed671ae1e12f47ddc8646d98f135090b01e54c3561e843084484a3", {
"6a11e4898a4014d400332ab0e096308c844584ff70943cdd1d6628d577f45fd8" "ID": "04dd031411ed671ae1e12f47ddc8646d98f135090b01e54c3561e843084484a3"
},
{
"ID": "6a11e4898a4014d400332ab0e096308c844584ff70943cdd1d6628d577f45fd8"
}
] ]
}, },
{ {
"Name": "bob", "Name": "bob",
"Keys": [ "Keys": [
"433e245c656ae9733cdcc504bfa560f90950104442c4528c9616daa45824ccba" {
"ID": "433e245c656ae9733cdcc504bfa560f90950104442c4528c9616daa45824ccba"
}
] ]
}, },
{ {
"Name": "carol", "Name": "carol",
"Keys": [ "Keys": [
"d32fa8b5ca08273a2880f455fcb318da3dc80aeae1a30610815140deef8f30d9", {
"9a8bbec6ba2af88a5fad6047d428d17e6d05dbdd03d15b4fc8a9a0e8049cd606" "ID": "d32fa8b5ca08273a2880f455fcb318da3dc80aeae1a30610815140deef8f30d9"
},
{
"ID": "9a8bbec6ba2af88a5fad6047d428d17e6d05dbdd03d15b4fc8a9a0e8049cd606"
}
] ]
} }
], ],
@ -119,13 +138,17 @@ $ docker trust inspect my-image:purple | jq
{ {
"Name": "Repository", "Name": "Repository",
"Keys": [ "Keys": [
"27df2c8187e7543345c2e0bf3a1262e0bc63a72754e9a7395eac3f747ec23a44" {
"ID": "27df2c8187e7543345c2e0bf3a1262e0bc63a72754e9a7395eac3f747ec23a44"
}
] ]
}, },
{ {
"Name": "Root", "Name": "Root",
"Keys": [ "Keys": [
"40b66ccc8b176be8c7d365a17f3e046d1c3494e053dd57cfeacfe2e19c4f8e8f" {
"ID": "40b66ccc8b176be8c7d365a17f3e046d1c3494e053dd57cfeacfe2e19c4f8e8f"
}
] ]
} }
] ]
@ -140,23 +163,29 @@ $ docker trust inspect unsigned-img
No signatures or cannot access unsigned-img No signatures or cannot access unsigned-img
``` ```
However, if other tags are signed in the same image repository, `docker trust inspect` reports relevant key information and omits the `SignedTags` key. However, if other tags are signed in the same image repository, `docker trust inspect` reports relevant key information:
```bash ```bash
$ docker trust inspect alpine:unsigned | jq $ docker trust inspect alpine:unsigned
[ [
{ {
"Name": "alpine:unsigned",
"Signers": [],
"AdminstrativeKeys": [ "AdminstrativeKeys": [
{ {
"Name": "Repository", "Name": "Repository",
"Keys": [ "Keys": [
"5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd" {
"ID": "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd"
}
] ]
}, },
{ {
"Name": "Root", "Name": "Root",
"Keys": [ "Keys": [
"a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce" {
"ID": "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce"
}
] ]
} }
] ]
@ -166,14 +195,170 @@ $ docker trust inspect alpine:unsigned | jq
### Get details about signatures for all image tags in a repository ### Get details about signatures for all image tags in a repository
```bash If no tag is specified, `docker trust inspect` will report details for all signed tags in the repository:
$ docker trust inspect alpine | jq
```bash
$ docker trust inspect alpine
[
{
"Name": "alpine",
"SignedTags": [
{
"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"
]
}
],
"Signers": [],
"AdminstrativeKeys": [
{
"Name": "Repository",
"Keys": [
{
"ID": "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd"
}
]
},
{
"Name": "Root",
"Keys": [
{
"ID": "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce"
}
]
}
]
}
]
``` ```
### Get details about signatures for multiple images ### Get details about signatures for multiple images
`docker trust inspect` can take multiple repositories and images as arguments, and reports the results in an ordered list:
```bash ```bash
$ docker trust inspect alpine ubuntu | jq $ docker trust inspect alpine notary
[
{
"Name": "alpine",
"SignedTags": [
{
"SignedTag": "3.5",
"Digest": "b007a354427e1880de9cdba533e8e57382b7f2853a68a478a17d447b302c219c",
"Signers": [
"Repo Admin"
]
},
{
"SignedTag": "3.6",
"Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478",
"Signers": [
"Repo Admin"
]
},
{
"SignedTag": "edge",
"Digest": "23e7d843e63a3eee29b6b8cfcd10e23dd1ef28f47251a985606a31040bf8e096",
"Signers": [
"Repo Admin"
]
},
{
"SignedTag": "integ-test-base",
"Digest": "3952dc48dcc4136ccdde37fbef7e250346538a55a0366e3fccc683336377e372",
"Signers": [
"Repo Admin"
]
},
{
"SignedTag": "latest",
"Digest": "d6bfc3baf615dc9618209a8d607ba2a8103d9c8a405b3bd8741d88b4bef36478",
"Signers": [
"Repo Admin"
]
}
],
"Signers": [],
"AdminstrativeKeys": [
{
"Name": "Repository",
"Keys": [
{
"ID": "5a46c9aaa82ff150bb7305a2d17d0c521c2d784246807b2dc611f436a69041fd"
}
]
},
{
"Name": "Root",
"Keys": [
{
"ID": "a2489bcac7a79aa67b19b96c4a3bf0c675ffdf00c6d2fabe1a5df1115e80adce"
}
]
}
]
},
{
"Name": "notary",
"SignedTags": [
{
"SignedTag": "server",
"Digest": "71f64ab718a3331dee103bc5afc6bc492914738ce37c2d2f127a8133714ecf5c",
"Signers": [
"Repo Admin"
]
},
{
"SignedTag": "signer",
"Digest": "a6122d79b1e74f70b5dd933b18a6d1f99329a4728011079f06b245205f158fe8",
"Signers": [
"Repo Admin"
]
}
],
"Signers": [],
"AdminstrativeKeys": [
{
"Name": "Root",
"Keys": [
{
"ID": "8cdcdef5bd039f4ab5a029126951b5985eebf57cabdcdc4d21f5b3be8bb4ce92"
}
]
},
{
"Name": "Repository",
"Keys": [
{
"ID": "85bfd031017722f950d480a721f845a2944db26a3dc084040a70f1b0d9bbb3df"
}
]
}
]
}
]
``` ```