| // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: |
| //go:build go1.25 |
| |
| package trust |
| |
| import ( |
| "context" |
| "encoding/json" |
| "fmt" |
| "sort" |
| |
| "github.com/docker/cli/cli" |
| "github.com/docker/cli/cli/command" |
| "github.com/docker/cli/cli/command/inspect" |
| "github.com/spf13/cobra" |
| "github.com/theupdateframework/notary/tuf/data" |
| ) |
| |
| type inspectOptions struct { |
| remotes []string |
| // FIXME(n4ss): this is consistent with `docker service inspect` but we should provide |
| // a `--format` flag too. (format and pretty-print should be exclusive) |
| prettyPrint bool |
| } |
| |
| func newInspectCommand(dockerCLI command.Cli) *cobra.Command { |
| options := inspectOptions{} |
| cmd := &cobra.Command{ |
| Use: "inspect IMAGE[:TAG] [IMAGE[:TAG]...]", |
| Short: "Return low-level information about keys and signatures", |
| Args: cli.RequiresMinArgs(1), |
| RunE: func(cmd *cobra.Command, args []string) error { |
| options.remotes = args |
| |
| return runInspect(cmd.Context(), dockerCLI, options) |
| }, |
| DisableFlagsInUseLine: true, |
| } |
| |
| flags := cmd.Flags() |
| flags.BoolVar(&options.prettyPrint, "pretty", false, "Print the information in a human friendly format") |
| |
| return cmd |
| } |
| |
| func runInspect(ctx context.Context, dockerCLI command.Cli, opts inspectOptions) error { |
| if opts.prettyPrint { |
| var err error |
| |
| for index, remote := range opts.remotes { |
| if err = prettyPrintTrustInfo(ctx, dockerCLI, remote); err != nil { |
| return err |
| } |
| |
| // Additional separator between the inspection output of each image |
| if index < len(opts.remotes)-1 { |
| _, _ = fmt.Fprint(dockerCLI.Out(), "\n\n") |
| } |
| } |
| |
| return err |
| } |
| |
| getRefFunc := func(ref string) (any, []byte, error) { |
| i, err := getRepoTrustInfo(ctx, dockerCLI, ref) |
| return nil, i, err |
| } |
| return inspect.Inspect(dockerCLI.Out(), opts.remotes, "", getRefFunc) |
| } |
| |
| func getRepoTrustInfo(ctx context.Context, dockerCLI command.Cli, remote string) ([]byte, error) { |
| signatureRows, adminRolesWithSigs, delegationRoles, err := lookupTrustInfo(ctx, dockerCLI, remote) |
| if err != nil { |
| return []byte{}, err |
| } |
| // 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 = []string{releasedRoleName} |
| } |
| } |
| |
| signerList, adminList := []trustSigner{}, []trustSigner{} |
| |
| signerRoleToKeyIDs := getDelegationRoleToKeyMap(delegationRoles) |
| |
| for signerName, signerKeys := range signerRoleToKeyIDs { |
| 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 }) |
| |
| for _, adminRole := range adminRolesWithSigs { |
| switch adminRole.Name { |
| case data.CanonicalRootRole: |
| rootKeys := []trustKey{} |
| for _, keyID := range adminRole.KeyIDs { |
| rootKeys = append(rootKeys, trustKey{ID: keyID}) |
| } |
| adminList = append(adminList, trustSigner{"Root", rootKeys}) |
| case data.CanonicalTargetsRole: |
| 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 }) |
| |
| return json.Marshal(trustRepo{ |
| Name: remote, |
| SignedTags: signatureRows, |
| Signers: signerList, |
| AdministrativeKeys: adminList, |
| }) |
| } |