Files
docker-cli/cli/command/trust/common.go
Sebastiaan van Stijn 0ba820ed0b cli/trust: remove special handling for "plugin" Class
This code depended on the registry Service interface, which has been removed,
so needed to be refactored. Digging further into the reason this code existed,
it looked like the Class=plugin was previously required on Docker Hub to handle
plugins, but this requirement is no longer there, so we can remove this special
handling.

This patch removes the special handling to both remove the use of the registry.Service
interface, as well as removing complexity that is no longer needed.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2023-03-23 13:44:48 +01:00

157 lines
5.4 KiB
Go

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/fvbommel/sortorder"
"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
}
// trustRepo represents consumable information about a trusted repository
type trustRepo struct {
Name string
SignedTags []trustTagRow
Signers []trustSigner
AdministrativeKeys []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) ([]trustTagRow, []client.RoleWithSignatures, []data.Role, error) {
ctx := context.Background()
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote)
if err != nil {
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, err
}
tag := imgRefAndAuth.Tag()
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
if err != nil {
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
}
if err = clearChangeList(notaryRepo); err != nil {
return []trustTagRow{}, []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 []trustTagRow{}, []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 []trustTagRow{}, []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) []trustTagRow {
signatureRows := []trustTagRow{}
// 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.Slice(signatureRows, func(i, j int) bool {
return sortorder.NaturalLess(signatureRows[i].SignedTag, signatureRows[j].SignedTag)
})
return signatureRows
}