mirror of https://github.com/docker/cli.git
258 lines
9.6 KiB
Go
258 lines
9.6 KiB
Go
|
package client
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
|
||
|
canonicaljson "github.com/docker/go/canonical/json"
|
||
|
store "github.com/theupdateframework/notary/storage"
|
||
|
"github.com/theupdateframework/notary/tuf"
|
||
|
"github.com/theupdateframework/notary/tuf/data"
|
||
|
"github.com/theupdateframework/notary/tuf/utils"
|
||
|
)
|
||
|
|
||
|
// Target represents a simplified version of the data TUF operates on, so external
|
||
|
// applications don't have to depend on TUF data types.
|
||
|
type Target struct {
|
||
|
Name string // the name of the target
|
||
|
Hashes data.Hashes // the hash of the target
|
||
|
Length int64 // the size in bytes of the target
|
||
|
Custom *canonicaljson.RawMessage // the custom data provided to describe the file at TARGETPATH
|
||
|
}
|
||
|
|
||
|
// TargetWithRole represents a Target that exists in a particular role - this is
|
||
|
// produced by ListTargets and GetTargetByName
|
||
|
type TargetWithRole struct {
|
||
|
Target
|
||
|
Role data.RoleName
|
||
|
}
|
||
|
|
||
|
// TargetSignedStruct is a struct that contains a Target, the role it was found in, and the list of signatures for that role
|
||
|
type TargetSignedStruct struct {
|
||
|
Role data.DelegationRole
|
||
|
Target Target
|
||
|
Signatures []data.Signature
|
||
|
}
|
||
|
|
||
|
//ErrNoSuchTarget is returned when no valid trust data is found.
|
||
|
type ErrNoSuchTarget string
|
||
|
|
||
|
func (f ErrNoSuchTarget) Error() string {
|
||
|
return fmt.Sprintf("No valid trust data for %s", string(f))
|
||
|
}
|
||
|
|
||
|
// RoleWithSignatures is a Role with its associated signatures
|
||
|
type RoleWithSignatures struct {
|
||
|
Signatures []data.Signature
|
||
|
data.Role
|
||
|
}
|
||
|
|
||
|
// NewReadOnly is the base method that returns a new notary repository for reading.
|
||
|
// It expects an initialized cache. In case of a nil remote store, a default
|
||
|
// offline store is used.
|
||
|
func NewReadOnly(repo *tuf.Repo) ReadOnly {
|
||
|
return &reader{tufRepo: repo}
|
||
|
}
|
||
|
|
||
|
type reader struct {
|
||
|
tufRepo *tuf.Repo
|
||
|
}
|
||
|
|
||
|
// ListTargets lists all targets for the current repository. The list of
|
||
|
// roles should be passed in order from highest to lowest priority.
|
||
|
//
|
||
|
// IMPORTANT: if you pass a set of roles such as [ "targets/a", "targets/x"
|
||
|
// "targets/a/b" ], even though "targets/a/b" is part of the "targets/a" subtree
|
||
|
// its entries will be strictly shadowed by those in other parts of the "targets/a"
|
||
|
// subtree and also the "targets/x" subtree, as we will defer parsing it until
|
||
|
// we explicitly reach it in our iteration of the provided list of roles.
|
||
|
func (r *reader) ListTargets(roles ...data.RoleName) ([]*TargetWithRole, error) {
|
||
|
if len(roles) == 0 {
|
||
|
roles = []data.RoleName{data.CanonicalTargetsRole}
|
||
|
}
|
||
|
targets := make(map[string]*TargetWithRole)
|
||
|
for _, role := range roles {
|
||
|
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
||
|
skipRoles := utils.RoleNameSliceRemove(roles, role)
|
||
|
|
||
|
// Define a visitor function to populate the targets map in priority order
|
||
|
listVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||
|
// We found targets so we should try to add them to our targets map
|
||
|
for targetName, targetMeta := range tgt.Signed.Targets {
|
||
|
// Follow the priority by not overriding previously set targets
|
||
|
// and check that this path is valid with this role
|
||
|
if _, ok := targets[targetName]; ok || !validRole.CheckPaths(targetName) {
|
||
|
continue
|
||
|
}
|
||
|
targets[targetName] = &TargetWithRole{
|
||
|
Target: Target{
|
||
|
Name: targetName,
|
||
|
Hashes: targetMeta.Hashes,
|
||
|
Length: targetMeta.Length,
|
||
|
Custom: targetMeta.Custom,
|
||
|
},
|
||
|
Role: validRole.Name,
|
||
|
}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
r.tufRepo.WalkTargets("", role, listVisitorFunc, skipRoles...)
|
||
|
}
|
||
|
|
||
|
var targetList []*TargetWithRole
|
||
|
for _, v := range targets {
|
||
|
targetList = append(targetList, v)
|
||
|
}
|
||
|
|
||
|
return targetList, nil
|
||
|
}
|
||
|
|
||
|
// GetTargetByName returns a target by the given name. If no roles are passed
|
||
|
// it uses the targets role and does a search of the entire delegation
|
||
|
// graph, finding the first entry in a breadth first search of the delegations.
|
||
|
// If roles are passed, they should be passed in descending priority and
|
||
|
// the target entry found in the subtree of the highest priority role
|
||
|
// will be returned.
|
||
|
// See the IMPORTANT section on ListTargets above. Those roles also apply here.
|
||
|
func (r *reader) GetTargetByName(name string, roles ...data.RoleName) (*TargetWithRole, error) {
|
||
|
if len(roles) == 0 {
|
||
|
roles = append(roles, data.CanonicalTargetsRole)
|
||
|
}
|
||
|
var resultMeta data.FileMeta
|
||
|
var resultRoleName data.RoleName
|
||
|
var foundTarget bool
|
||
|
for _, role := range roles {
|
||
|
// Define an array of roles to skip for this walk (see IMPORTANT comment above)
|
||
|
skipRoles := utils.RoleNameSliceRemove(roles, role)
|
||
|
|
||
|
// Define a visitor function to find the specified target
|
||
|
getTargetVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||
|
if tgt == nil {
|
||
|
return nil
|
||
|
}
|
||
|
// We found the target and validated path compatibility in our walk,
|
||
|
// so we should stop our walk and set the resultMeta and resultRoleName variables
|
||
|
if resultMeta, foundTarget = tgt.Signed.Targets[name]; foundTarget {
|
||
|
resultRoleName = validRole.Name
|
||
|
return tuf.StopWalk{}
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
// Check that we didn't error, and that we assigned to our target
|
||
|
if err := r.tufRepo.WalkTargets(name, role, getTargetVisitorFunc, skipRoles...); err == nil && foundTarget {
|
||
|
return &TargetWithRole{Target: Target{Name: name, Hashes: resultMeta.Hashes, Length: resultMeta.Length, Custom: resultMeta.Custom}, Role: resultRoleName}, nil
|
||
|
}
|
||
|
}
|
||
|
return nil, ErrNoSuchTarget(name)
|
||
|
|
||
|
}
|
||
|
|
||
|
// GetAllTargetMetadataByName searches the entire delegation role tree to find the specified target by name for all
|
||
|
// roles, and returns a list of TargetSignedStructs for each time it finds the specified target.
|
||
|
// If given an empty string for a target name, it will return back all targets signed into the repository in every role
|
||
|
func (r *reader) GetAllTargetMetadataByName(name string) ([]TargetSignedStruct, error) {
|
||
|
var targetInfoList []TargetSignedStruct
|
||
|
|
||
|
// Define a visitor function to find the specified target
|
||
|
getAllTargetInfoByNameVisitorFunc := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||
|
if tgt == nil {
|
||
|
return nil
|
||
|
}
|
||
|
// We found a target and validated path compatibility in our walk,
|
||
|
// so add it to our list if we have a match
|
||
|
// if we have an empty name, add all targets, else check if we have it
|
||
|
var targetMetaToAdd data.Files
|
||
|
if name == "" {
|
||
|
targetMetaToAdd = tgt.Signed.Targets
|
||
|
} else {
|
||
|
if meta, ok := tgt.Signed.Targets[name]; ok {
|
||
|
targetMetaToAdd = data.Files{name: meta}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for targetName, resultMeta := range targetMetaToAdd {
|
||
|
targetInfo := TargetSignedStruct{
|
||
|
Role: validRole,
|
||
|
Target: Target{Name: targetName, Hashes: resultMeta.Hashes, Length: resultMeta.Length, Custom: resultMeta.Custom},
|
||
|
Signatures: tgt.Signatures,
|
||
|
}
|
||
|
targetInfoList = append(targetInfoList, targetInfo)
|
||
|
}
|
||
|
// continue walking to all child roles
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
// Check that we didn't error, and that we found the target at least once
|
||
|
if err := r.tufRepo.WalkTargets(name, "", getAllTargetInfoByNameVisitorFunc); err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
if len(targetInfoList) == 0 {
|
||
|
return nil, ErrNoSuchTarget(name)
|
||
|
}
|
||
|
return targetInfoList, nil
|
||
|
}
|
||
|
|
||
|
// ListRoles returns a list of RoleWithSignatures objects for this repo
|
||
|
// This represents the latest metadata for each role in this repo
|
||
|
func (r *reader) ListRoles() ([]RoleWithSignatures, error) {
|
||
|
// Get all role info from our updated keysDB, can be empty
|
||
|
roles := r.tufRepo.GetAllLoadedRoles()
|
||
|
|
||
|
var roleWithSigs []RoleWithSignatures
|
||
|
|
||
|
// Populate RoleWithSignatures with Role from keysDB and signatures from TUF metadata
|
||
|
for _, role := range roles {
|
||
|
roleWithSig := RoleWithSignatures{Role: *role, Signatures: nil}
|
||
|
switch role.Name {
|
||
|
case data.CanonicalRootRole:
|
||
|
roleWithSig.Signatures = r.tufRepo.Root.Signatures
|
||
|
case data.CanonicalTargetsRole:
|
||
|
roleWithSig.Signatures = r.tufRepo.Targets[data.CanonicalTargetsRole].Signatures
|
||
|
case data.CanonicalSnapshotRole:
|
||
|
roleWithSig.Signatures = r.tufRepo.Snapshot.Signatures
|
||
|
case data.CanonicalTimestampRole:
|
||
|
roleWithSig.Signatures = r.tufRepo.Timestamp.Signatures
|
||
|
default:
|
||
|
if !data.IsDelegation(role.Name) {
|
||
|
continue
|
||
|
}
|
||
|
if _, ok := r.tufRepo.Targets[role.Name]; ok {
|
||
|
// We'll only find a signature if we've published any targets with this delegation
|
||
|
roleWithSig.Signatures = r.tufRepo.Targets[role.Name].Signatures
|
||
|
}
|
||
|
}
|
||
|
roleWithSigs = append(roleWithSigs, roleWithSig)
|
||
|
}
|
||
|
return roleWithSigs, nil
|
||
|
}
|
||
|
|
||
|
// GetDelegationRoles returns the keys and roles of the repository's delegations
|
||
|
// Also converts key IDs to canonical key IDs to keep consistent with signing prompts
|
||
|
func (r *reader) GetDelegationRoles() ([]data.Role, error) {
|
||
|
// All top level delegations (ex: targets/level1) are stored exclusively in targets.json
|
||
|
_, ok := r.tufRepo.Targets[data.CanonicalTargetsRole]
|
||
|
if !ok {
|
||
|
return nil, store.ErrMetaNotFound{Resource: data.CanonicalTargetsRole.String()}
|
||
|
}
|
||
|
|
||
|
// make a copy for traversing nested delegations
|
||
|
allDelegations := []data.Role{}
|
||
|
|
||
|
// Define a visitor function to populate the delegations list and translate their key IDs to canonical IDs
|
||
|
delegationCanonicalListVisitor := func(tgt *data.SignedTargets, validRole data.DelegationRole) interface{} {
|
||
|
// For the return list, update with a copy that includes canonicalKeyIDs
|
||
|
// These aren't validated by the validRole
|
||
|
canonicalDelegations, err := translateDelegationsToCanonicalIDs(tgt.Signed.Delegations)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
allDelegations = append(allDelegations, canonicalDelegations...)
|
||
|
return nil
|
||
|
}
|
||
|
err := r.tufRepo.WalkTargets("", "", delegationCanonicalListVisitor)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
return allDelegations, nil
|
||
|
}
|