2017-11-15 14:50:33 -05:00
|
|
|
package trust
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2018-03-09 12:37:43 -05:00
|
|
|
"fmt"
|
2017-11-15 14:50:33 -05:00
|
|
|
"sort"
|
|
|
|
|
|
|
|
"github.com/docker/cli/cli"
|
|
|
|
"github.com/docker/cli/cli/command"
|
2017-11-17 15:04:51 -05:00
|
|
|
"github.com/docker/cli/cli/command/inspect"
|
2017-11-15 14:50:33 -05:00
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"github.com/theupdateframework/notary/tuf/data"
|
|
|
|
)
|
|
|
|
|
2018-03-09 12:37:43 -05:00
|
|
|
type inspectOptions struct {
|
|
|
|
remotes []string
|
2018-03-09 14:26:43 -05:00
|
|
|
// FIXME(n4ss): this is consistent with `docker service inspect` but we should provide
|
|
|
|
// a `--format` flag too. (format and pretty-print should be exclusive)
|
2018-03-09 12:37:43 -05:00
|
|
|
prettyPrint bool
|
|
|
|
}
|
|
|
|
|
2017-11-15 14:50:33 -05:00
|
|
|
func newInspectCommand(dockerCli command.Cli) *cobra.Command {
|
2018-03-09 12:37:43 -05:00
|
|
|
options := inspectOptions{}
|
2017-11-15 14:50:33 -05:00
|
|
|
cmd := &cobra.Command{
|
2017-11-16 13:09:57 -05:00
|
|
|
Use: "inspect IMAGE[:TAG] [IMAGE[:TAG]...]",
|
2017-11-15 14:50:33 -05:00
|
|
|
Short: "Return low-level information about keys and signatures",
|
2017-11-16 13:09:57 -05:00
|
|
|
Args: cli.RequiresMinArgs(1),
|
2017-11-15 14:50:33 -05:00
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
2018-03-09 12:37:43 -05:00
|
|
|
options.remotes = args
|
|
|
|
|
|
|
|
return runInspect(dockerCli, options)
|
2017-11-15 14:50:33 -05:00
|
|
|
},
|
|
|
|
}
|
2018-03-09 12:37:43 -05:00
|
|
|
|
|
|
|
flags := cmd.Flags()
|
|
|
|
flags.BoolVar(&options.prettyPrint, "pretty", false, "Print the information in a human friendly format")
|
|
|
|
|
2017-11-15 14:50:33 -05:00
|
|
|
return cmd
|
|
|
|
}
|
|
|
|
|
2018-03-09 12:37:43 -05:00
|
|
|
func runInspect(dockerCli command.Cli, opts inspectOptions) error {
|
|
|
|
if opts.prettyPrint {
|
2018-03-09 14:26:43 -05:00
|
|
|
var err error
|
2018-03-09 12:37:43 -05:00
|
|
|
|
|
|
|
for index, remote := range opts.remotes {
|
|
|
|
if err = prettyPrintTrustInfo(dockerCli, remote); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Additional separator between the inspection output of each image
|
2018-03-09 14:26:43 -05:00
|
|
|
if index < len(opts.remotes)-1 {
|
2018-03-09 12:37:43 -05:00
|
|
|
fmt.Fprint(dockerCli.Out(), "\n\n")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-11-17 15:04:51 -05:00
|
|
|
getRefFunc := func(ref string) (interface{}, []byte, error) {
|
|
|
|
i, err := getRepoTrustInfo(dockerCli, ref)
|
|
|
|
return nil, i, err
|
2017-11-16 13:09:57 -05:00
|
|
|
}
|
2018-03-09 12:37:43 -05:00
|
|
|
return inspect.Inspect(dockerCli.Out(), opts.remotes, "", getRefFunc)
|
2017-11-16 13:09:57 -05:00
|
|
|
}
|
|
|
|
|
2017-11-17 15:04:51 -05:00
|
|
|
func getRepoTrustInfo(cli command.Cli, remote string) ([]byte, error) {
|
2017-11-15 14:50:33 -05:00
|
|
|
signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(cli, remote)
|
|
|
|
if err != nil {
|
2017-11-17 15:04:51 -05:00
|
|
|
return []byte{}, err
|
2017-11-15 14:50:33 -05:00
|
|
|
}
|
|
|
|
// process the signatures to include repo admin if signed by the base targets role
|
|
|
|
for idx, sig := range signatureRows {
|
|
|
|
if len(sig.Signers) == 0 {
|
|
|
|
signatureRows[idx].Signers = append(sig.Signers, releasedRoleName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
signerList, adminList := []trustSigner{}, []trustSigner{}
|
|
|
|
|
|
|
|
signerRoleToKeyIDs := getDelegationRoleToKeyMap(delegationRoles)
|
|
|
|
|
|
|
|
for signerName, signerKeys := range signerRoleToKeyIDs {
|
2017-11-17 15:04:51 -05:00
|
|
|
signerKeyList := []trustKey{}
|
|
|
|
for _, keyID := range signerKeys {
|
|
|
|
signerKeyList = append(signerKeyList, trustKey{ID: keyID})
|
|
|
|
}
|
|
|
|
signerList = append(signerList, trustSigner{signerName, signerKeyList})
|
2017-11-15 14:50:33 -05:00
|
|
|
}
|
|
|
|
sort.Slice(signerList, func(i, j int) bool { return signerList[i].Name > signerList[j].Name })
|
|
|
|
|
|
|
|
for _, adminRole := range adminRolesWithSigs {
|
|
|
|
switch adminRole.Name {
|
|
|
|
case data.CanonicalRootRole:
|
2017-11-17 15:04:51 -05:00
|
|
|
rootKeys := []trustKey{}
|
|
|
|
for _, keyID := range adminRole.KeyIDs {
|
|
|
|
rootKeys = append(rootKeys, trustKey{ID: keyID})
|
|
|
|
}
|
|
|
|
adminList = append(adminList, trustSigner{"Root", rootKeys})
|
2017-11-15 14:50:33 -05:00
|
|
|
case data.CanonicalTargetsRole:
|
2017-11-17 15:04:51 -05:00
|
|
|
targetKeys := []trustKey{}
|
|
|
|
for _, keyID := range adminRole.KeyIDs {
|
|
|
|
targetKeys = append(targetKeys, trustKey{ID: keyID})
|
|
|
|
}
|
|
|
|
adminList = append(adminList, trustSigner{"Repository", targetKeys})
|
2017-11-15 14:50:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
sort.Slice(adminList, func(i, j int) bool { return adminList[i].Name > adminList[j].Name })
|
|
|
|
|
2017-11-17 15:04:51 -05:00
|
|
|
return json.Marshal(trustRepo{
|
2018-08-19 20:57:04 -04:00
|
|
|
Name: remote,
|
|
|
|
SignedTags: signatureRows,
|
|
|
|
Signers: signerList,
|
|
|
|
AdministrativeKeys: adminList,
|
2017-11-17 15:04:51 -05:00
|
|
|
})
|
2017-11-15 14:50:33 -05:00
|
|
|
}
|