File: reader.go

package info (click to toggle)
docker.io 27.5.1%2Bdfsg4-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 67,452 kB
  • sloc: sh: 5,847; makefile: 1,146; ansic: 664; python: 162; asm: 133
file content (257 lines) | stat: -rw-r--r-- 9,792 bytes parent folder | download | duplicates (7)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
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
}