2017-11-17 15:04:51 -05:00
|
|
|
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"
|
2020-08-28 08:35:09 -04:00
|
|
|
"github.com/fvbommel/sortorder"
|
2017-11-17 15:04:51 -05:00
|
|
|
"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 {
|
2018-08-19 20:57:04 -04:00
|
|
|
Name string
|
|
|
|
SignedTags []trustTagRow
|
|
|
|
Signers []trustSigner
|
|
|
|
AdministrativeKeys []trustSigner
|
2017-11-17 15:04:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2023-09-09 18:27:44 -04:00
|
|
|
func lookupTrustInfo(ctx context.Context, cli command.Cli, remote string) ([]trustTagRow, []client.RoleWithSignatures, []data.Role, error) {
|
2023-03-15 11:21:02 -04:00
|
|
|
imgRefAndAuth, err := trust.GetImageReferencesAndAuth(ctx, image.AuthResolver(cli), remote)
|
2017-11-17 15:04:51 -05:00
|
|
|
if err != nil {
|
2018-07-08 15:08:17 -04:00
|
|
|
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, err
|
2017-11-17 15:04:51 -05:00
|
|
|
}
|
|
|
|
tag := imgRefAndAuth.Tag()
|
|
|
|
notaryRepo, err := cli.NotaryClient(imgRefAndAuth, trust.ActionsPullOnly)
|
|
|
|
if err != nil {
|
2018-07-08 15:08:17 -04:00
|
|
|
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, trust.NotaryError(imgRefAndAuth.Reference().Name(), err)
|
2017-11-17 15:04:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if err = clearChangeList(notaryRepo); err != nil {
|
2018-07-08 15:08:17 -04:00
|
|
|
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, err
|
2017-11-17 15:04:51 -05:00
|
|
|
}
|
|
|
|
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 {
|
linting: ST1005: error strings should not be capitalized (stylecheck)
While fixing, also updated errors without placeholders to `errors.New()`, and
updated some code to use pkg/errors if it was already in use in the file.
cli/command/config/inspect.go:59:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Cannot supply extra formatting options to the pretty template")
^
cli/command/node/inspect.go:61:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Cannot supply extra formatting options to the pretty template")
^
cli/command/secret/inspect.go:57:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Cannot supply extra formatting options to the pretty template")
^
cli/command/trust/common.go:77:74: ST1005: error strings should not be capitalized (stylecheck)
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signatures or cannot access %s", remote)
^
cli/command/trust/common.go:85:73: ST1005: error strings should not be capitalized (stylecheck)
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signers for %s", remote)
^
cli/command/trust/sign.go:137:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("No tag specified for %s", imgRefAndAuth.Name())
^
cli/command/trust/sign.go:151:19: ST1005: error strings should not be capitalized (stylecheck)
return *target, fmt.Errorf("No tag specified")
^
cli/command/trust/signer_add.go:77:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Failed to add signer to: %s", strings.Join(errRepos, ", "))
^
cli/command/trust/signer_remove.go:52:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Error removing signer from: %s", strings.Join(errRepos, ", "))
^
cli/command/trust/signer_remove.go:67:17: ST1005: error strings should not be capitalized (stylecheck)
return false, fmt.Errorf("All signed tags are currently revoked, use docker trust sign to fix")
^
cli/command/trust/signer_remove.go:108:17: ST1005: error strings should not be capitalized (stylecheck)
return false, fmt.Errorf("No signer %s for repository %s", signerName, repoName)
^
opts/hosts.go:89:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid bind address format: %s", addr)
^
opts/hosts.go:100:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr)
^
opts/hosts.go:119:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr)
^
opts/hosts.go:144:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
^
opts/hosts.go:155:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-09-02 18:04:53 -04:00
|
|
|
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("no signatures or cannot access %s", remote)
|
2017-11-17 15:04:51 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
signatureRows := matchReleasedSignatures(allSignedTargets)
|
|
|
|
|
|
|
|
// get the administrative roles
|
|
|
|
adminRolesWithSigs, err := notaryRepo.ListRoles()
|
|
|
|
if err != nil {
|
linting: ST1005: error strings should not be capitalized (stylecheck)
While fixing, also updated errors without placeholders to `errors.New()`, and
updated some code to use pkg/errors if it was already in use in the file.
cli/command/config/inspect.go:59:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Cannot supply extra formatting options to the pretty template")
^
cli/command/node/inspect.go:61:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Cannot supply extra formatting options to the pretty template")
^
cli/command/secret/inspect.go:57:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Cannot supply extra formatting options to the pretty template")
^
cli/command/trust/common.go:77:74: ST1005: error strings should not be capitalized (stylecheck)
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signatures or cannot access %s", remote)
^
cli/command/trust/common.go:85:73: ST1005: error strings should not be capitalized (stylecheck)
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("No signers for %s", remote)
^
cli/command/trust/sign.go:137:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("No tag specified for %s", imgRefAndAuth.Name())
^
cli/command/trust/sign.go:151:19: ST1005: error strings should not be capitalized (stylecheck)
return *target, fmt.Errorf("No tag specified")
^
cli/command/trust/signer_add.go:77:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Failed to add signer to: %s", strings.Join(errRepos, ", "))
^
cli/command/trust/signer_remove.go:52:10: ST1005: error strings should not be capitalized (stylecheck)
return fmt.Errorf("Error removing signer from: %s", strings.Join(errRepos, ", "))
^
cli/command/trust/signer_remove.go:67:17: ST1005: error strings should not be capitalized (stylecheck)
return false, fmt.Errorf("All signed tags are currently revoked, use docker trust sign to fix")
^
cli/command/trust/signer_remove.go:108:17: ST1005: error strings should not be capitalized (stylecheck)
return false, fmt.Errorf("No signer %s for repository %s", signerName, repoName)
^
opts/hosts.go:89:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid bind address format: %s", addr)
^
opts/hosts.go:100:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid proto, expected %s: %s", proto, addr)
^
opts/hosts.go:119:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid proto, expected tcp: %s", tryAddr)
^
opts/hosts.go:144:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
^
opts/hosts.go:155:14: ST1005: error strings should not be capitalized (stylecheck)
return "", fmt.Errorf("Invalid bind address format: %s", tryAddr)
^
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2022-09-02 18:04:53 -04:00
|
|
|
return []trustTagRow{}, []client.RoleWithSignatures{}, []data.Role{}, fmt.Errorf("no signers for %s", remote)
|
2017-11-17 15:04:51 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
2018-07-08 15:08:17 -04:00
|
|
|
func matchReleasedSignatures(allTargets []client.TargetSignedStruct) []trustTagRow {
|
|
|
|
signatureRows := []trustTagRow{}
|
2017-11-17 15:04:51 -05:00
|
|
|
// 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})
|
|
|
|
}
|
2018-07-08 15:08:17 -04:00
|
|
|
sort.Slice(signatureRows, func(i, j int) bool {
|
2018-08-08 11:55:48 -04:00
|
|
|
return sortorder.NaturalLess(signatureRows[i].SignedTag, signatureRows[j].SignedTag)
|
2018-07-08 15:08:17 -04:00
|
|
|
})
|
2017-11-17 15:04:51 -05:00
|
|
|
return signatureRows
|
|
|
|
}
|